commit 9e32a4d7867d0d8059a735a21f7a81d9890fe3ca Author: Edwin Noorlander Date: Thu Jul 30 16:26:21 2026 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a09980 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +config.php +storage/geoip/*.mmdb +storage/geoip/*.bin +*.log +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..3d9a2a6 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,183 @@ +body { + background-color: #f5f6fa; + font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; +} + +.admin-sidebar { + position: fixed; + top: 0; + left: 0; + width: 240px; + min-height: 100vh; + z-index: 1030; + display: flex; + flex-direction: column; + color: #fff; +} + +.sidebar-header { + padding: 1.25rem 1rem; + font-weight: 600; + font-size: 1.1rem; + border-bottom: 1px solid rgba(255,255,255,0.1); +} + +.sidebar-nav { + flex: 1; + padding: 0.75rem 0; + overflow-y: auto; +} + +.nav-section { + padding: 0.5rem 1rem 0.25rem; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.08em; + opacity: 0.6; +} + +.sidebar-nav .nav-link { + color: rgba(255,255,255,0.85); + padding: 0.6rem 1rem; + font-size: 0.9rem; + transition: all 0.15s ease; + display: block; + text-decoration: none; +} + +.sidebar-nav .nav-link:hover { + color: #fff; + background: rgba(255,255,255,0.1); +} + +.sidebar-nav .nav-link.active { + color: #fff; + border-left: 3px solid #fff; + background: rgba(255,255,255,0.12); + font-weight: 500; +} + +.sidebar-footer { + padding: 0.75rem 1rem; + border-top: 1px solid rgba(255,255,255,0.1); + text-align: center; + opacity: 0.6; +} + +.admin-main { + margin-left: 240px; + padding: 2rem; + min-height: 100vh; +} + +.stat-card { + background: #fff; + border-radius: 0.5rem; + padding: 1.25rem; + display: flex; + align-items: center; + gap: 1rem; + box-shadow: 0 1px 3px rgba(0,0,0,0.08); + transition: box-shadow 0.2s ease; +} + +.stat-card:hover { + box-shadow: 0 4px 12px rgba(0,0,0,0.1); +} + +.stat-icon { + width: 48px; + height: 48px; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.4rem; + flex-shrink: 0; +} + +.stat-body { + flex: 1; + min-width: 0; +} + +.stat-value { + font-size: 1.5rem; + font-weight: 700; + line-height: 1.2; +} + +.stat-label { + font-size: 0.85rem; + color: #6c757d; +} + +.card { + border: none; + border-radius: 0.5rem; +} + +.card-header { + border-bottom: 1px solid #e9ecef; + font-weight: 500; + border-radius: 0.5rem 0.5rem 0 0 !important; + padding: 0.75rem 1.25rem; +} + +.badge { + font-weight: 500; +} + +.map-tooltip { + position: absolute; + background: rgba(0,0,0,0.8); + color: #fff; + padding: 0.3rem 0.6rem; + border-radius: 0.25rem; + font-size: 0.85rem; + pointer-events: none; + z-index: 1050; + white-space: nowrap; +} + +.world-map-wrapper { + position: relative; +} + +.table > :not(caption) > * > * { + vertical-align: middle; +} + +.progress { + border-radius: 4px; + background-color: #e9ecef; +} + +.progress-bar { + border-radius: 4px; +} + +.pagination { + margin-bottom: 0; +} + +@media (max-width: 767.98px) { + .admin-sidebar { + position: relative; + width: 100%; + min-height: auto; + } + + .admin-main { + margin-left: 0; + padding: 1rem; + } + + .stat-card { + padding: 1rem; + } + + .stat-value { + font-size: 1.2rem; + } +} diff --git a/assets/img/world-map.svg b/assets/img/world-map.svg new file mode 100644 index 0000000..a0ca14b --- /dev/null +++ b/assets/img/world-map.svg @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..8f2d965 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,4 @@ +document.addEventListener('DOMContentLoaded', function () { + var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (el) { return new bootstrap.Tooltip(el); }); +}); diff --git a/cli/generate-map.php b/cli/generate-map.php new file mode 100644 index 0000000..852bc4f --- /dev/null +++ b/cli/generate-map.php @@ -0,0 +1,161 @@ + alpha-2 mapping +$isoNumericToAlpha2 = [ + 4 => 'AF', 8 => 'AL', 12 => 'DZ', 20 => 'EG', 24 => 'AO', + 31 => 'AZ', 32 => 'AR', 36 => 'AU', 40 => 'AT', 50 => 'BD', + 51 => 'AM', 56 => 'BE', 64 => 'BT', 68 => 'BO', 70 => 'BA', + 72 => 'BW', 76 => 'BR', 84 => 'BZ', 90 => 'SB', 96 => 'BN', + 100 => 'BG', 104 => 'MM', 108 => 'BI', 112 => 'BY', 116 => 'KH', + 120 => 'CM', 124 => 'CA', 132 => 'CV', 140 => 'CF', 144 => 'LK', + 148 => 'TD', 152 => 'CL', 156 => 'CN', 158 => 'TW', 162 => 'CX', + 166 => 'CC', 170 => 'CO', 174 => 'KM', 178 => 'CG', 180 => 'CD', + 184 => 'CK', 188 => 'CR', 191 => 'HR', 192 => 'CU', 196 => 'CY', + 203 => 'CZ', 204 => 'BJ', 208 => 'DK', 212 => 'DM', 214 => 'DO', + 218 => 'EC', 222 => 'SV', 226 => 'GQ', 231 => 'ET', 232 => 'ER', + 233 => 'EE', 234 => 'FO', 238 => 'FK', 239 => 'GS', 242 => 'FJ', + 246 => 'FI', 248 => 'AX', 250 => 'FR', 254 => 'GF', 258 => 'PF', + 260 => 'TF', 262 => 'DJ', 266 => 'GA', 268 => 'GE', 270 => 'GM', + 276 => 'DE', 288 => 'GH', 292 => 'GI', 296 => 'KI', 300 => 'GR', + 304 => 'GL', 308 => 'GD', 312 => 'GP', 316 => 'GU', 320 => 'GT', + 324 => 'GN', 328 => 'GY', 332 => 'HT', 334 => 'HM', 336 => 'VA', + 340 => 'HN', 344 => 'HK', 348 => 'HU', 352 => 'IS', 356 => 'IN', + 360 => 'ID', 364 => 'IR', 368 => 'IQ', 372 => 'IE', 376 => 'IL', + 380 => 'IT', 384 => 'CI', 388 => 'JM', 392 => 'JP', 398 => 'KZ', + 400 => 'JO', 404 => 'KE', 408 => 'KP', 410 => 'KR', 414 => 'KW', + 417 => 'KG', 418 => 'LA', 422 => 'LB', 426 => 'LS', 428 => 'LV', + 430 => 'LR', 434 => 'LY', 438 => 'LI', 440 => 'LT', 442 => 'LU', + 446 => 'MO', 450 => 'MG', 454 => 'MW', 458 => 'MY', 462 => 'MV', + 466 => 'ML', 470 => 'MT', 478 => 'MR', 480 => 'MU', 484 => 'MX', + 492 => 'MC', 496 => 'MN', 498 => 'MD', 499 => 'ME', 504 => 'MA', + 508 => 'MZ', 512 => 'OM', 516 => 'NA', 520 => 'NR', 524 => 'NP', + 528 => 'NL', 540 => 'NC', 548 => 'VU', 554 => 'NZ', 558 => 'NI', + 562 => 'NE', 566 => 'NG', 570 => 'NU', 574 => 'NF', 578 => 'NO', + 580 => 'MP', 583 => 'FM', 584 => 'MH', 585 => 'PW', 586 => 'PK', + 591 => 'PA', 598 => 'PG', 600 => 'PY', 604 => 'PE', 608 => 'PH', + 612 => 'PN', 616 => 'PL', 620 => 'PT', 624 => 'GW', 626 => 'TL', + 630 => 'PR', 634 => 'QA', 634 => 'QA', 638 => 'RE', 642 => 'RO', + 643 => 'RU', 646 => 'RW', 652 => 'BL', 654 => 'SH', 659 => 'KN', + 660 => 'AI', 662 => 'LC', 663 => 'MF', 666 => 'PM', 670 => 'VC', + 674 => 'SM', 678 => 'ST', 682 => 'SA', 686 => 'SN', 688 => 'RS', + 690 => 'SC', 694 => 'SL', 702 => 'SG', 703 => 'SK', 704 => 'VN', + 705 => 'SI', 706 => 'SO', 710 => 'ZA', 716 => 'ZW', 724 => 'ES', + 728 => 'SS', 729 => 'SD', 732 => 'EH', 740 => 'SR', 748 => 'SZ', + 752 => 'SE', 756 => 'CH', 760 => 'SY', 762 => 'TJ', 764 => 'TH', + 768 => 'TG', 772 => 'TK', 776 => 'TO', 780 => 'TT', 784 => 'AE', + 788 => 'TN', 792 => 'TR', 795 => 'TM', 796 => 'TC', 798 => 'TV', + 800 => 'UG', 804 => 'UA', 807 => 'MK', 818 => 'EG', 826 => 'GB', + 831 => 'GG', 832 => 'JE', 833 => 'IM', 834 => 'TZ', 840 => 'US', + 854 => 'BF', 858 => 'UY', 860 => 'UZ', 862 => 'VE', 887 => 'YE', + 894 => 'ZM', +]; + +$width = 1000; +$height = 400; +$t = $data['transform']; +$scale = $t['scale']; +$translate = $t['translate']; +$arcs = $data['arcs']; + +function decodeArc($idx, &$arcs, $scale, $translate) { + $reverse = false; + if ($idx < 0) { + $reverse = true; + $idx = -$idx - 1; + } + $arc = $arcs[$idx]; + $points = []; + $x = 0; $y = 0; + foreach ($arc as $delta) { + $x += $delta[0]; + $y += $delta[1]; + $lon = $x * $scale[0] + $translate[0]; + $lat = $y * $scale[1] + $translate[1]; + $points[] = [$lon, $lat]; + } + if ($reverse) $points = array_reverse($points); + return $points; +} + +function ringToSVG($points, $w, $h) { + if (count($points) < 3) return ''; + $parts = []; + foreach ($points as $i => $p) { + $px = ($p[0] + 180) / 360 * $w; + $py = (90 - $p[1]) / 180 * $h; + $parts[] = ($i === 0 ? 'M' : 'L') . round($px, 2) . ',' . round($py, 2); + } + $parts[] = 'Z'; + return implode(' ', $parts); +} + +$svgPaths = []; + +foreach ($data['objects']['countries']['geometries'] as $geom) { + $numId = (int)$geom['id']; + $isoAlpha2 = $isoNumericToAlpha2[$numId] ?? ''; + $name = $geom['properties']['name'] ?? 'Unknown'; + if ($isoAlpha2 === 'AQ') continue; + + $polygons = []; + if ($geom['type'] === 'Polygon') { + $polygons[] = $geom['arcs']; + } elseif ($geom['type'] === 'MultiPolygon') { + $polygons = $geom['arcs']; + } + + foreach ($polygons as $rings) { + $allPoints = []; + foreach ($rings as $ring) { + foreach ($ring as $arcIdx) { + $pts = decodeArc($arcIdx, $arcs, $scale, $translate); + $allPoints = array_merge($allPoints, $pts); + } + break; // outer ring only, skip holes + } + $d = ringToSVG($allPoints, $width, $height); + if ($d) { + $svgPaths[] = ['iso' => $isoAlpha2, 'name' => $name, 'd' => $d]; + } + } +} + +$svg = ' + +'; + +foreach ($svgPaths as $c) { + $svg .= "\n" . ' '; +} + +$svg .= ' + +'; + +file_put_contents($outputFile, $svg); +echo "Wereldkaart opgeslagen: {$outputFile}\n"; +echo count($svgPaths) . " landpaden gegenereerd.\n"; diff --git a/cli/geoip-update.php b/cli/geoip-update.php new file mode 100644 index 0000000..7a9820d --- /dev/null +++ b/cli/geoip-update.php @@ -0,0 +1,90 @@ + $fp, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 120, + CURLOPT_USERAGENT => 'SyslogDash/1.0', +]); +curl_exec($ch); +$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); +fclose($fp); + +if ($httpCode !== 200) { + unlink($gzFile); + echo "Fout: HTTP {$httpCode} bij downloaden GeoIP database\n"; + echo "Tip: bezoek https://www.db-ip.com/db/lite.php voor handmatige download\n"; + exit(1); +} + +echo "Uitpakken...\n"; +$gz = gzopen($gzFile, 'r'); +$csv = fopen($csvFile, 'w'); +while ($line = gzgets($gz)) { + fputs($csv, $line); +} +gzclose($gz); +fclose($csv); +unlink($gzFile); + +// Convert to binary format for faster lookups +$ipv4File = $outputDir . '/ipv4.bin'; +$ipv6File = $outputDir . '/ipv6.bin'; + +$fh4 = fopen($ipv4File, 'w'); +$fh6 = fopen($ipv6File, 'w'); +$count4 = 0; +$count6 = 0; + +$csv = fopen($csvFile, 'r'); +while (($row = fgetcsv($csv)) !== false) { + if (count($row) < 3) continue; + $start = trim($row[0]); + $end = trim($row[1]); + $code = trim($row[2]); + if (strlen($code) !== 2) continue; + + if (filter_var($start, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $startLong = ip2long($start); + $endLong = ip2long($end); + if ($startLong === false || $endLong === false) continue; + fwrite($fh4, pack('NNa2', $startLong, $endLong, $code)); + $count4++; + } elseif (filter_var($start, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $startBin = inet_pton($start); + $endBin = inet_pton($end); + if ($startBin === false || $endBin === false) continue; + fwrite($fh6, $startBin . $endBin . $code); + $count6++; + } +} +fclose($csv); +fclose($fh4); +fclose($fh6); + +unlink($csvFile); + +echo "GeoIP database bijgewerkt:\n"; +echo " IPv4: {$count4} ranges\n"; +echo " IPv6: {$count6} ranges\n"; +echo " Bestanden: {$ipv4File}, {$ipv6File}\n"; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5165bdd --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "name": "noorlander/syslogdash", + "description": "Syslog Dashboard met GeoIP wereldkaart en BI mogelijkheden", + "type": "project", + "require": { + "php": ">=8.1", + "ext-pdo": "*", + "ext-mbstring": "*", + "ext-json": "*" + }, + "autoload": { + "psr-4": { + "SyslogDash\\": "src/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..784c584 --- /dev/null +++ b/composer.lock @@ -0,0 +1,23 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0a0f92517e549816293da40320cffa7e", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1", + "ext-pdo": "*", + "ext-mbstring": "*", + "ext-json": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/composer.phar b/composer.phar new file mode 100755 index 0000000..43f4b99 Binary files /dev/null and b/composer.phar differ diff --git a/config.sample.php b/config.sample.php new file mode 100644 index 0000000..6a8c648 --- /dev/null +++ b/config.sample.php @@ -0,0 +1,24 @@ + [ + 'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=Syslog;charset=utf8mb4', + 'username' => 'syslog', + 'password' => '', + ], + 'geoip' => [ + 'provider' => 'maxmind', + 'maxmind_db' => __DIR__ . '/storage/geoip/GeoLite2-Country.mmdb', + 'dbip_dir' => __DIR__ . '/storage/geoip', + ], + 'syslog' => [ + 'table' => 'SystemEvents', + 'host_to_ip' => [ + // 'hostname' => '1.2.3.4', + ], + 'resolve_hostnames' => true, + ], + 'dashboard' => [ + 'title' => 'Syslog Dash', + 'theme_color' => '#0a369d', + ], +]; diff --git a/index.php b/index.php new file mode 100644 index 0000000..cab2ffd --- /dev/null +++ b/index.php @@ -0,0 +1,18 @@ +getDailyStats($period); + $chartLabels = array_map(fn($d) => date('d-m', strtotime($d['date'])), $chartData); + $chartValues = array_column($chartData, 'cnt'); + $chartLabel = 'Berichten per dag'; + $chartType = 'bar'; + $extraRows = $chartData; + break; + + case 'hosts': + $chartData = $sq->getHostStats($period); + $chartLabels = array_column($chartData, 'FromHost'); + $chartValues = array_column($chartData, 'cnt'); + $chartLabel = 'Top hosts'; + $chartType = 'bar'; + break; + + case 'countries': + $chartData = $sq->getCountryStats($period); + $chartLabels = array_map(fn($c) => GeoIP::getCountryName($c['code']) . ' (' . $c['code'] . ')', $chartData); + $chartValues = array_column($chartData, 'count'); + $chartLabel = 'Berichten per land'; + $chartType = 'bar'; + break; + + case 'severity': + $chartData = $sq->getSeverityStats($period); + $chartLabels = array_map(fn($s) => SyslogQuery::severityName((int)$s['Priority']), $chartData); + $chartValues = array_column($chartData, 'cnt'); + $chartLabel = 'Severity verdeling'; + $chartType = 'doughnut'; + break; + + case 'tags': + $chartData = $sq->getTagStats($period); + $chartLabels = array_column($chartData, 'SysLogTag'); + $chartValues = array_column($chartData, 'cnt'); + $chartLabel = 'Top tags'; + $chartType = 'bar'; + break; + + case 'hourly': + $db = \SyslogDash\Database::getInstance(); + $table = Database::table(); + $stmt = $db->prepare(" + SELECT HOUR(`ReceivedAt`) AS h, COUNT(*) AS cnt + FROM `{$table}` + WHERE `ReceivedAt` >= :since + GROUP BY HOUR(`ReceivedAt`) + ORDER BY h ASC + "); + $stmt->execute([':since' => date('Y-m-d H:i:s', strtotime("-{$period} days"))]); + $chartData = $stmt->fetchAll(); + $hourlyData = array_fill(0, 24, 0); + foreach ($chartData as $r) $hourlyData[(int)$r['h']] = (int)$r['cnt']; + $chartLabels = array_map(fn($h) => sprintf('%02d:00', $h), range(0, 23)); + $chartValues = $hourlyData; + $chartLabel = 'Berichten per uur'; + $chartType = 'line'; + break; + + case 'custom': + $sql = $_POST['sql'] ?? ''; + $customResult = null; + if ($sql && preg_match('/^\s*SELECT/i', $sql)) { + $customResult = $sq->runQuery($sql); + } + break; +} +?> +
+

BI Rapporten

+
+ 7d + 30d + 90d + 365d + Alles +
+
+ +
+ +
+ +
+
Custom SQL Query
+
+
+
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + +
+
+ rijen + + +
+
+ +
+
+ +
+ CSV +
+
+
+ +
+
+ + +
+
Dagelijkse gegevens
+
+
+ + + + + + + + + + + + + +
DatumBerichtenHosts
+
+
+
+ + +
+
+ + + + + diff --git a/pages/dashboard.php b/pages/dashboard.php new file mode 100644 index 0000000..e50bdc8 --- /dev/null +++ b/pages/dashboard.php @@ -0,0 +1,163 @@ +getKPIStats(30); +$daily = $sq->getDailyStats(30); +$hostStats = $sq->getHostStats(30); +$sevStats = $sq->getSeverityStats(30); +$tagStats = $sq->getTagStats(30); +$topCountry = $kpi['topCountry']; +?> +
+

Dashboard

+ Laatste 30 dagen +
+ +
+
+
+
+
+
+
Log berichten
+ /dag +
+
+
+
+
+
+
+
+
Unieke hosts
+ apparaten +
+
+
+
+
+
+
+
—
+
Grootste land
+ +
+
+
+
+
+
+
+
+
Totaal (all-time)
+ sinds begin +
+
+
+
+ +
+
+
+
+ Berichten per dag +
+
+ +
+
+
+
+
+
Severity
+
+ +
+
+
+
+ +
+
+
+
Top hosts
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
Top tags
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+ + + diff --git a/pages/export.php b/pages/export.php new file mode 100644 index 0000000..bbfc711 --- /dev/null +++ b/pages/export.php @@ -0,0 +1,74 @@ +getLogs($filters, 1, 100000); + $data = $result['rows']; + $fields = ['ID','ReceivedAt','FromHost','Facility','Priority','SysLogTag','Message']; + break; + + case 'daily': + $data = $sq->getDailyStats($period); + $fields = ['Datum','Berichten','Hosts']; + break; + + case 'hosts': + $data = $sq->getHostStats($period); + $fields = ['Host','Aantal']; + break; + + case 'countries': + $data = $sq->getCountryStats($period); + $fields = ['Code','Aantal']; + break; + + case 'severity': + $data = $sq->getSeverityStats($period); + $fields = ['Priority','Severity','Aantal']; + array_walk($data, fn(&$r) => $r['SeverityName'] = SyslogQuery::severityName((int)$r['Priority'])); + $fields = ['Priority','SeverityName','Aantal']; + break; + + default: + $data = []; + $fields = []; +} + +if ($format === 'json') { + header('Content-Type: application/json; charset=utf-8'); + header("Content-Disposition: attachment; filename=\"{$filename}\""); + echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + exit; +} + +header('Content-Type: text/csv; charset=utf-8'); +header("Content-Disposition: attachment; filename=\"{$filename}\""); +echo "\xEF\xBB\xBF"; // BOM for Excel UTF-8 + +$out = fopen('php://output', 'w'); +fputcsv($out, $fields, ';'); +foreach ($data as $row) { + $line = []; + foreach ($fields as $f) { + $line[] = $row[$f] ?? ''; + } + fputcsv($out, $line, ';'); +} +fclose($out); diff --git a/pages/logs.php b/pages/logs.php new file mode 100644 index 0000000..ccc9ffe --- /dev/null +++ b/pages/logs.php @@ -0,0 +1,148 @@ + $_GET['host'] ?? '', + 'country' => $_GET['country'] ?? '', + 'tag' => $_GET['tag'] ?? '', + 'facility' => $_GET['facility'] ?? '', + 'priority' => $_GET['priority'] ?? '', + 'search' => $_GET['search'] ?? '', + 'from' => $_GET['from'] ?? '', + 'to' => $_GET['to'] ?? '', + 'sort' => $_GET['sort'] ?? 'DESC', + 'sort_col' => $_GET['sort_col'] ?? 'ReceivedAt', +]; +$filters = array_filter($filters, fn($v) => $v !== ''); +$page = max(1, (int)($_GET['p'] ?? 1)); + +$result = $sq->getLogs($filters, $page, 50); +$hosts = $sq->getDistinct('FromHost'); +$facilities = $sq->getDistinct('Facility'); +$priorities = $sq->getDistinct('Priority'); + +function sortUrl($col, $filters, $currentSort, $currentCol) { + $dir = 'ASC'; + if ($currentCol === $col && $currentSort === 'ASC') $dir = 'DESC'; + $params = array_merge($filters, ['sort_col' => $col, 'sort' => $dir, 'p' => 1]); + return '?page=logs&' . http_build_query($params); +} +function filterUrl($filters, $override) { + $params = array_merge($filters, $override, ['p' => 1]); + return '?page=logs&' . http_build_query($params); +} +?> +
+

Logs

+
+ CSV + JSON +
+
+ +
+
Filters
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + Wissen + resultaten (pagina /) +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Ontvangen Host FacilitySeverityTagBericht
+ +
+
+
+
+ + 1): ?> + + + diff --git a/pages/worldmap.php b/pages/worldmap.php new file mode 100644 index 0000000..72e292c --- /dev/null +++ b/pages/worldmap.php @@ -0,0 +1,222 @@ +getCountryStats($period); +$daily = $sq->getDailyStats($period); +$totalLogs = array_sum(array_column($countryStats, 'count')); + +$mapCountries = []; +foreach ($countryStats as $c) { + $mapCountries[$c['code']] = $c['count']; +} + +$drillCountry = $_GET['country'] ?? null; +$drillHosts = []; +if ($drillCountry) { + foreach ($countryStats as $c) { + if (strtoupper($c['code']) === strtoupper($drillCountry)) { + $drillHosts = $c['hosts']; + break; + } + } +} + +$svgPath = __DIR__ . '/assets/img/world-map.svg'; +$svgContent = ''; +if (file_exists($svgPath)) { + $svgContent = file_get_contents($svgPath); + $styles = ''; + $maxCountry = count($countryStats) > 0 ? $countryStats[0]['count'] : 1; + foreach ($countryStats as $c) { + if ($c['code'] === 'UNKNOWN') continue; + $ratio = $c['count'] / $maxCountry; + if ($ratio > 0.66) $fill = '#052c65'; + elseif ($ratio > 0.33) $fill = '#0d6efd'; + elseif ($ratio > 0.1) $fill = '#6ea8fe'; + else $fill = '#cfe2ff'; + $styles .= "#{$c['code']} { fill: {$fill}; }"; + } + $svgContent = str_replace('', $styles . '', $svgContent); +} +?> +
+

Wereldkaart

+ +
+ + +
+ Gefilterd op land: + Filter wissen +
+ + +
+
+
+
+
+
+
Landen
+
+
+
+
+
+
+
+
+
Berichten
+
+
+
+
+
+
+
+
+
Onbekend (geen GeoIP)
+
+
+
+
+
+
+
+
getUniqueHosts($period) ?>
+
Unieke hosts
+
+
+
+
+ +
+
+ Wereldkaart + + weinig + gemiddeld + veel + meest + +
+
+ +
+ +
+ + +
+ Geen wereldkaart SVG gevonden. Genereer deze met: + php cli/generate-map.php +
+ +
+
+ + +
+
Hosts in
+
+
+ + + + + + +
+
+
+ + +
+
Landen ()
+
+
+ + + + + + + + + + + + + $c): ?> + + + + + + + + + +
#LandBerichten%
+ + + + + 0 ? round($c['count'] / $totalLogs * 100, 1) : 0 ?>% +
+
+
+
+
+
+
+ + + diff --git a/src/Database.php b/src/Database.php new file mode 100644 index 0000000..a3626a3 --- /dev/null +++ b/src/Database.php @@ -0,0 +1,56 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + ] + ); + } catch (PDOException $e) { + throw new \RuntimeException('Database verbinding mislukt: ' . $e->getMessage()); + } + } + return self::$instance; + } + + private static function getConfig(): array + { + $cfg = require __DIR__ . '/../config.php'; + return $cfg['db']; + } + + public static function table(): string + { + $cfg = require __DIR__ . '/../config.php'; + return $cfg['syslog']['table'] ?? 'SystemEvents'; + } + + public static function getHostToIp(): array + { + $cfg = require __DIR__ . '/../config.php'; + return $cfg['syslog']['host_to_ip'] ?? []; + } + + public static function resolveHostnames(): bool + { + $cfg = require __DIR__ . '/../config.php'; + return $cfg['syslog']['resolve_hostnames'] ?? true; + } +} diff --git a/src/GeoIP.php b/src/GeoIP.php new file mode 100644 index 0000000..3c624e1 --- /dev/null +++ b/src/GeoIP.php @@ -0,0 +1,160 @@ += $range['start'] && $long <= $range['end']) { + self::$ipv4Cache[$ip] = $range['code']; + return $range['code']; + } + } + + self::$ipv4Cache[$ip] = null; + return null; + } + + private static function lookupIPv6(string $ip): ?string + { + if (isset(self::$ipv6Cache[$ip])) { + return self::$ipv6Cache[$ip]; + } + + if (self::$ipv6Ranges === null) { + self::$ipv6Ranges = self::loadRanges('ipv6.bin', 34, 'a16a16a2'); + } + + $bin = inet_pton($ip); + if ($bin === false) return null; + + foreach (self::$ipv6Ranges as $range) { + if ($bin >= $range['start'] && $bin <= $range['end']) { + self::$ipv6Cache[$ip] = $range['code']; + return $range['code']; + } + } + + self::$ipv6Cache[$ip] = null; + return null; + } + + private static function loadRanges(string $file, int $recordSize, string $format): array + { + $cfg = require __DIR__ . '/../config.php'; + $path = $cfg['geoip']['dbip_dir'] ?? __DIR__ . '/../storage/geoip'; + $filePath = rtrim($path, '/') . '/' . $file; + + if (!file_exists($filePath)) return []; + + $fh = fopen($filePath, 'rb'); + if (!$fh) return []; + + $ranges = []; + while (!feof($fh)) { + $record = fread($fh, $recordSize); + if ($record === false || strlen($record) < $recordSize) break; + + if ($format === 'NNa2') { + $data = unpack('Nstart/Nend/a2code', $record); + $ranges[] = [ + 'start' => $data['start'], + 'end' => $data['end'], + 'code' => $data['code'], + ]; + } elseif ($format === 'a16a16a2') { + $data = unpack('a16start/a16end/a2code', $record); + $ranges[] = [ + 'start' => $data['start'], + 'end' => $data['end'], + 'code' => $data['code'], + ]; + } + } + fclose($fh); + return $ranges; + } + + public static function getCountryFlagEmoji(?string $code): string + { + if ($code === null || $code === '' || $code === 'UNKNOWN') { + return 'šŸŒ'; + } + $code = strtoupper($code); + $regional = [0x1F1E6, 0x1F1E7, 0x1F1E8, 0x1F1E9, 0x1F1EA, 0x1F1EB, 0x1F1EC, 0x1F1ED, 0x1F1EE, 0x1F1EF, 0x1F1F0, 0x1F1F1, 0x1F1F2, 0x1F1F3, 0x1F1F4, 0x1F1F5, 0x1F1F6, 0x1F1F7, 0x1F1F8, 0x1F1F9, 0x1F1FA, 0x1F1FB, 0x1F1FC, 0x1F1FD, 0x1F1FE, 0x1F1FF]; + $a = ord($code[0]) - 65; + $b = ord($code[1]) - 65; + if ($a < 0 || $a > 25 || $b < 0 || $b > 25) { + return 'šŸŒ'; + } + return mb_chr($regional[$a]) . mb_chr($regional[$b]); + } + + public static function getCountryName($code, string $lang = 'nl'): string + { + $names = self::getCountryNames($lang); + if ($code === null || $code === '') return 'Onbekend'; + $code = strtoupper($code); + return $names[$code] ?? $code; + } + + public static function getCountryNames(string $lang = 'nl'): array + { + return [ + 'NL' => 'Nederland', 'DE' => 'Duitsland', 'BE' => 'BelgiĆ«', + 'FR' => 'Frankrijk', 'GB' => 'Verenigd Koninkrijk', 'US' => 'Verenigde Staten', + 'CN' => 'China', 'RU' => 'Rusland', 'BR' => 'BraziliĆ«', + 'IN' => 'India', 'JP' => 'Japan', 'KR' => 'Zuid-Korea', + 'IT' => 'ItaliĆ«', 'ES' => 'Spanje', 'PT' => 'Portugal', + 'PL' => 'Polen', 'SE' => 'Zweden', 'NO' => 'Noorwegen', + 'DK' => 'Denemarken', 'FI' => 'Finland', 'AT' => 'Oostenrijk', + 'CH' => 'Zwitserland', 'IE' => 'Ierland', 'LU' => 'Luxemburg', + 'AU' => 'AustraliĆ«', 'NZ' => 'Nieuw-Zeeland', 'CA' => 'Canada', + 'SG' => 'Singapore', 'HK' => 'Hongkong', 'TW' => 'Taiwan', + 'ZA' => 'Zuid-Afrika', 'AE' => 'Verenigde Arabische Emiraten', + 'IL' => 'IsraĆ«l', 'TR' => 'Turkije', 'GR' => 'Griekenland', + 'CZ' => 'TsjechiĆ«', 'SK' => 'Slowakije', 'HU' => 'Hongarije', + 'RO' => 'RoemeniĆ«', 'BG' => 'Bulgarije', 'HR' => 'KroatiĆ«', + 'RS' => 'ServiĆ«', 'UA' => 'OekraĆÆne', 'BY' => 'Wit-Rusland', + ]; + } + + public static function hasDatabase(): bool + { + $cfg = require __DIR__ . '/../config.php'; + $dir = $cfg['geoip']['dbip_dir'] ?? __DIR__ . '/../storage/geoip'; + return file_exists(rtrim($dir, '/') . '/ipv4.bin'); + } +} diff --git a/src/Layout.php b/src/Layout.php new file mode 100644 index 0000000..eefe3d6 --- /dev/null +++ b/src/Layout.php @@ -0,0 +1,74 @@ + ['icon' => 'bi-speedometer2', 'label' => 'Dashboard'], + 'worldmap' => ['icon' => 'bi-globe2', 'label' => 'Wereldkaart'], + 'logs' => ['icon' => 'bi-list-ul', 'label' => 'Logs'], + 'bi' => ['icon' => 'bi-bar-chart-line', 'label' => 'BI Rapporten'], + ]; + + $pageFile = __DIR__ . '/../pages/' . basename($page) . '.php'; + $pageContent = ''; + if (file_exists($pageFile)) { + extract($data); + ob_start(); + require $pageFile; + $pageContent = ob_get_clean(); + } + + $currentPage = basename($page); + ?> + + + + + <?= $title ?> — <?= $navItems[$currentPage]['label'] ?? $currentPage ?> + + + + + + + + + +
+ +
+ + + + + +db = Database::getInstance(); + $this->table = Database::table(); + $this->hostToIp = Database::getHostToIp(); + $this->resolveHostnames = Database::resolveHostnames(); + } + + public function getHostIps(): array + { + $ipMap = $this->hostToIp; + $hosts = $this->getDistinctHosts(); + foreach ($hosts as $host) { + if (!isset($ipMap[$host])) { + if ($this->resolveHostnames && preg_match('/^\d+\.\d+\.\d+\.\d+$/', $host)) { + $ipMap[$host] = $host; + } elseif ($this->resolveHostnames) { + $resolved = gethostbyname($host); + if ($resolved !== $host && filter_var($resolved, FILTER_VALIDATE_IP)) { + $ipMap[$host] = $resolved; + } + } + } + } + return $ipMap; + } + + private function getDistinctHosts(): array + { + $stmt = $this->db->query("SELECT DISTINCT `FromHost` FROM `{$this->table}` ORDER BY `FromHost`"); + return $stmt->fetchAll(PDO::FETCH_COLUMN); + } + + public function getCountryStats(int $days = 30): array + { + $geo = new GeoIP(); + $hostIps = $this->getHostIps(); + $countries = []; + + $where = ''; + $params = []; + if ($days > 0) { + $where = "WHERE `ReceivedAt` >= :since"; + $params[':since'] = date('Y-m-d H:i:s', strtotime("-{$days} days")); + } + + $stmt = $this->db->prepare(" + SELECT `FromHost`, COUNT(*) AS cnt + FROM `{$this->table}` + {$where} + GROUP BY `FromHost` + ORDER BY cnt DESC + "); + $stmt->execute($params); + + while ($row = $stmt->fetch()) { + $host = $row['FromHost']; + $ip = $hostIps[$host] ?? null; + $country = $ip ? $geo->lookupCountry($ip) : null; + $code = $country ?? 'UNKNOWN'; + if (!isset($countries[$code])) { + $countries[$code] = ['code' => $code, 'count' => 0, 'hosts' => []]; + } + $countries[$code]['count'] += (int)$row['cnt']; + $countries[$code]['hosts'][] = $host; + } + + usort($countries, fn($a, $b) => $b['count'] <=> $a['count']); + return $countries; + } + + public function getLogs(array $filters = [], int $page = 1, int $perPage = 50): array + { + $conditions = []; + $params = []; + + if (!empty($filters['country'])) { + $hostIps = $this->getHostIps(); + $countryHosts = []; + $geo = new GeoIP(); + foreach ($hostIps as $host => $ip) { + $c = $geo->lookupCountry($ip); + if (strtoupper($c) === strtoupper($filters['country'])) { + $countryHosts[] = $host; + } + } + if (!empty($countryHosts)) { + $placeholders = implode(',', array_fill(0, count($countryHosts), '?')); + $conditions[] = "`FromHost` IN ({$placeholders})"; + $params = array_merge($params, $countryHosts); + } + } + + if (!empty($filters['host'])) { + $conditions[] = "`FromHost` = :host"; + $params[':host'] = $filters['host']; + } + + if (!empty($filters['tag'])) { + $conditions[] = "`SysLogTag` LIKE :tag"; + $params[':tag'] = '%' . $filters['tag'] . '%'; + } + + if (!empty($filters['facility'])) { + $conditions[] = "`Facility` = :facility"; + $params[':facility'] = (int)$filters['facility']; + } + + if (!empty($filters['priority'])) { + $conditions[] = "`Priority` = :priority"; + $params[':priority'] = (int)$filters['priority']; + } + + if (!empty($filters['search'])) { + $conditions[] = "`Message` LIKE :search"; + $params[':search'] = '%' . $filters['search'] . '%'; + } + + if (!empty($filters['from'])) { + $conditions[] = "`ReceivedAt` >= :from"; + $params[':from'] = $filters['from']; + } + + if (!empty($filters['to'])) { + $conditions[] = "`ReceivedAt` <= :to"; + $params[':to'] = $filters['to']; + } + + $where = !empty($conditions) ? 'WHERE ' . implode(' AND ', $conditions) : ''; + + $countStmt = $this->db->prepare("SELECT COUNT(*) FROM `{$this->table}` {$where}"); + $countStmt->execute($params); + $total = (int)$countStmt->fetchColumn(); + + $offset = ($page - 1) * $perPage; + $order = ($filters['sort'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC'; + $orderCol = 'ReceivedAt'; + if (!empty($filters['sort_col'])) { + $allowed = ['ReceivedAt', 'DeviceReportedTime', 'FromHost', 'Facility', 'Priority', 'SysLogTag']; + if (in_array($filters['sort_col'], $allowed)) { + $orderCol = "`{$filters['sort_col']}`"; + } + } + + $stmt = $this->db->prepare(" + SELECT `ID`, `ReceivedAt`, `DeviceReportedTime`, `FromHost`, + `Facility`, `Priority`, `Message`, `SysLogTag` + FROM `{$this->table}` + {$where} + ORDER BY {$orderCol} {$order} + LIMIT {$perPage} OFFSET {$offset} + "); + $stmt->execute($params); + $rows = $stmt->fetchAll(); + + return [ + 'rows' => $rows, + 'total' => $total, + 'page' => $page, + 'perPage' => $perPage, + 'pages' => max(1, ceil($total / $perPage)), + ]; + } + + public function getDailyStats(int $days = 30): array + { + $stmt = $this->db->prepare(" + SELECT DATE(`ReceivedAt`) AS d, + COUNT(*) AS cnt, + COUNT(DISTINCT `FromHost`) AS hosts + FROM `{$this->table}` + WHERE `ReceivedAt` >= :since + GROUP BY DATE(`ReceivedAt`) + ORDER BY d ASC + "); + $stmt->execute([':since' => date('Y-m-d H:i:s', strtotime("-{$days} days"))]); + $rows = $stmt->fetchAll(); + + $result = []; + $start = new \DateTime("-{$days} days"); + $end = new \DateTime(); + $period = new \DatePeriod($start, new \DateInterval('P1D'), $end); + $data = []; + foreach ($rows as $r) { + $data[$r['d']] = ['cnt' => (int)$r['cnt'], 'hosts' => (int)$r['hosts']]; + } + foreach ($period as $dt) { + $key = $dt->format('Y-m-d'); + $result[] = [ + 'date' => $key, + 'cnt' => $data[$key]['cnt'] ?? 0, + 'hosts' => $data[$key]['hosts'] ?? 0, + ]; + } + return $result; + } + + public function getHostStats(int $days = 30): array + { + $stmt = $this->db->prepare(" + SELECT `FromHost`, COUNT(*) AS cnt + FROM `{$this->table}` + WHERE `ReceivedAt` >= :since + GROUP BY `FromHost` + ORDER BY cnt DESC + LIMIT 20 + "); + $stmt->execute([':since' => date('Y-m-d H:i:s', strtotime("-{$days} days"))]); + return $stmt->fetchAll(); + } + + public function getTagStats(int $days = 30): array + { + $stmt = $this->db->prepare(" + SELECT `SysLogTag`, COUNT(*) AS cnt + FROM `{$this->table}` + WHERE `ReceivedAt` >= :since AND `SysLogTag` IS NOT NULL + GROUP BY `SysLogTag` + ORDER BY cnt DESC + LIMIT 15 + "); + $stmt->execute([':since' => date('Y-m-d H:i:s', strtotime("-{$days} days"))]); + return $stmt->fetchAll(); + } + + public function getSeverityStats(int $days = 30): array + { + $stmt = $this->db->prepare(" + SELECT `Priority`, COUNT(*) AS cnt + FROM `{$this->table}` + WHERE `ReceivedAt` >= :since + GROUP BY `Priority` + ORDER BY `Priority` ASC + "); + $stmt->execute([':since' => date('Y-m-d H:i:s', strtotime("-{$days} days"))]); + return $stmt->fetchAll(); + } + + public function getTotalCount(int $days = 0): int + { + $where = ''; + $params = []; + if ($days > 0) { + $where = "WHERE `ReceivedAt` >= :since"; + $params[':since'] = date('Y-m-d H:i:s', strtotime("-{$days} days")); + } + $stmt = $this->db->prepare("SELECT COUNT(*) FROM `{$this->table}` {$where}"); + $stmt->execute($params); + return (int)$stmt->fetchColumn(); + } + + public function getUniqueHosts(int $days = 0): int + { + $where = ''; + $params = []; + if ($days > 0) { + $where = "WHERE `ReceivedAt` >= :since"; + $params[':since'] = date('Y-m-d H:i:s', strtotime("-{$days} days")); + } + $stmt = $this->db->prepare("SELECT COUNT(DISTINCT `FromHost`) FROM `{$this->table}` {$where}"); + $stmt->execute($params); + return (int)$stmt->fetchColumn(); + } + + public function getKPIStats(int $days = 30): array + { + $total = $this->getTotalCount($days); + $uniqueHosts = $this->getUniqueHosts($days); + $countryStats = $this->getCountryStats($days); + $topCountry = $countryStats[0] ?? null; + $totalAll = $this->getTotalCount(0); + $avgPerDay = $days > 0 ? round($total / $days, 0) : 0; + + return [ + 'total' => $total, + 'uniqueHosts' => $uniqueHosts, + 'topCountry' => $topCountry, + 'totalAll' => $totalAll, + 'avgPerDay' => $avgPerDay, + ]; + } + + public function runQuery(string $sql, array $params = []): array + { + try { + $stmt = $this->db->prepare($sql); + $stmt->execute($params); + if (preg_match('/^\s*SELECT/i', $sql)) { + return ['rows' => $stmt->fetchAll(), 'error' => null]; + } + return ['rows' => ['affected' => $stmt->rowCount()], 'error' => null]; + } catch (\PDOException $e) { + return ['rows' => [], 'error' => $e->getMessage()]; + } + } + + public function getDistinct(string $column): array + { + $allowed = ['FromHost', 'Facility', 'Priority', 'SysLogTag', 'EventLogType']; + if (!in_array($column, $allowed)) return []; + $stmt = $this->db->query("SELECT DISTINCT `{$column}` FROM `{$this->table}` ORDER BY `{$column}`"); + return $stmt->fetchAll(PDO::FETCH_COLUMN); + } + + public static function facilityName(int $code): string + { + $names = [ + 0 => 'kern', 1 => 'user', 2 => 'mail', 3 => 'daemon', + 4 => 'auth', 5 => 'syslog', 6 => 'lpr', 7 => 'news', + 8 => 'uucp', 9 => 'cron', 10 => 'authpriv', 11 => 'ftp', + 16 => 'local0', 17 => 'local1', 18 => 'local2', 19 => 'local3', + 20 => 'local4', 21 => 'local5', 22 => 'local6', 23 => 'local7', + ]; + return $names[$code] ?? "facility_{$code}"; + } + + public static function severityName(int $code): string + { + $names = [ + 0 => 'emerg', 1 => 'alert', 2 => 'crit', 3 => 'err', + 4 => 'warning', 5 => 'notice', 6 => 'info', 7 => 'debug', + ]; + return $names[$code] ?? "severity_{$code}"; + } + + public static function severityClass(int $code): string + { + return match ($code) { + 0, 1, 2 => 'danger', + 3, 4 => 'warning', + 5 => 'info', + 6, 7 => 'secondary', + default => 'secondary', + }; + } +} diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..b589aed --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..2052022 --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,396 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..f13cd0d --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..cfb0e37 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'SyslogDash\\' => 11, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'SyslogDash\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit0a0f92517e549816293da40320cffa7e::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit0a0f92517e549816293da40320cffa7e::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit0a0f92517e549816293da40320cffa7e::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..87fda74 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,5 @@ +{ + "packages": [], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..50e2e86 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,23 @@ + array( + 'name' => 'noorlander/syslogdash', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'noorlander/syslogdash' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..2beb149 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,25 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + throw new \RuntimeException( + 'Composer detected issues in your platform: ' . implode(' ', $issues) + ); +}