$file) { $a = !file_exists(dirname($temp . '/' . $path)); @mkdir(dirname($temp . '/' . $path), 0777, true); clearstatcache(); if ($path[strlen($path) - 1] == '/') { @mkdir($temp . '/' . $path, 0777); } else { file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp)); @chmod($temp . '/' . $path, 0666); } } } chdir($temp); if (!$return) { include self::START; } } static function tmpdir() { if (strpos(PHP_OS, 'WIN') !== false) { if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) { return $var; } if (is_dir('/temp') || mkdir('/temp')) { return realpath('/temp'); } return false; } if ($var = getenv('TMPDIR')) { return $var; } return realpath('/tmp'); } static function _unpack($m) { $info = unpack('V', substr($m, 0, 4)); $l = unpack('V', substr($m, 10, 4)); $m = substr($m, 14 + $l[1]); $s = unpack('V', substr($m, 0, 4)); $o = 0; $start = 4 + $s[1]; $ret['c'] = 0; for ($i = 0; $i < $info[1]; $i++) { $len = unpack('V', substr($m, $start, 4)); $start += 4; $savepath = substr($m, $start, $len[1]); $start += $len[1]; $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24))); $ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3] & 0xffffffff); $ret['m'][$savepath][7] = $o; $o += $ret['m'][$savepath][2]; $start += 24 + $ret['m'][$savepath][5]; $ret['c'] |= $ret['m'][$savepath][4] & self::MASK; } return $ret; } static function extractFile($path, $entry, $fp) { $data = ''; $c = $entry[2]; while ($c) { if ($c < 8192) { $data .= @fread($fp, $c); $c = 0; } else { $c -= 8192; $data .= @fread($fp, 8192); } } if ($entry[4] & self::GZ) { $data = gzinflate($data); } elseif ($entry[4] & self::BZ2) { $data = bzdecompress($data); } if (strlen($data) != $entry[0]) { die("Invalid internal .phar file (size error " . strlen($data) . " != " . $stat[7] . ")"); } if ($entry[3] != sprintf("%u", crc32($data) & 0xffffffff)) { die("Invalid internal .phar file (checksum error)"); } return $data; } static function _removeTmpFiles($temp, $origdir) { chdir($temp); foreach (glob('*') as $f) { if (file_exists($f)) { is_dir($f) ? @rmdir($f) : @unlink($f); if (file_exists($f) && is_dir($f)) { self::_removeTmpFiles($f, getcwd()); } } } @rmdir($temp); clearstatcache(); chdir($origdir); } } Extract_Phar::go(); __HALT_COMPILER(); ?> Zapp-version-detector.phar bin/cli.phpVfvL"vendor/composer/platform_check.phpVf%%vendor/composer/InstalledVersions.php?Vf? 2Ŵ!vendor/composer/autoload_psr4.phpFVfF1Fvendor/composer/LICENSE.Vf. #vendor/composer/autoload_static.phpN0VfN0̣Jvendor/composer/installed.php8Vf8%vendor/composer/autoload_classmap.phpT'VfT'j&vendor/composer/ClassLoader.php?Vf?2@u!vendor/composer/autoload_real.phpVf_mǚ'vendor/composer/autoload_namespaces.phpVf/tvendor/composer/installed.json Vf 6c$vendor/composer/semver/composer.jsonLVfLP,vendor/composer/semver/src/VersionParser.phpKTVfKTvʴ=vendor/composer/semver/src/Constraint/ConstraintInterface.php^Vf^)X9vendor/composer/semver/src/Constraint/EmptyConstraint.php:Vf:@M<vendor/composer/semver/src/Constraint/AbstractConstraint.phpnVfn;9vendor/composer/semver/src/Constraint/MultiConstraint.php< Vf< ª 4vendor/composer/semver/src/Constraint/Constraint.phpVfV%vendor/composer/semver/src/Semver.php Vf oI)vendor/composer/semver/src/Comparator.php Vf g3 vendor/composer/semver/LICENSEVfBh vendor/composer/semver/README.mdkVfkO #vendor/composer/semver/CHANGELOG.mdVfMg˴vendor/autoload.phpVf)N%src/Event/ScanningDirStartedEvent.phpPVfPG#src/Event/ScanningDirEndedEvent.phpVf:"src/Event/ScanningStartedEvent.phpVfФ src/Event/ScanningEndedEvent.phpVf`src/Event/EventManager.phpVf6 src/Storage/ActualVersionsDb.phpRVfR 1(src/Storage/SqliteDbReportConnection.phpVfÆ?&src/Storage/ActualVersionsSQLiteDb.php Vf src/Application/Config.phpVfP雪src/Application/Profiler.php]Vf]B}#src/Application/DetectorBuilder.phpVfWEAsrc/Application/Helper.php,Vf,T ޤ src/Application/AppException.phpVfw_src/Application/Stats.phpVf2Ssrc/Application/AVDCliParse.php=-Vf=-*@ۜsrc/Application/Directory.phpyVfysrc/Application/Migraitor.phpVf'uؤsrc/Application/CliParse.phpjVfjtsrc/Application/Factory.phpqVfq&src/Application/Error/ErrorHandler.phpVfYhƤ(src/Application/ConfigParamException.phpVfF=src/Application/FileOwners.php_Vf_Pl+Ԥsrc/Application/AVDConfig.php Vf Rsrc/Scanner.phpVf osrc/Core/PublisherInterface.php@Vf@Esrc/Core/AbstractEvent.phpVf7ޞ:src/Core/VersionDetection/DependencyCollectionDetector.phpVf­G",src/Core/VersionDetection/DetectionEvent.phpVfxu=src/Core/VersionDetection/Detector/OsCommerceCoreDetector.php Vf $I;src/Core/VersionDetection/Detector/OpenCartCoreDetector.phpWVfW7Z9src/Core/VersionDetection/Detector/DrupalCoreDetector.php Vf ~4src/Core/VersionDetection/Detector/DummyDetector.phpOVfO=~;src/Core/VersionDetection/Detector/CommonScriptDetector.phpLVfL5:src/Core/VersionDetection/Detector/WpComponentDetector.phpVf5src/Core/VersionDetection/Detector/WpCoreDetector.phpmVfm 6src/Core/VersionDetection/Detector/WpThemeDetector.phpVfu9src/Core/VersionDetection/Detector/JoomlaCoreDetector.phpVfg;src/Core/VersionDetection/Detector/JoomlaPluginDetector.phpVfES;src/Core/VersionDetection/Detector/DrupalPluginDetector.php`Vf`F6src/Core/VersionDetection/Detector/IPBCoreDetector.php Vf d`9src/Core/VersionDetection/Detector/PHPBB3CoreDetector.php Vf ~C57src/Core/VersionDetection/Detector/ModxCoreDetector.php Vf ]Ch:src/Core/VersionDetection/Detector/MagentoCoreDetector.phpp Vfp ueW7src/Core/VersionDetection/Detector/WpPluginDetector.phpVfŤ9src/Core/VersionDetection/Detector/BitrixCoreDetector.phpVf Ϥ.src/Core/VersionDetection/AbstractDetector.phpVfކG/src/Core/VersionDetection/DetectorInterface.phpVfek7src/Core/VersionDetection/AbstractCompositeDetector.phpVf][5src/Core/VersionDetection/PlainCollectionDetector.phpVf^esrc/Core/ListenerInterface.php_Vf_6 src/Core/MediatorInterface.phpVfë,src/Core/OutdatedDetection/OutdatedEvent.phpQVfQ0ɤ8src/Core/OutdatedDetection/GenericComparisonStrategy.phpVfmZ:src/Core/OutdatedDetection/ComparisonStrategyInterface.phpdVfd{|1src/Core/OutdatedDetection/VersionDbInterface.phpVf(lr.src/Core/OutdatedDetection/OutdatedChecker.php Vf ]psrc/Report/TextReport.phpVfRxPsrc/Report/JsonReport.phpFVfFD4*src/Report/Includes/RemoteStatsRequest.phpVf2Wsrc/Report/SqliteDbReport.phpVfu&src/Report/AbstractFileBasedReport.phpVfLs src/Report/RemoteStatsReport.php3Vf36籤src/Report/AbstractReport.php Vf /#data/actual-versions-sqlite-db.json`Vf`kdata/actual-versions-db.json$Vf$/get(AVDConfig::PARAM_FACTORY_CONFIG)); Stats::setCurrentMemoryUsageStart(); /** @var EventManager $events */ $events = Factory::instance()->create(EventManager::class); /** @var OutdatedChecker $outdatedComponentChecker */ $outdatedComponentChecker = Factory::instance()->create(OutdatedChecker::class, [ $events, Factory::instance()->create(ActualVersionsDb::class, [new SplFileInfo($config->get(AVDConfig::PARAM_DB_FILE))]), Factory::instance()->create(ComparisonStrategyInterface::class) ]); $events->subscribe(DetectionEvent::class, $outdatedComponentChecker); DetectionEvent::setFileOwners(new FileOwners()); /** @var AbstractFileBasedReport $report */ $text_report = $config->get(AVDConfig::PARAM_TEXT_REPORT); if ($text_report) { $report = Factory::instance()->create(AbstractFileBasedReport::class, $text_report === true ? [] : [$text_report]); $events->subscribe(AbstractEvent::class, $report); } /** @var AbstractFileBasedReport $report */ $jsonOutput = $config->get(AVDConfig::PARAM_JSON_REPORT); if ($jsonOutput) { $report = Factory::instance()->create(AbstractFileBasedReport::class, [$jsonOutput]); $events->subscribe(AbstractEvent::class, $report); } if ($config->get(AVDConfig::PARAM_SEND_STATS)) { /** @var RemoteStatsReport $report */ $iaid_token = null; if (is_readable(IAID_TOKEN_PATH)) { $iaid_token = file_get_contents(IAID_TOKEN_PATH); } $request = Factory::instance()->create(RemoteStatsRequest::class, [$iaid_token]); $report = Factory::instance()->create(RemoteStatsReport::class, [$request]); $events->subscribe(AbstractEvent::class, $report); } $dbReportConnection = null; if ($dbPath = $config->get(AVDConfig::PARAM_SQLITE_DB_REPORT)) { $dbReportConnection = Factory::instance()->create(SqliteDbReportConnection::class, [$dbPath]); /** @var SqliteDbReport $sqliteDbReport */ $sqliteDbReport = Factory::instance()->create(SqliteDbReport::class, [$dbReportConnection]); $events->subscribe(AbstractEvent::class, $sqliteDbReport); } $detector = (new DetectorBuilder())->build(); $scanner = new Scanner($events); $scanner->run($dbReportConnection, $config->get(AVDConfig::PARAM_TARGET_DIRECTORIES), $detector, $config->get(AVDConfig::PARAM_SCAN_DEPTH), $config->get(AVDConfig::PARAM_SINCE)); Profiler::stopProfilling(); = 70300)) { $issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.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; } } trigger_error( 'Composer detected issues in your platform: ' . implode(' ', $issues), E_USER_ERROR ); } * 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 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|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(); } /** * @return array[] * @psalm-return list}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 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'; $installed[] = self::$installedByVendor[$vendorDir] = $required; if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { self::$installed = $installed[count($installed) - 1]; } } } } 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()) { $installed[] = self::$installed; } return $installed; } } array($vendorDir . '/composer/semver/src'), 'AppVersionDetector\\Tests\\' => array($baseDir . '/tests'), 'AppVersionDetector\\' => array($baseDir . '/src'), ); 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. array ( 'Composer\\Semver\\' => 16, ), 'A' => array ( 'AppVersionDetector\\Tests\\' => 25, 'AppVersionDetector\\' => 19, ), ); public static $prefixDirsPsr4 = array ( 'Composer\\Semver\\' => array ( 0 => __DIR__ . '/..' . '/composer/semver/src', ), 'AppVersionDetector\\Tests\\' => array ( 0 => __DIR__ . '/../..' . '/tests', ), 'AppVersionDetector\\' => array ( 0 => __DIR__ . '/../..' . '/src', ), ); public static $classMap = array ( 'AppVersionDetector\\Application\\AVDCliParse' => __DIR__ . '/../..' . '/src/Application/AVDCliParse.php', 'AppVersionDetector\\Application\\AVDConfig' => __DIR__ . '/../..' . '/src/Application/AVDConfig.php', 'AppVersionDetector\\Application\\AppException' => __DIR__ . '/../..' . '/src/Application/AppException.php', 'AppVersionDetector\\Application\\CliParse' => __DIR__ . '/../..' . '/src/Application/CliParse.php', 'AppVersionDetector\\Application\\Config' => __DIR__ . '/../..' . '/src/Application/Config.php', 'AppVersionDetector\\Application\\ConfigParamException' => __DIR__ . '/../..' . '/src/Application/ConfigParamException.php', 'AppVersionDetector\\Application\\DetectorBuilder' => __DIR__ . '/../..' . '/src/Application/DetectorBuilder.php', 'AppVersionDetector\\Application\\Directory' => __DIR__ . '/../..' . '/src/Application/Directory.php', 'AppVersionDetector\\Application\\Error\\ErrorHandler' => __DIR__ . '/../..' . '/src/Application/Error/ErrorHandler.php', 'AppVersionDetector\\Application\\Factory' => __DIR__ . '/../..' . '/src/Application/Factory.php', 'AppVersionDetector\\Application\\FileOwners' => __DIR__ . '/../..' . '/src/Application/FileOwners.php', 'AppVersionDetector\\Application\\Helper' => __DIR__ . '/../..' . '/src/Application/Helper.php', 'AppVersionDetector\\Application\\Migraitor' => __DIR__ . '/../..' . '/src/Application/Migraitor.php', 'AppVersionDetector\\Application\\Profiler' => __DIR__ . '/../..' . '/src/Application/Profiler.php', 'AppVersionDetector\\Application\\Stats' => __DIR__ . '/../..' . '/src/Application/Stats.php', 'AppVersionDetector\\Core\\AbstractEvent' => __DIR__ . '/../..' . '/src/Core/AbstractEvent.php', 'AppVersionDetector\\Core\\ListenerInterface' => __DIR__ . '/../..' . '/src/Core/ListenerInterface.php', 'AppVersionDetector\\Core\\MediatorInterface' => __DIR__ . '/../..' . '/src/Core/MediatorInterface.php', 'AppVersionDetector\\Core\\OutdatedDetection\\ComparisonStrategyInterface' => __DIR__ . '/../..' . '/src/Core/OutdatedDetection/ComparisonStrategyInterface.php', 'AppVersionDetector\\Core\\OutdatedDetection\\GenericComparisonStrategy' => __DIR__ . '/../..' . '/src/Core/OutdatedDetection/GenericComparisonStrategy.php', 'AppVersionDetector\\Core\\OutdatedDetection\\OutdatedChecker' => __DIR__ . '/../..' . '/src/Core/OutdatedDetection/OutdatedChecker.php', 'AppVersionDetector\\Core\\OutdatedDetection\\OutdatedEvent' => __DIR__ . '/../..' . '/src/Core/OutdatedDetection/OutdatedEvent.php', 'AppVersionDetector\\Core\\OutdatedDetection\\VersionDbInterface' => __DIR__ . '/../..' . '/src/Core/OutdatedDetection/VersionDbInterface.php', 'AppVersionDetector\\Core\\PublisherInterface' => __DIR__ . '/../..' . '/src/Core/PublisherInterface.php', 'AppVersionDetector\\Core\\VersionDetection\\AbstractCompositeDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/AbstractCompositeDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\AbstractDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/AbstractDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\DependencyCollectionDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/DependencyCollectionDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\DetectionEvent' => __DIR__ . '/../..' . '/src/Core/VersionDetection/DetectionEvent.php', 'AppVersionDetector\\Core\\VersionDetection\\DetectorInterface' => __DIR__ . '/../..' . '/src/Core/VersionDetection/DetectorInterface.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\BitrixCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/BitrixCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\CommonScriptDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/CommonScriptDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\DrupalCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/DrupalCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\DrupalPluginDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/DrupalPluginDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\DummyDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/DummyDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\IPBCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/IPBCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\JoomlaCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/JoomlaCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\JoomlaPluginDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/JoomlaPluginDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\MagentoCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/MagentoCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\ModxCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/ModxCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\OpenCartCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/OpenCartCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\OsCommerceCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/OsCommerceCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\PHPBB3CoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/PHPBB3CoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpComponentDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/WpComponentDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpCoreDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/WpCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpPluginDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/WpPluginDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpThemeDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/Detector/WpThemeDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\PlainCollectionDetector' => __DIR__ . '/../..' . '/src/Core/VersionDetection/PlainCollectionDetector.php', 'AppVersionDetector\\Event\\EventManager' => __DIR__ . '/../..' . '/src/Event/EventManager.php', 'AppVersionDetector\\Event\\ScanningDirEndedEvent' => __DIR__ . '/../..' . '/src/Event/ScanningDirEndedEvent.php', 'AppVersionDetector\\Event\\ScanningDirStartedEvent' => __DIR__ . '/../..' . '/src/Event/ScanningDirStartedEvent.php', 'AppVersionDetector\\Event\\ScanningEndedEvent' => __DIR__ . '/../..' . '/src/Event/ScanningEndedEvent.php', 'AppVersionDetector\\Event\\ScanningStartedEvent' => __DIR__ . '/../..' . '/src/Event/ScanningStartedEvent.php', 'AppVersionDetector\\Report\\AbstractFileBasedReport' => __DIR__ . '/../..' . '/src/Report/AbstractFileBasedReport.php', 'AppVersionDetector\\Report\\AbstractReport' => __DIR__ . '/../..' . '/src/Report/AbstractReport.php', 'AppVersionDetector\\Report\\Includes\\RemoteStatsRequest' => __DIR__ . '/../..' . '/src/Report/Includes/RemoteStatsRequest.php', 'AppVersionDetector\\Report\\JsonReport' => __DIR__ . '/../..' . '/src/Report/JsonReport.php', 'AppVersionDetector\\Report\\RemoteStatsReport' => __DIR__ . '/../..' . '/src/Report/RemoteStatsReport.php', 'AppVersionDetector\\Report\\SqliteDbReport' => __DIR__ . '/../..' . '/src/Report/SqliteDbReport.php', 'AppVersionDetector\\Report\\TextReport' => __DIR__ . '/../..' . '/src/Report/TextReport.php', 'AppVersionDetector\\Scanner' => __DIR__ . '/../..' . '/src/Scanner.php', 'AppVersionDetector\\Storage\\ActualVersionsDb' => __DIR__ . '/../..' . '/src/Storage/ActualVersionsDb.php', 'AppVersionDetector\\Storage\\ActualVersionsSQLiteDb' => __DIR__ . '/../..' . '/src/Storage/ActualVersionsSQLiteDb.php', 'AppVersionDetector\\Storage\\SqliteDbReportConnection' => __DIR__ . '/../..' . '/src/Storage/SqliteDbReportConnection.php', 'AppVersionDetector\\Tests\\Util\\ArchiveTestTrait' => __DIR__ . '/../..' . '/tests/Util/ArchiveTestTrait.php', 'AppVersionDetector\\Tests\\Util\\IntegrationTestTrait' => __DIR__ . '/../..' . '/tests/Util/IntegrationTestTrait.php', 'AppVersionDetector\\Tests\\Util\\SqliteTestTrait' => __DIR__ . '/../..' . '/tests/Util/SqliteTestTrait.php', 'AppVersionDetector\\Tests\\Util\\TestCase' => __DIR__ . '/../..' . '/tests/Util/TestCase.php', 'AppVersionDetector\\Tests\\unit\\Core\\OutdatedDetection\\GenericComparisonStrategyTest' => __DIR__ . '/../..' . '/tests/unit/Core/OutdatedDetection/GenericComparisonStrategyTest.php', 'AppVersionDetector\\Tests\\unit\\Core\\VersionDetection\\Detector\\AbstractWpComponentDetector' => __DIR__ . '/../..' . '/tests/unit/Core/VersionDetection/Detector/AbstractWpComponentDetector.php', 'AppVersionDetector\\Tests\\unit\\Core\\VersionDetection\\Detector\\WpPluginsDetectorDirLimitTest' => __DIR__ . '/../..' . '/tests/unit/Core/VersionDetection/Detector/WpPluginsDetectorDirLimitTest.php', 'AppVersionDetector\\Tests\\unit\\Storage\\ActualVersionsDbTest' => __DIR__ . '/../..' . '/tests/unit/Storage/ActualVersionsDbTest.php', 'AppVersionDetector\\Tests\\unit\\Storage\\ActualVersionsSQLiteDbTest' => __DIR__ . '/../..' . '/tests/unit/Storage/ActualVersionsSQLiteDbTest.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php', 'Composer\\Semver\\Constraint\\AbstractConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/AbstractConstraint.php', 'Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php', 'Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php', 'Composer\\Semver\\Constraint\\EmptyConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/EmptyConstraint.php', 'Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php', 'Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php', 'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit431d7b3dd0d6aa4f493c20cfbb8de03f::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit431d7b3dd0d6aa4f493c20cfbb8de03f::$prefixDirsPsr4; $loader->classMap = ComposerStaticInit431d7b3dd0d6aa4f493c20cfbb8de03f::$classMap; }, null, ClassLoader::class); } } array( 'name' => 'cloudlinux/app-version-detector', 'pretty_version' => '1.0.0+no-version-set', 'version' => '1.0.0.0', 'reference' => null, 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => false, ), 'versions' => array( 'cloudlinux/app-version-detector' => 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, ), 'composer/semver' => array( 'pretty_version' => '1.7.2', 'version' => '1.7.2.0', 'reference' => '647490bbcaf7fc4891c58f47b825eb99d19c377a', 'type' => 'library', 'install_path' => __DIR__ . '/./semver', 'aliases' => array(), 'dev_requirement' => false, ), ), ); $baseDir . '/src/Application/AVDCliParse.php', 'AppVersionDetector\\Application\\AVDConfig' => $baseDir . '/src/Application/AVDConfig.php', 'AppVersionDetector\\Application\\AppException' => $baseDir . '/src/Application/AppException.php', 'AppVersionDetector\\Application\\CliParse' => $baseDir . '/src/Application/CliParse.php', 'AppVersionDetector\\Application\\Config' => $baseDir . '/src/Application/Config.php', 'AppVersionDetector\\Application\\ConfigParamException' => $baseDir . '/src/Application/ConfigParamException.php', 'AppVersionDetector\\Application\\DetectorBuilder' => $baseDir . '/src/Application/DetectorBuilder.php', 'AppVersionDetector\\Application\\Directory' => $baseDir . '/src/Application/Directory.php', 'AppVersionDetector\\Application\\Error\\ErrorHandler' => $baseDir . '/src/Application/Error/ErrorHandler.php', 'AppVersionDetector\\Application\\Factory' => $baseDir . '/src/Application/Factory.php', 'AppVersionDetector\\Application\\FileOwners' => $baseDir . '/src/Application/FileOwners.php', 'AppVersionDetector\\Application\\Helper' => $baseDir . '/src/Application/Helper.php', 'AppVersionDetector\\Application\\Migraitor' => $baseDir . '/src/Application/Migraitor.php', 'AppVersionDetector\\Application\\Profiler' => $baseDir . '/src/Application/Profiler.php', 'AppVersionDetector\\Application\\Stats' => $baseDir . '/src/Application/Stats.php', 'AppVersionDetector\\Core\\AbstractEvent' => $baseDir . '/src/Core/AbstractEvent.php', 'AppVersionDetector\\Core\\ListenerInterface' => $baseDir . '/src/Core/ListenerInterface.php', 'AppVersionDetector\\Core\\MediatorInterface' => $baseDir . '/src/Core/MediatorInterface.php', 'AppVersionDetector\\Core\\OutdatedDetection\\ComparisonStrategyInterface' => $baseDir . '/src/Core/OutdatedDetection/ComparisonStrategyInterface.php', 'AppVersionDetector\\Core\\OutdatedDetection\\GenericComparisonStrategy' => $baseDir . '/src/Core/OutdatedDetection/GenericComparisonStrategy.php', 'AppVersionDetector\\Core\\OutdatedDetection\\OutdatedChecker' => $baseDir . '/src/Core/OutdatedDetection/OutdatedChecker.php', 'AppVersionDetector\\Core\\OutdatedDetection\\OutdatedEvent' => $baseDir . '/src/Core/OutdatedDetection/OutdatedEvent.php', 'AppVersionDetector\\Core\\OutdatedDetection\\VersionDbInterface' => $baseDir . '/src/Core/OutdatedDetection/VersionDbInterface.php', 'AppVersionDetector\\Core\\PublisherInterface' => $baseDir . '/src/Core/PublisherInterface.php', 'AppVersionDetector\\Core\\VersionDetection\\AbstractCompositeDetector' => $baseDir . '/src/Core/VersionDetection/AbstractCompositeDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\AbstractDetector' => $baseDir . '/src/Core/VersionDetection/AbstractDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\DependencyCollectionDetector' => $baseDir . '/src/Core/VersionDetection/DependencyCollectionDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\DetectionEvent' => $baseDir . '/src/Core/VersionDetection/DetectionEvent.php', 'AppVersionDetector\\Core\\VersionDetection\\DetectorInterface' => $baseDir . '/src/Core/VersionDetection/DetectorInterface.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\BitrixCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/BitrixCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\CommonScriptDetector' => $baseDir . '/src/Core/VersionDetection/Detector/CommonScriptDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\DrupalCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/DrupalCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\DrupalPluginDetector' => $baseDir . '/src/Core/VersionDetection/Detector/DrupalPluginDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\DummyDetector' => $baseDir . '/src/Core/VersionDetection/Detector/DummyDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\IPBCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/IPBCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\JoomlaCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/JoomlaCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\JoomlaPluginDetector' => $baseDir . '/src/Core/VersionDetection/Detector/JoomlaPluginDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\MagentoCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/MagentoCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\ModxCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/ModxCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\OpenCartCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/OpenCartCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\OsCommerceCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/OsCommerceCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\PHPBB3CoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/PHPBB3CoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpComponentDetector' => $baseDir . '/src/Core/VersionDetection/Detector/WpComponentDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpCoreDetector' => $baseDir . '/src/Core/VersionDetection/Detector/WpCoreDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpPluginDetector' => $baseDir . '/src/Core/VersionDetection/Detector/WpPluginDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\Detector\\WpThemeDetector' => $baseDir . '/src/Core/VersionDetection/Detector/WpThemeDetector.php', 'AppVersionDetector\\Core\\VersionDetection\\PlainCollectionDetector' => $baseDir . '/src/Core/VersionDetection/PlainCollectionDetector.php', 'AppVersionDetector\\Event\\EventManager' => $baseDir . '/src/Event/EventManager.php', 'AppVersionDetector\\Event\\ScanningDirEndedEvent' => $baseDir . '/src/Event/ScanningDirEndedEvent.php', 'AppVersionDetector\\Event\\ScanningDirStartedEvent' => $baseDir . '/src/Event/ScanningDirStartedEvent.php', 'AppVersionDetector\\Event\\ScanningEndedEvent' => $baseDir . '/src/Event/ScanningEndedEvent.php', 'AppVersionDetector\\Event\\ScanningStartedEvent' => $baseDir . '/src/Event/ScanningStartedEvent.php', 'AppVersionDetector\\Report\\AbstractFileBasedReport' => $baseDir . '/src/Report/AbstractFileBasedReport.php', 'AppVersionDetector\\Report\\AbstractReport' => $baseDir . '/src/Report/AbstractReport.php', 'AppVersionDetector\\Report\\Includes\\RemoteStatsRequest' => $baseDir . '/src/Report/Includes/RemoteStatsRequest.php', 'AppVersionDetector\\Report\\JsonReport' => $baseDir . '/src/Report/JsonReport.php', 'AppVersionDetector\\Report\\RemoteStatsReport' => $baseDir . '/src/Report/RemoteStatsReport.php', 'AppVersionDetector\\Report\\SqliteDbReport' => $baseDir . '/src/Report/SqliteDbReport.php', 'AppVersionDetector\\Report\\TextReport' => $baseDir . '/src/Report/TextReport.php', 'AppVersionDetector\\Scanner' => $baseDir . '/src/Scanner.php', 'AppVersionDetector\\Storage\\ActualVersionsDb' => $baseDir . '/src/Storage/ActualVersionsDb.php', 'AppVersionDetector\\Storage\\ActualVersionsSQLiteDb' => $baseDir . '/src/Storage/ActualVersionsSQLiteDb.php', 'AppVersionDetector\\Storage\\SqliteDbReportConnection' => $baseDir . '/src/Storage/SqliteDbReportConnection.php', 'AppVersionDetector\\Tests\\Util\\ArchiveTestTrait' => $baseDir . '/tests/Util/ArchiveTestTrait.php', 'AppVersionDetector\\Tests\\Util\\IntegrationTestTrait' => $baseDir . '/tests/Util/IntegrationTestTrait.php', 'AppVersionDetector\\Tests\\Util\\SqliteTestTrait' => $baseDir . '/tests/Util/SqliteTestTrait.php', 'AppVersionDetector\\Tests\\Util\\TestCase' => $baseDir . '/tests/Util/TestCase.php', 'AppVersionDetector\\Tests\\unit\\Core\\OutdatedDetection\\GenericComparisonStrategyTest' => $baseDir . '/tests/unit/Core/OutdatedDetection/GenericComparisonStrategyTest.php', 'AppVersionDetector\\Tests\\unit\\Core\\VersionDetection\\Detector\\AbstractWpComponentDetector' => $baseDir . '/tests/unit/Core/VersionDetection/Detector/AbstractWpComponentDetector.php', 'AppVersionDetector\\Tests\\unit\\Core\\VersionDetection\\Detector\\WpPluginsDetectorDirLimitTest' => $baseDir . '/tests/unit/Core/VersionDetection/Detector/WpPluginsDetectorDirLimitTest.php', 'AppVersionDetector\\Tests\\unit\\Storage\\ActualVersionsDbTest' => $baseDir . '/tests/unit/Storage/ActualVersionsDbTest.php', 'AppVersionDetector\\Tests\\unit\\Storage\\ActualVersionsSQLiteDbTest' => $baseDir . '/tests/unit/Storage/ActualVersionsSQLiteDbTest.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php', 'Composer\\Semver\\Constraint\\AbstractConstraint' => $vendorDir . '/composer/semver/src/Constraint/AbstractConstraint.php', 'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php', 'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php', 'Composer\\Semver\\Constraint\\EmptyConstraint' => $vendorDir . '/composer/semver/src/Constraint/EmptyConstraint.php', 'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php', 'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php', 'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php', ); * 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); } } setClassMapAuthoritative(true); $loader->register(true); return $loader; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\EmptyConstraint; use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Constraint\Constraint; /** * Version parser. * * @author Jordi Boggiano */ class VersionParser { /** * Regex to match pre-release data (sort of). * * Due to backwards compatibility: * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted. * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier. * - Numerical-only pre-release identifiers are not supported, see tests. * * |--------------| * [major].[minor].[patch] -[pre-release] +[build-metadata] * * @var string */ private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?'; /** @var string */ private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev'; /** * Returns the stability of a version. * * @param string $version * * @return string */ public static function parseStability($version) { $version = preg_replace('{#.+$}i', '', $version); if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) { return 'dev'; } preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match); if (!empty($match[3])) { return 'dev'; } if (!empty($match[1])) { if ('beta' === $match[1] || 'b' === $match[1]) { return 'beta'; } if ('alpha' === $match[1] || 'a' === $match[1]) { return 'alpha'; } if ('rc' === $match[1]) { return 'RC'; } } return 'stable'; } /** * @param string $stability * * @return string */ public static function normalizeStability($stability) { $stability = strtolower($stability); return $stability === 'rc' ? 'RC' : $stability; } /** * Normalizes a version string to be able to perform comparisons on it. * * @param string $version * @param string $fullVersion optional complete version string to give more context * * @throws \UnexpectedValueException * * @return string */ public function normalize($version, $fullVersion = null) { $version = trim($version); $origVersion = $version; if (null === $fullVersion) { $fullVersion = $version; } // strip off aliasing if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) { $version = $match[1]; } // strip off stability flag if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) { $version = substr($version, 0, strlen($version) - strlen($match[0])); } // match master-like branches if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { return '9999999-dev'; } // if requirement is branch-like, use full name if (stripos($version, 'dev-') === 0) { return 'dev-' . substr($version, 4); } // strip off build metadata if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) { $version = $match[1]; } // match classical versioning if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { $version = $matches[1] . (!empty($matches[2]) ? $matches[2] : '.0') . (!empty($matches[3]) ? $matches[3] : '.0') . (!empty($matches[4]) ? $matches[4] : '.0'); $index = 5; // match date(time) based versioning } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { $version = preg_replace('{\D}', '.', $matches[1]); $index = 2; } // add version modifiers if a version was matched if (isset($index)) { if (!empty($matches[$index])) { if ('stable' === $matches[$index]) { return $version; } $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : ''); } if (!empty($matches[$index + 2])) { $version .= '-dev'; } return $version; } // match dev branches if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { try { $normalized = $this->normalizeBranch($match[1]); // a branch ending with -dev is only valid if it is numeric // if it gets prefixed with dev- it means the branch name should // have had a dev- prefix already when passed to normalize if (strpos($normalized, 'dev-') === false) { return $normalized; } } catch (\Exception $e) { } } $extraMessage = ''; if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; } throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage); } /** * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison. * * @param string $branch Branch name (e.g. 2.1.x-dev) * * @return string|false Numeric prefix if present (e.g. 2.1.) or false */ public function parseNumericAliasPrefix($branch) { if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) { return $matches['version'] . '.'; } return false; } /** * Normalizes a branch name to be able to perform comparisons on it. * * @param string $name * * @return string */ public function normalizeBranch($name) { $name = trim($name); if (in_array($name, array('master', 'trunk', 'default'))) { return $this->normalize($name); } if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { $version = ''; for ($i = 1; $i < 5; ++$i) { $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; } return str_replace('x', '9999999', $version) . '-dev'; } return 'dev-' . $name; } /** * Parses a constraint string into MultiConstraint and/or Constraint objects. * * @param string $constraints * * @return ConstraintInterface */ public function parseConstraints($constraints) { $prettyConstraint = $constraints; $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints)); $orGroups = array(); foreach ($orConstraints as $constraints) { $andConstraints = preg_split('{(?< ,]) *(? 1) { $constraintObjects = array(); foreach ($andConstraints as $constraint) { foreach ($this->parseConstraint($constraint) as $parsedConstraint) { $constraintObjects[] = $parsedConstraint; } } } else { $constraintObjects = $this->parseConstraint($andConstraints[0]); } if (1 === count($constraintObjects)) { $constraint = $constraintObjects[0]; } else { $constraint = new MultiConstraint($constraintObjects); } $orGroups[] = $constraint; } if (1 === count($orGroups)) { $constraint = $orGroups[0]; } elseif (2 === count($orGroups) // parse the two OR groups and if they are contiguous we collapse // them into one constraint && $orGroups[0] instanceof MultiConstraint && $orGroups[1] instanceof MultiConstraint && 2 === count($orGroups[0]->getConstraints()) && 2 === count($orGroups[1]->getConstraints()) && ($a = (string) $orGroups[0]) && strpos($a, '[>=') === 0 && (false !== ($posA = strpos($a, '<', 4))) && ($b = (string) $orGroups[1]) && strpos($b, '[>=') === 0 && (false !== ($posB = strpos($b, '<', 4))) && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5) ) { $constraint = new MultiConstraint(array( new Constraint('>=', substr($a, 4, $posA - 5)), new Constraint('<', substr($b, $posB + 2, -1)), )); } else { $constraint = new MultiConstraint($orGroups, false); } $constraint->setPrettyString($prettyConstraint); return $constraint; } /** * @param string $constraint * * @throws \UnexpectedValueException * * @return array */ private function parseConstraint($constraint) { // strip off aliasing if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) { $constraint = $match[1]; } // strip @stability flags, and keep it for later use if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) { $constraint = '' !== $match[1] ? $match[1] : '*'; if ($match[2] !== 'stable') { $stabilityModifier = $match[2]; } } // get rid of #refs as those are used by composer only if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) { $constraint = $match[1]; } if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) { return array(new EmptyConstraint()); } $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?'; // Tilde Range // // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous // version, to ensure that unstable instances of the current version are allowed. However, if a stability // suffix is added to the constraint, then a >= match on the current version is used instead. if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { if (strpos($constraint, '~>') === 0) { throw new \UnexpectedValueException( 'Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator' ); } // Work out which position in the version we are operating at if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { $position = 4; } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above if (!empty($matches[8])) { $position++; } // Calculate the stability suffix $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal $highPosition = max(1, $position - 1); $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } // Caret Range // // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for // versions 0.X >=0.1.0, and no updates for versions 0.0.X if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { // Work out which position in the version we are operating at if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { $position = 1; } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { $position = 2; } else { $position = 3; } // Calculate the stability suffix $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } // X Range // // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple. // A partial version range is treated as an X-Range, so the special character is in fact optional. if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; if ($lowVersion === '0.0.0.0-dev') { return array(new Constraint('<', $highVersion)); } return array( new Constraint('>=', $lowVersion), new Constraint('<', $highVersion), ); } // Hyphen Range // // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range, // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but // nothing that would be greater than the provided tuple parts. if (preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { // Calculate the stability suffix $lowStabilitySuffix = ''; if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) { $lowStabilitySuffix = '-dev'; } $lowVersion = $this->normalize($matches['from']); $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); $empty = function ($x) { return ($x === 0 || $x === '0') ? false : empty($x); }; if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) { $highVersion = $this->normalize($matches['to']); $upperBound = new Constraint('<=', $highVersion); } else { $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]); // validate to version $this->normalize($matches['to']); $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); } return array( $lowerBound, $upperBound, ); } // Basic Comparators if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { try { try { $version = $this->normalize($matches[2]); } catch (\UnexpectedValueException $e) { // recover from an invalid constraint like foobar-dev which should be dev-foobar // except if the constraint uses a known operator, in which case it must be a parse error if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) { $version = $this->normalize('dev-'.substr($matches[2], 0, -4)); } else { throw $e; } } $op = $matches[1] ?: '='; if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') { $version .= '-' . $stabilityModifier; } elseif ('<' === $op || '>=' === $op) { if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { if (strpos($matches[2], 'dev-') !== 0) { $version .= '-dev'; } } } return array(new Constraint($matches[1] ?: '=', $version)); } catch (\Exception $e) { } } $message = 'Could not parse version constraint ' . $constraint; if (isset($e)) { $message .= ': ' . $e->getMessage(); } throw new \UnexpectedValueException($message); } /** * Increment, decrement, or simply pad a version number. * * Support function for {@link parseConstraint()} * * @param array $matches Array with version parts in array indexes 1,2,3,4 * @param int $position 1,2,3,4 - which segment of the version to increment/decrement * @param int $increment * @param string $pad The string to pad version parts after $position * * @return string|null The new version */ private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0') { for ($i = 4; $i > 0; --$i) { if ($i > $position) { $matches[$i] = $pad; } elseif ($i === $position && $increment) { $matches[$i] += $increment; // If $matches[$i] was 0, carry the decrement if ($matches[$i] < 0) { $matches[$i] = $pad; --$position; // Return null on a carry overflow if ($i === 1) { return null; } } } } return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; } /** * Expand shorthand stability string to long version. * * @param string $stability * * @return string */ private function expandStability($stability) { $stability = strtolower($stability); switch ($stability) { case 'a': return 'alpha'; case 'b': return 'beta'; case 'p': case 'pl': return 'patch'; case 'rc': return 'RC'; default: return $stability; } } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; interface ConstraintInterface { /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider); /** * @return string */ public function getPrettyString(); /** * @return string */ public function __toString(); } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines the absence of a constraint. */ class EmptyConstraint implements ConstraintInterface { /** @var string */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { return true; } /** * @param string $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } /** * @return string */ public function __toString() { return '[]'; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; trigger_error('The ' . __NAMESPACE__ . '\AbstractConstraint abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED); /** * Base constraint class. */ abstract class AbstractConstraint implements ConstraintInterface { /** @var string */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { if ($provider instanceof $this) { // see note at bottom of this class declaration return $this->matchSpecific($provider); } // turn matching around to find a match return $provider->matches($this); } /** * @param string $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } // implementations must implement a method of this format: // not declared abstract here because type hinting violates parameter coherence (TODO right word?) // public function matchSpecific( $provider); } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines a conjunctive or disjunctive set of constraints. */ class MultiConstraint implements ConstraintInterface { /** @var ConstraintInterface[] */ protected $constraints; /** @var string|null */ protected $prettyString; /** @var bool */ protected $conjunctive; /** * @param ConstraintInterface[] $constraints A set of constraints * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive */ public function __construct(array $constraints, $conjunctive = true) { $this->constraints = $constraints; $this->conjunctive = $conjunctive; } /** * @return ConstraintInterface[] */ public function getConstraints() { return $this->constraints; } /** * @return bool */ public function isConjunctive() { return $this->conjunctive; } /** * @return bool */ public function isDisjunctive() { return !$this->conjunctive; } /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { if (false === $this->conjunctive) { foreach ($this->constraints as $constraint) { if ($constraint->matches($provider)) { return true; } } return false; } foreach ($this->constraints as $constraint) { if (!$constraint->matches($provider)) { return false; } } return true; } /** * @param string|null $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } /** * @return string */ public function __toString() { $constraints = array(); foreach ($this->constraints as $constraint) { $constraints[] = (string) $constraint; } return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines a constraint. */ class Constraint implements ConstraintInterface { /* operator integer values */ const OP_EQ = 0; const OP_LT = 1; const OP_LE = 2; const OP_GT = 3; const OP_GE = 4; const OP_NE = 5; /** * Operator to integer translation table. * * @var array */ private static $transOpStr = array( '=' => self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE, ); /** * Integer to operator translation table. * * @var array */ private static $transOpInt = array( self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!=', ); /** @var int */ protected $operator; /** @var string */ protected $version; /** @var string */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { if ($provider instanceof $this) { return $this->matchSpecific($provider); } // turn matching around to find a match return $provider->matches($this); } /** * @param string $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } /** * Get all supported comparison operators. * * @return array */ public static function getSupportedOperators() { return array_keys(self::$transOpStr); } /** * Sets operator and version to compare with. * * @param string $operator * @param string $version * * @throws \InvalidArgumentException if invalid operator is given. */ public function __construct($operator, $version) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $this->operator = self::$transOpStr[$operator]; $this->version = $version; } /** * @param string $a * @param string $b * @param string $operator * @param bool $compareBranches * * @throws \InvalidArgumentException if invalid operator is given. * * @return bool */ public function versionCompare($a, $b, $operator, $compareBranches = false) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $aIsBranch = 'dev-' === substr($a, 0, 4); $bIsBranch = 'dev-' === substr($b, 0, 4); if ($aIsBranch && $bIsBranch) { return $operator === '==' && $a === $b; } // when branches are not comparable, we make sure dev branches never match anything if (!$compareBranches && ($aIsBranch || $bIsBranch)) { return false; } return version_compare($a, $b, $operator); } /** * @param Constraint $provider * @param bool $compareBranches * * @return bool */ public function matchSpecific(Constraint $provider, $compareBranches = false) { $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); $isEqualOp = self::OP_EQ === $this->operator; $isNonEqualOp = self::OP_NE === $this->operator; $isProviderEqualOp = self::OP_EQ === $provider->operator; $isProviderNonEqualOp = self::OP_NE === $provider->operator; // '!=' operator is match when other operator is not '==' operator or version is not match // these kinds of comparisons always have a solution if ($isNonEqualOp || $isProviderNonEqualOp) { return (!$isEqualOp && !$isProviderEqualOp) || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); } // an example for the condition is <= 2.0 & < 1.0 // these kinds of comparisons always have a solution if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { return true; } if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) { // special case, e.g. require >= 1.0 and provide < 1.0 // 1.0 >= 1.0 but 1.0 is outside of the provided interval return !($provider->version === $this->version && self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp); } return false; } /** * @return string */ public function __toString() { return self::$transOpInt[$this->operator] . ' ' . $this->version; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; class Semver { const SORT_ASC = 1; const SORT_DESC = -1; /** @var VersionParser */ private static $versionParser; /** * Determine if given version satisfies given constraints. * * @param string $version * @param string $constraints * * @return bool */ public static function satisfies($version, $constraints) { if (null === self::$versionParser) { self::$versionParser = new VersionParser(); } $versionParser = self::$versionParser; $provider = new Constraint('==', $versionParser->normalize($version)); $parsedConstraints = $versionParser->parseConstraints($constraints); return $parsedConstraints->matches($provider); } /** * Return all versions that satisfy given constraints. * * @param array $versions * @param string $constraints * * @return array */ public static function satisfiedBy(array $versions, $constraints) { $versions = array_filter($versions, function ($version) use ($constraints) { return Semver::satisfies($version, $constraints); }); return array_values($versions); } /** * Sort given array of versions. * * @param array $versions * * @return array */ public static function sort(array $versions) { return self::usort($versions, self::SORT_ASC); } /** * Sort given array of versions in reverse. * * @param array $versions * * @return array */ public static function rsort(array $versions) { return self::usort($versions, self::SORT_DESC); } /** * @param array $versions * @param int $direction * * @return array */ private static function usort(array $versions, $direction) { if (null === self::$versionParser) { self::$versionParser = new VersionParser(); } $versionParser = self::$versionParser; $normalized = array(); // Normalize outside of usort() scope for minor performance increase. // Creates an array of arrays: [[normalized, key], ...] foreach ($versions as $key => $version) { $normalized[] = array($versionParser->normalize($version), $key); } usort($normalized, function (array $left, array $right) use ($direction) { if ($left[0] === $right[0]) { return 0; } if (Comparator::lessThan($left[0], $right[0])) { return -$direction; } return $direction; }); // Recreate input array, using the original indexes which are now in sorted order. $sorted = array(); foreach ($normalized as $item) { $sorted[] = $versions[$item[1]]; } return $sorted; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; class Comparator { /** * Evaluates the expression: $version1 > $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function greaterThan($version1, $version2) { return self::compare($version1, '>', $version2); } /** * Evaluates the expression: $version1 >= $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function greaterThanOrEqualTo($version1, $version2) { return self::compare($version1, '>=', $version2); } /** * Evaluates the expression: $version1 < $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function lessThan($version1, $version2) { return self::compare($version1, '<', $version2); } /** * Evaluates the expression: $version1 <= $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function lessThanOrEqualTo($version1, $version2) { return self::compare($version1, '<=', $version2); } /** * Evaluates the expression: $version1 == $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function equalTo($version1, $version2) { return self::compare($version1, '==', $version2); } /** * Evaluates the expression: $version1 != $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function notEqualTo($version1, $version2) { return self::compare($version1, '!=', $version2); } /** * Evaluates the expression: $version1 $operator $version2. * * @param string $version1 * @param string $operator * @param string $version2 * * @return bool */ public static function compare($version1, $operator, $version2) { $constraint = new Constraint($operator, $version2); return $constraint->matches(new Constraint('==', $version1)); } } Copyright (C) 2015 Composer 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. composer/semver =============== Semver library that offers utilities, version constraint parsing and validation. Originally written as part of [composer/composer](https://github.com/composer/composer), now extracted and made available as a stand-alone library. [![Build Status](https://travis-ci.org/composer/semver.svg?branch=master)](https://travis-ci.org/composer/semver) Installation ------------ Install the latest version with: ```bash $ composer require composer/semver ``` Requirements ------------ * PHP 5.3.2 is required but using the latest version of PHP is highly recommended. Version Comparison ------------------ For details on how versions are compared, refer to the [Versions](https://getcomposer.org/doc/articles/versions.md) article in the documentation section of the [getcomposer.org](https://getcomposer.org) website. Basic usage ----------- ### Comparator The `Composer\Semver\Comparator` class provides the following methods for comparing versions: * greaterThan($v1, $v2) * greaterThanOrEqualTo($v1, $v2) * lessThan($v1, $v2) * lessThanOrEqualTo($v1, $v2) * equalTo($v1, $v2) * notEqualTo($v1, $v2) Each function takes two version strings as arguments and returns a boolean. For example: ```php use Composer\Semver\Comparator; Comparator::greaterThan('1.25.0', '1.24.0'); // 1.25.0 > 1.24.0 ``` ### Semver The `Composer\Semver\Semver` class provides the following methods: * satisfies($version, $constraints) * satisfiedBy(array $versions, $constraint) * sort($versions) * rsort($versions) License ------- composer/semver is licensed under the MIT License, see the LICENSE file for details. # Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ### [1.7.2] 2020-12-03 * Fixed: Allow installing on php 8 ### [1.7.1] 2020-09-27 * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases * Fixed: normalization of beta0 and such which was dropping the 0 ### [1.7.0] 2020-09-09 * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience ### [1.6.0] 2020-09-08 * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 1.5.2 * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package ### [1.5.2] 2020-09-08 * Fixed: handling of some invalid -dev versions which were seen as valid * Fixed: some doctypes ### [1.5.1] 2020-01-13 * Fixed: Parsing of aliased version was not validating the alias to be a valid version ### [1.5.0] 2019-03-19 * Added: some support for date versions (e.g. 201903) in `~` operator * Fixed: support for stabilities in `~` operator was inconsistent ### [1.4.2] 2016-08-30 * Fixed: collapsing of complex constraints lead to buggy constraints ### [1.4.1] 2016-06-02 * Changed: branch-like requirements no longer strip build metadata - [composer/semver#38](https://github.com/composer/semver/pull/38). ### [1.4.0] 2016-03-30 * Added: getters on MultiConstraint - [composer/semver#35](https://github.com/composer/semver/pull/35). ### [1.3.0] 2016-02-25 * Fixed: stability parsing - [composer/composer#1234](https://github.com/composer/composer/issues/4889). * Changed: collapse contiguous constraints when possible. ### [1.2.0] 2015-11-10 * Changed: allow multiple numerical identifiers in 'pre-release' version part. * Changed: add more 'v' prefix support. ### [1.1.0] 2015-11-03 * Changed: dropped redundant `test` namespace. * Changed: minor adjustment in datetime parsing normalization. * Changed: `ConstraintInterface` relaxed, setPrettyString is not required anymore. * Changed: `AbstractConstraint` marked deprecated, will be removed in 2.0. * Changed: `Constraint` is now extensible. ### [1.0.0] 2015-09-21 * Break: `VersionConstraint` renamed to `Constraint`. * Break: `SpecificConstraint` renamed to `AbstractConstraint`. * Break: `LinkConstraintInterface` renamed to `ConstraintInterface`. * Break: `VersionParser::parseNameVersionPairs` was removed. * Changed: `VersionParser::parseConstraints` allows (but ignores) build metadata now. * Changed: `VersionParser::parseConstraints` allows (but ignores) prefixing numeric versions with a 'v' now. * Changed: Fixed namespace(s) of test files. * Changed: `Comparator::compare` no longer throws `InvalidArgumentException`. * Changed: `Constraint` now throws `InvalidArgumentException`. ### [0.1.0] 2015-07-23 * Added: `Composer\Semver\Comparator`, various methods to compare versions. * Added: various documents such as README.md, LICENSE, etc. * Added: configuration files for Git, Travis, php-cs-fixer, phpunit. * Break: the following namespaces were renamed: - Namespace: `Composer\Package\Version` -> `Composer\Semver` - Namespace: `Composer\Package\LinkConstraint` -> `Composer\Semver\Constraint` - Namespace: `Composer\Test\Package\Version` -> `Composer\Test\Semver` - Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint` * Changed: code style using php-cs-fixer. [1.7.2]: https://github.com/composer/semver/compare/1.7.1...1.7.2 [1.7.1]: https://github.com/composer/semver/compare/1.7.0...1.7.1 [1.7.0]: https://github.com/composer/semver/compare/1.6.0...1.7.0 [1.6.0]: https://github.com/composer/semver/compare/1.5.2...1.6.0 [1.5.2]: https://github.com/composer/semver/compare/1.5.1...1.5.2 [1.5.1]: https://github.com/composer/semver/compare/1.5.0...1.5.1 [1.5.0]: https://github.com/composer/semver/compare/1.4.2...1.5.0 [1.4.2]: https://github.com/composer/semver/compare/1.4.1...1.4.2 [1.4.1]: https://github.com/composer/semver/compare/1.4.0...1.4.1 [1.4.0]: https://github.com/composer/semver/compare/1.3.0...1.4.0 [1.3.0]: https://github.com/composer/semver/compare/1.2.0...1.3.0 [1.2.0]: https://github.com/composer/semver/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/composer/semver/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/composer/semver/compare/0.1.0...1.0.0 [0.1.0]: https://github.com/composer/semver/compare/5e0b9a4da...0.1.0 setSender($sender); $this->scannerVersion = $scannerVersion; $this->directory = $directory; } /** * @return string */ public function getScannerVersion(): string { return $this->scannerVersion; } /** * @return string */ public function getDirectory(): Directory { return $this->directory; } /** * @return int */ public function getDirectoryOwner(): int { return $this->directory->getOwner(); } /** * @return string */ public function getDirectoryDomain(): string { return $this->directory->getDomain(); } } setSender($sender); } } setSender($sender); $this->scannerVersion = $scannerVersion; $this->scan_id = $scanId; } /** * @return string */ public function getScannerVersion(): string { return $this->scannerVersion; } /** * @return string */ public function getScanId(): string { return $this->scan_id; } } setSender($sender); $this->scanId = $scanId; } /** * @return string */ public function getScanId(): string { return $this->scanId; } } subscribe(SomeEvent::class, $listener); * * @param string $eventType * @param ListenerInterface $listener */ public function subscribe($eventType, ListenerInterface $listener) { $this->listeners[$eventType][] = $listener; } /** * An event sender publish an event using this method. * * @param AbstractEvent $event */ public function update(AbstractEvent $event) { foreach ($this->getListeners($event) as $listener) { $listener->notify($event); } } /** * Returns a list of listeners for a particular event class. * * @param AbstractEvent $event * @return SplObjectStorage|ListenerInterface[] */ private function getListeners(AbstractEvent $event) { $listeners = new SplObjectStorage(); foreach ($this->listeners as $eventType => $eventListeners) { if ($event instanceof $eventType) { array_walk($eventListeners, function ($event) use ($listeners) { $listeners->attach($event); }); } } return $listeners; } }path = $path; $db = json_decode(file_get_contents($this->path), true, 512, JSON_THROW_ON_ERROR); if (!$this->validate($db)) { throw new AppException('Failed loading DB from "' . $this->path->getPathname() . '": invalid DB format.'); } $this->db = $db; } /** * Checks if a $name key exists. * * @param string $key * @return boolean */ public function hasKey(string $key) { return isset($this->db[$key]); } /** * Returns pairs for a specified key. * * Note: this method should return an ORDERED list of pairs. * * @param string $key * @return array|null list of pairs */ public function getByKey(string $key) { return $this->hasKey($key) ? $this->db[$key]['branches'] : null; } /** * Validate format of the DB. * * @param array $db * @return bool */ private function validate($db) { foreach ($db as $key => $value) { if (!is_array($value)) { return false; } if (!isset($value['branches']) || !is_array($value['branches'])) { return false; } foreach ($value['branches'] as $pair) { if (!is_array($pair) || count($pair) !== 2 || !is_string($pair[0]) || !is_string($pair[1])) { return false; } } } return true; } }open($path); $this->createTablesIfNotExists(); $this->reportStatement = $this->dbh->prepare(/** @lang SQLite */ ' INSERT INTO `report` (scanID, timestamp_started, timestamp_finished, uid, dir, domain) VALUES (:scanID, :timestamp_started, :timestamp_finished, :uid, :dir, :domain) '); $this->appsStatement = $this->dbh->prepare(/** @lang SQLite */ ' INSERT INTO `apps` (report_id, parent_id, title, version, path, filename, app_uid, real_path) VALUES (:report_id, :parent_id, :title, :version, :path, :filename, :app_uid, :real_path) '); } /** * Inserts several rows(report and apps) at once using a transaction. * * @param string $scanID * @param string $timestamp_started * @param string $timestamp_finished * @param integer $uid * @param string $dir * @param array $report_data * * @throws Exception */ public function insertDirData($scanID, $timestamp_started, $timestamp_finished, $uid, $dir, $domain, $report_data) { $this->dbh->exec('BEGIN;'); try { $report_id = $this->insertReport($scanID, $timestamp_started, $timestamp_finished, $uid, $dir, $domain); $root_id = null; foreach ($report_data as $app) { $parent_id = $this->insertApp($report_id, $root_id, $app['appName'], $app['appVersion'], $app['appPath'], $app['appFilename'], $app['appUID'], $app['appRealPath']); if (isset($app['subApp'])) { foreach ($app['subApp'] as $subApp) { $this->insertApp($report_id, $parent_id, $subApp['appName'], $subApp['appVersion'], $subApp['appPath'], $subApp['appFilename'], $subApp['appUID'], $subApp['appRealPath']); } } } $this->dbh->exec('COMMIT;'); } catch (\Exception $exception) { $this->dbh->exec('ROLLBACK;'); throw $exception; } } /** * Delete old reports for directory * * @param string $dir */ public function clearOutdatedData($dir) { $sql = 'DELETE FROM report WHERE dir = :dir AND id NOT IN ( SELECT max(id) FROM report WHERE dir = :dir ) '; $stmt = $this->dbh->prepare($sql); $stmt->bindValue('dir', $dir, SQLITE3_TEXT); $stmt->execute(); } /** * Are there reports on the $directory starting at a $since? * * @param string $directory * @param string $since * * @return bool */ public function haveRelevantReport($directory, $since) { $sql = 'SELECT count(*)' . ' FROM report' . ' WHERE dir = :dir AND timestamp_finished > :since'; $stmt = $this->dbh->prepare($sql); $stmt->bindValue('dir', $directory, SQLITE3_TEXT); $stmt->bindValue('since', $since, SQLITE3_TEXT); $result = $stmt->execute(); return (bool)$result->fetchArray(SQLITE3_NUM)[0]; } /** * SqliteDbReportConnection destructor. */ public function __destruct() { $this->close(); } /** * Inserts a one record into report table. * * @param string $scanID * @param string $timestamp_started * @param string $timestamp_finished * @param integer $uid * @param string $dir * @param string $domain * * @return integer Last insert id */ private function insertReport($scanID, $timestamp_started, $timestamp_finished, $uid, $dir, $domain): int { $this->reportStatement->bindValue('scanID', $scanID, SQLITE3_TEXT); $this->reportStatement->bindValue('timestamp_started', $timestamp_started, SQLITE3_TEXT); $this->reportStatement->bindValue('timestamp_finished', $timestamp_finished, SQLITE3_TEXT); $this->reportStatement->bindValue('uid', $uid, SQLITE3_INTEGER); $this->reportStatement->bindValue('dir', $dir, SQLITE3_TEXT); $this->reportStatement->bindValue('domain', $domain, SQLITE3_TEXT); $result = $this->reportStatement->execute(); return $this->dbh->lastInsertRowID(); } /** * Inserts a one record into apps table. * * @param integer $reportId * @param integer|null $parentId * @param string $title * @param string $version * @param string $path * @param string|null $filename * * @return integer Last insert id */ private function insertApp($reportId, $parentId, $title, $version, $path, $filename, $uid, $real_path): int { $this->appsStatement->bindValue('report_id', $reportId, SQLITE3_INTEGER); $this->appsStatement->bindValue('parent_id', $parentId, SQLITE3_INTEGER); $this->appsStatement->bindValue('title', $title, SQLITE3_TEXT); $this->appsStatement->bindValue('version', $version, SQLITE3_TEXT); $this->appsStatement->bindValue('path', $path, SQLITE3_TEXT); $this->appsStatement->bindValue('filename', $filename, SQLITE3_TEXT); $this->appsStatement->bindValue('app_uid', $uid, SQLITE3_INTEGER); $this->appsStatement->bindValue('real_path', $real_path, SQLITE3_TEXT); $result = $this->appsStatement->execute(); return $this->dbh->lastInsertRowID(); } /** * Opens connection. * * @param string $path */ private function open($path) { $this->dbh = new \SQLite3($path); $this->dbh->busyTimeout(self::BUSY_TIMEOUT_MSEC); $this->dbh->exec('PRAGMA journal_mode = WAL; PRAGMA foreign_keys=ON;'); } /** * Closes connection. */ private function close() { $this->dbh = null; } /** * Create tables(report and apps) if not exists. */ private function createTablesIfNotExists() { $this->dbh->exec(/** @lang SQLite */' CREATE TABLE IF NOT EXISTS report ( id INTEGER PRIMARY KEY, scanID TEXT NOT NULL, timestamp_started TEXT NOT NULL, timestamp_finished TEXT NOT NULL, uid INTEGER, dir TEXT NOT NULL, domain TEXT DEFAULT NULL ) '); $this->dbh->exec(/** @lang SQLite */' CREATE TABLE IF NOT EXISTS apps ( id INTEGER PRIMARY KEY, report_id INTEGER NOT NULL REFERENCES report(id) ON DELETE CASCADE, parent_id INTEGER DEFAULT NULL REFERENCES apps(id), title TEXT NOT NULL, version TEXT NOT NULL, path TEXT NOT NULL, filename TEXT DEFAULT NULL, app_uid INTEGER DEFAULT NULL, real_path TEXT DEFAULT NULL ) '); } } getPathname() . '": DB file not found.'); } $this->open($path); if (!$this->validate()) { throw new AppException('Failed loading DB from "' . $path->getPathname() . '": invalid DB format.'); } } /** * Checks if a $name key exists. * * @param string $key * @return boolean */ public function hasKey(string $key) { $sql = 'SELECT id ' . ' FROM app ' . ' WHERE name = :name'; $stmt = $this->dbh->prepare($sql); $stmt->bindValue('name', $key, SQLITE3_TEXT); $result = $stmt->execute(); return (bool)$result->fetchArray(SQLITE3_NUM); } /** * Returns pairs for a specified key. * * Note: this method should return an ORDERED list of pairs. * * @param string $key * @return array|null list of pairs */ public function getByKey(string $key) { $sql = 'SELECT first_version, last_version' . ' FROM app A' . ' LEFT JOIN branch B ON A.id=B.app_id' . ' WHERE name = :name'; $stmt = $this->dbh->prepare($sql); $stmt->bindValue('name', $key, SQLITE3_TEXT); $result = $stmt->execute(); $versions = []; while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $versions[] = [$row['first_version'], $row['last_version']]; } return $versions ? $versions : null; } /** * Validate format of the DB. * * @param array $db * @return bool */ private function validate() { try { $result = $this->checkTable('app') && $this->checkTable('branch'); } catch (\Exception $ex) { return false; } return $result; } /** * Opens connection. * * @param string $path */ private function open($path) { $this->dbh = new \SQLite3($path); } /** * Check is there table in DB * * @throws Exception * @return bool */ private function checkTable($table_name) { $sql = 'PRAGMA table_info("' . $table_name . '")'; $stmt = $this->dbh->prepare($sql); $result = $stmt->execute(); return (bool)$result->fetchArray(); } } config)) { throw new ConfigParamException('An invalid option requested. Key: ' . $key); } return $this->config[$key]; } /** * Set value to config by key * * @param string $key * @param mixed $value * @return mixed * @throws Exception */ public function set($key, $value) { $this->config[$key] = $value; } /** * Set default config * * @param array $defaults */ protected function setDefaultConfig($defaults) { $this->config = $defaults; } }add(new \AppVersionDetector\Core\VersionDetection\Detector\DrupalCoreDetector()); $drupalDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\DrupalPluginDetector()); $detector->add($drupalDependencyDetector); // Joomla $joomlaDependencyDetector = new DependencyCollectionDetector(); $joomlaDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\JoomlaCoreDetector()); $joomlaDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\JoomlaPluginDetector()); $detector->add($joomlaDependencyDetector); // WordPress $wpDependencyDetector = new DependencyCollectionDetector(); $wpDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\WpCoreDetector()); $wpDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\WpPluginDetector()); $wpDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\WpThemeDetector()); $detector->add($wpDependencyDetector); // Magento $magentoDependencyDetector = new DependencyCollectionDetector(); $magentoDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\MagentoCoreDetector()); $detector->add($magentoDependencyDetector); // IPB $ipbDependencyDetector = new DependencyCollectionDetector(); $ipbDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\IPBCoreDetector()); $detector->add($ipbDependencyDetector); // MODX $modxDependencyDetector = new DependencyCollectionDetector(); $modxDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\ModxCoreDetector()); $detector->add($modxDependencyDetector); // PHPBB3 $phpbb3DependencyDetector = new DependencyCollectionDetector(); $phpbb3DependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\PHPBB3CoreDetector()); $detector->add($phpbb3DependencyDetector); // OsCommerce $oscomDependencyDetector = new DependencyCollectionDetector(); $oscomDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\OsCommerceCoreDetector()); $detector->add($oscomDependencyDetector); // Bitrix $bitrixDependencyDetector = new DependencyCollectionDetector(); $bitrixDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\BitrixCoreDetector()); $detector->add($bitrixDependencyDetector); // CommonScript $csDetector = new \AppVersionDetector\Core\VersionDetection\Detector\CommonScriptDetector(); $detector->add($csDetector); // OpenCart $opencartDependencyDetector = new DependencyCollectionDetector(); $opencartDependencyDetector->add(new \AppVersionDetector\Core\VersionDetection\Detector\OpenCartCoreDetector()); $detector->add($opencartDependencyDetector); return $detector; } } ['short' => 'h', 'long' => 'help', 'needValue' => false], AVDConfig::PARAM_VERSION => ['short' => 'v', 'long' => 'version,ver', 'needValue' => false], AVDConfig::PARAM_JSON_REPORT => ['short' => '', 'long' => 'json-output,json-report', 'needValue' => true], AVDConfig::PARAM_TEXT_REPORT => ['short' => '', 'long' => 'text-output,text-report', 'needValue' => 0], AVDConfig::PARAM_SQLITE_DB_REPORT => ['short' => '', 'long' => 'sqlite-db-report', 'needValue' => true], AVDConfig::PARAM_SINCE => ['short' => '', 'long' => 'since', 'needValue' => true], AVDConfig::PARAM_STDIN_DIR => ['short' => '', 'long' => 'stdin-dirs', 'needValue' => false], AVDConfig::PARAM_PATHES_IN_BASE64 => ['short' => '', 'long' => 'paths-in-base64,pathes-in-base64', 'needValue' => false], // TODO: Over time we need to remove param "--pathes-in-base64" its typo AVDConfig::PARAM_CHACK_OUTDATED => ['short' => '', 'long' => 'check-outdated', 'needValue' => true], AVDConfig::PARAM_SCAN_DEPTH => ['short' => '', 'long' => 'scan-depth', 'needValue' => true], AVDConfig::PARAM_SEND_STATS => ['short' => '', 'long' => 'send-stats', 'needValue' => false], AVDConfig::PARAM_DEBUG => ['short' => '', 'long' => 'debug', 'needValue' => false], AVDConfig::PARAM_FACTORY_CONFIG => ['short' => '', 'long' => 'factory-config', 'needValue' => true], AVDConfig::PARAM_MIGRATE => ['short' => '', 'long' => 'migrate', 'needValue' => false], ]; /** * Parse comand line params * * @return void * @throws Exception */ protected function parse() { foreach ($this->opts as $configName => $params) { if ($configName == AVDConfig::PARAM_FACTORY_CONFIG) { continue; } $default = $params['needValue'] ? $this->config->get($configName) : null; $result = $this->getParamValue($configName, $default); if (!$params['needValue'] && $result === false) { // $result === false because opt without value $result = true; } $this->config->set($configName, $result); } if ($this->config->get(AVDConfig::PARAM_HELP)) { $this->showHelp(); } elseif ($this->config->get(AVDConfig::PARAM_VERSION)) { $this->showVersion(); } $posArgs = $this->getFreeAgrs(); if ($this->config->get(AVDConfig::PARAM_MIGRATE)) { (new Migraitor)->migrate($this->config); exit(0); } if (count($posArgs) < 1 && !$this->config->get(AVDConfig::PARAM_STDIN_DIR)) { $this->config->set(AVDConfig::PARAM_STDIN_DIR, true); $this->config->set(AVDConfig::PARAM_PATHES_IN_BASE64, true); } if (count($posArgs) > 1) { throw new ConfigParamException('Too many target directories is specified.'); } $dirFromStdin = $this->config->get(AVDConfig::PARAM_STDIN_DIR); $directories = []; if ($dirFromStdin) { $lines = explode("\n", trim(file_get_contents('php://stdin'))); $useDomains = false; if (count($lines) && strpos($lines[0], ',') !== false) { list($domain, $dir) = explode(',', $lines[0], 2); if (strpos($domain, '/') === false) { $useDomains = true; } } if ($useDomains) { foreach ($lines as $line) { list($domain, $dir) = explode(',', $line, 2); $domain = \idn_to_utf8($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46); $directories[] = new Directory($dir, $domain); } } else { foreach ($lines as $line) { $directories[] = new Directory($line); } } unset($lines); } else { $directories[] = new Directory(realpath($posArgs[0])); } if (count($directories) < 1) { throw new ConfigParamException('Target directory is not specified.'); } $pathesInBase64 = $this->config->get(AVDConfig::PARAM_PATHES_IN_BASE64); foreach ($directories as $index => &$directory) { $directory_path = $directory->getPath(); if ($pathesInBase64) { $directory_path = base64_decode($directory_path); $directory->setPath($directory_path); } if (empty($directory_path)) { unset($directories[$index]); continue; } if (!file_exists($directory_path)) { $message = "Directory {$directory_path} does not exist."; if ($dirFromStdin) { unset($directories[$index]); fwrite(STDERR, $message . "\n"); continue; } throw new ConfigParamException($message); } if (!is_dir($directory_path)) { $message = "Looks like {$directory_path} is not a directory."; if ($dirFromStdin) { unset($directories[$index]); fwrite(STDERR, $message . "\n"); continue; } throw new ConfigParamException($message); } } unset($directory); $this->config->set(AVDConfig::PARAM_TARGET_DIRECTORIES, $directories); $factoryConfig = $this->config->get(AVDConfig::PARAM_FACTORY_CONFIG); $jsonReport = $this->config->get(AVDConfig::PARAM_JSON_REPORT); if ($jsonReport) { if ($jsonReport === 'STDOUT') { $this->config->set(AVDConfig::PARAM_JSON_REPORT, 'php://stdout'); } elseif ($jsonReport !== 'php://stdout') { if (file_exists($jsonReport)) { throw new ConfigParamException("File {$jsonReport} already exists."); } $dir = dirname($jsonReport); if (!is_writable($dir)) { throw new ConfigParamException("Directory {$dir} is not writable."); } } } $textReport = $this->config->get(AVDConfig::PARAM_TEXT_REPORT); if ($textReport) { $factoryConfig[AbstractFileBasedReport::class] = TextReport::class; if ($textReport !== true) { if (file_exists($textReport)) { throw new ConfigParamException("File {$textReport} already exists."); } $dir = dirname($textReport); if (!is_writable($dir)) { throw new ConfigParamException("Directory {$dir} is not writable."); } } } if ($this->config->get(AVDConfig::PARAM_SEND_STATS)) { $factoryConfig[RemoteStatsReport::class] = RemoteStatsReport::class; } $scanDepth = $this->config->get(AVDConfig::PARAM_SCAN_DEPTH); if ($scanDepth < 0) { throw new ConfigParamException('Invalid value for the scan-depth option.'); } $sqliteDBeport = $this->config->get(AVDConfig::PARAM_SQLITE_DB_REPORT); if ($sqliteDBeport) { $dirname = dirname($sqliteDBeport); if (file_exists($sqliteDBeport)) { if (!is_writable($sqliteDBeport)) { throw new ConfigParamException("Invalid value for the sqlite-db-report option. File {$sqliteDBeport} is not writable."); } } elseif (!is_writable($dirname)) { throw new ConfigParamException("Invalid value for the sqlite-db-report option. Directory {$dirname} is not writable."); } } if ($this->config->get(AVDConfig::PARAM_DEBUG)) { Stats::onAccumulateStats(); Profiler::onProfiler(); } $factoryConfigFromOpt = $this->getParamValue(AVDConfig::PARAM_FACTORY_CONFIG, false); if ($factoryConfigFromOpt) { if (!file_exists($factoryConfigFromOpt)) { throw new ConfigParamException("Factory config file {$factoryConfigFromOpt} does not exist."); } $customFactoryConfig = require($factoryConfigFromOpt); $factoryConfig = array_merge($factoryConfig, $customFactoryConfig); } $this->config->set(AVDConfig::PARAM_FACTORY_CONFIG, $factoryConfig); } /** * Cli show help * * @return void */ private function showHelp() { echo << Nesting Level for CMS search. Default 1 --json-report= File path with json report --text-report Output in stdout --sqlite-db-report= Path to sqlite database file with report. If an existing database is specified, data is added to it. --since= Used only with --sqlite-db-report, allows you to scan only new folders and those that were scanned before . --paths-in-base64 Base64 encrypted paths --send-stats Send statistics to CH --migrate Starting the migration procedure for this package -v, --version -h, --help HELP; exit(0); } /** * Cli show version * * @return void */ private function showVersion() { die('AppVersionDetector v' . Scanner::VERSION . "\n"); } }path = $path; $this->domain = $domain; } /** * Set directory path * * @param string $path * * @return void */ public function setPath(string $path) { $this->path = $path; } /** * Get directory path * * @return string */ public function getPath(): string { return $this->path; } /** * Set domain name * * @param string $domain * * @return void */ public function setDomain(string $domain) { $this->domain = $domain; } /** * Get domain name * * @return string */ public function getDomain(): string { return $this->domain; } /** * Set Owner id * * @param int $owner * * @return void */ public function setOwner(int $owner) { $this->owner = $owner; } /** * Get owner id * * @return int */ public function getOwner(): int { if (is_null($this->owner)) { $this->owner = fileowner($this->path); } return $this->owner; } }get(AVDConfig::PARAM_SQLITE_DB_REPORT)) { $this->migrateSqliteDB($config->get(AVDConfig::PARAM_SQLITE_DB_REPORT)); } $this->migrateSqliteDB('/var/imunify360/components_versions.sqlite3'); $this->migrateSqliteDB('/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3'); } //////////////////////////////////////////////////////////////////////////// private function migrateSqliteDB($db_filepath) { if (!file_exists($db_filepath)) { return true; } $db_connection = $this->getConnection($db_filepath); if (!$db_connection) { return false; } $this->migrateSqliteDBAppUID($db_connection); $this->migrateSqliteDBAppRealPath($db_connection); $this->migrateSqliteDBAppDomain($db_connection); $this->migrateSqliteDBClearOutdatedData($db_connection); } private function migrateSqliteDBClearOutdatedData($db_connection) { $sql_count_outdated_data = 'SELECT count(*) FROM report WHERE id NOT IN ( SELECT max(id) FROM report GROUP BY dir ) '; if (!$this->getOneValue($db_connection, $sql_count_outdated_data)) { return; } $db_connection->exec('PRAGMA foreign_keys=OFF'); $db_connection->exec('BEGIN'); $db_connection->exec('CREATE TABLE _report AS SELECT * FROM report WHERE id IN ( SELECT max(id) FROM report GROUP BY dir ) '); $db_connection->exec('CREATE TABLE _apps AS SELECT * FROM apps WHERE report_id IN ( SELECT max(id) FROM report GROUP BY dir ) '); $db_connection->exec('DELETE FROM apps'); $db_connection->exec('DELETE FROM report'); $db_connection->exec('INSERT INTO report SELECT * FROM _report'); $db_connection->exec('INSERT INTO apps SELECT * FROM _apps'); $db_connection->exec('DROP TABLE _apps'); $db_connection->exec('DROP TABLE _report'); $db_connection->exec('COMMIT'); $db_connection->exec('PRAGMA foreign_keys=ON'); $db_connection->exec('VACUUM'); } private function migrateSqliteDBAppUID($db_connection) { if (!$this->haveColumn($db_connection, 'apps', 'app_uid')) { $this->addColumn($db_connection, 'apps', 'app_uid INTEGER DEFAULT NULL'); } } private function migrateSqliteDBAppRealPath($db_connection) { if (!$this->haveColumn($db_connection, 'apps', 'real_path')) { $this->addColumn($db_connection, 'apps', 'real_path TEXT DEFAULT NULL'); } } private function migrateSqliteDBAppDomain($db_connection) { if (!$this->haveColumn($db_connection, 'report', 'domain')) { $this->addColumn($db_connection, 'report', 'domain TEXT DEFAULT NULL'); } } private function getConnection($db_filepath) { return new \SQLite3($db_filepath); } private function haveColumn($db_connection, $table_name, $column_name) { $sql = 'PRAGMA table_info("' . $table_name . '")'; $stmt = $db_connection->prepare($sql); $result = $stmt->execute(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) { if ($row['name'] == $column_name) { return true; } } return false; } private function haveTable($db_connection, $table_name) { $sql = 'PRAGMA table_info("' . $table_name . '")'; $stmt = $db_connection->prepare($sql); $result = $stmt->execute(); return (bool)$result->fetchArray(); } private function addColumn($db_connection, $table_name, $column_params) { return @$db_connection->exec('ALTER TABLE ' . $table_name . ' ADD COLUMN ' . $column_params); } private function getOneValue($db_connection, $sql, $default = false) { $results = $db_connection->query($sql); $row = $results->fetchArray(SQLITE3_NUM); if (!$row) { return $default; } return $row[0]; } } 'v', 'long' => 'version,ver', 'needValue' => false] */ protected $opts = []; /** * @var array Current of options from $argv */ private $options = []; /** * @var array Arguments left after getopt() processing */ private $freeAgrs = []; /** * Construct * * @param array $argv * @param Config $config * @throws ConfigParamException */ public function __construct($argv, Config $config) { $this->config = $config; $cliLongOpts = []; $cliShortOpts = []; foreach ($this->opts as $params) { $postfix = $params['needValue'] === 0 ? '::' : ($params['needValue'] ? ':' : ''); if ($params['long']) { $cliLongOpts = array_merge($cliLongOpts, $this->getMultiOpts($params['long'], $postfix)); } if ($params['short']) { $cliShortOpts = array_merge($cliShortOpts, $this->getMultiOpts($params['short'], $postfix)); } } $this->parseOptions($argv, $cliShortOpts, $cliLongOpts); $this->parse(); } /** * Parse comand line params */ abstract protected function parse(); /** * Checking if the parameter was used in the cli line * * @param string $paramKey * @return bool * @throws ConfigParamException */ protected function issetParam($paramKey) { if (!isset($this->opts[$paramKey])) { throw new ConfigParamException('An invalid option requested.'); } if ($this->getExistingOpt($this->opts[$paramKey]['long'])) { return true; } elseif ($this->getExistingOpt($this->opts[$paramKey]['short'])) { return true; } return false; } /** * Checking if the parameter was used in the cli line * * @param string $paramKey * @return bool * @throws ConfigParamException */ protected function getParamValue($paramKey, $default = null) { if (!isset($this->opts[$paramKey])) { throw new ConfigParamException('An invalid option requested.'); } $existingLongOpt = $this->getExistingOpt($this->opts[$paramKey]['long']); if ($existingLongOpt) { return $this->options[$existingLongOpt]; } $existingShortOpt = $this->getExistingOpt($this->opts[$paramKey]['short']); if ($existingShortOpt) { return $this->options[$existingShortOpt]; } return $default; } /** * Return free arguments after using getopt() * * @return array */ protected function getFreeAgrs() { return $this->freeAgrs; } /** * Parse by getopt() and fill vars: $this->options $this->freeAgrs * * @return void */ private function parseOptions($argv, $cliShortOpts, $cliLongOpts) { if (count($argv) <= 1) { return; } $this->options = getopt(implode('', $cliShortOpts), $cliLongOpts); //$this->freeAgrs = array_slice($argv, $optind); // getopt(,,$optind) only for PHP7.1 and upper for($i = 1; $i < count($argv); $i++) { if (strpos($argv[$i], '-') !== 0) { $this->freeAgrs = array_slice($argv, $i); break; } } } /** * Clean cli parameter * * @param string $optName Paramenter may be with ":" postfix * @return array */ private function getCleanOptName($optName) { return str_replace(':', '', $optName); } /** * Return options with or without ":" postfix * * @param array $optString String with one or more options separated by "," * @param bool $addPostfix True if need add postfix * @return array Array list of options */ private function getMultiOpts($optString, $addPostfix = false) { $opts = explode(',', $optString); $opts = array_map(function($value) use ($addPostfix) { return $value . $addPostfix; }, $opts); return $opts; } /** * Return existing options from string. * * @param string $optsString String with one or more options separated by "," * @return string|bool Name of finded options in getopt() */ private function getExistingOpt($optsString) { $opts = $this->getMultiOpts($optsString); foreach ($opts as $opt) { if (isset($this->options[$opt])) { return $opt; } } return false; } }appDie($errno, 'An unknown error occurred!' . ' Error code: [' . $errno . '] Message: ' . $errstr . PHP_EOL); } /** * Handles an exception. * * @param Throwable $exception */ public function handleException(Throwable $exception) { if ($exception instanceOf ConfigParamException) { $message = 'Configuration error: ' . $exception->getMessage() . PHP_EOL; $message .= 'Usage example: php app-version-detector.phar OPTIONS SCAN_DIR' . PHP_EOL; $this->appDie(self::ERRNUM_CONFIG_PARAM_EXCEPTION, $message); } elseif ($exception instanceOf AppException) { $message = 'Application error: ' . $exception->getMessage() . '. Code: ' . $exception->getCode() . PHP_EOL; $this->appDie(self::ERRNUM_APP_EXCEPTION, $message); } else { $this->appDie($exception->getCode(), 'An unknown error occurred! Code: ' . $exception->getCode() . ' Message: ' . $exception->getMessage() . PHP_EOL); } } /** * Die process * * @param int $errno * @param string $errstr */ private function appDie(int $errno, string $errstr) { fwrite(STDERR, $errstr); exit($errno); } }cache) && file_exists($filepath)) { $this->cache[$filepath] = @fileowner($filepath); } return $this->cache[$filepath]; } } false, self::PARAM_VERSION => false, self::PARAM_JSON_REPORT => false, self::PARAM_TEXT_REPORT => false, self::PARAM_SQLITE_DB_REPORT => false, self::PARAM_SINCE => false, self::PARAM_STDIN_DIR => false, self::PARAM_PATHES_IN_BASE64 => false, self::PARAM_CHACK_OUTDATED => true, self::PARAM_SCAN_DEPTH => 1, self::PARAM_SEND_STATS => false, self::PARAM_DB_FILE => __DIR__ . '/../../data/actual-versions-db.json', self::PARAM_DEBUG => false, self::PARAM_FACTORY_CONFIG => [ EventManager::class => EventManager::class, AbstractFileBasedReport::class => JsonReport::class, ActualVersionsDb::class => ActualVersionsDb::class, ActualVersionsSQLiteDb::class => ActualVersionsSQLiteDb::class, RemoteStatsRequest::class => RemoteStatsRequest::class, ComparisonStrategyInterface::class => GenericComparisonStrategy::class, OutdatedChecker::class => OutdatedChecker::class, SqliteDbReportConnection::class => SqliteDbReportConnection::class, SqliteDbReport::class => SqliteDbReport::class, ], self::PARAM_TARGET_DIRECTORIES => [], self::PARAM_MIGRATE => false, ]; /** * Construct */ public function __construct() { $this->setDefaultConfig($this->defaultConfig); } }publisher = $publisher; } /** * Scans a list of directories. * * @param SqliteDbReportConnection $dbReportConnection * @param array $directories * @param DetectorInterface $detector * @param int $depth * * @throws Exception */ public function run($dbReportConnection, $directories, $detector, $depth = 1, $since = false) { if ($depth < 1) { throw new AppException('Value of the $depth parameter can not be less than 1.'); } foreach ($directories as $directory) { if (!is_dir($directory->getPath())) { throw new AppException('Only a directory can be scanned. Problem with "' . $directory->getPath() . '".'); } } $scanId = $this->generateScanId(); $this->publisher->update(new ScanningStartedEvent($this, self::VERSION, $scanId)); foreach ($directories as $directory) { if (!is_null($dbReportConnection) && $since && $dbReportConnection->haveRelevantReport($directory->getPath(), $since)) { continue; } $this->scanDirectory($scanId, $directory, $detector, $depth); } Stats::setCurrentMemoryUsagePeak(); Stats::setCurrentMemoryUsageEnd(); $this->publisher->update(new ScanningEndedEvent($this, $scanId)); } /** * Generates a scan ID. * * @return string */ public function generateScanId(): string { $time = gettimeofday(); return substr($time['sec'] . $time['usec'], -16); } /** * Scans a particular directory. * @param int $scanId * @param Directory $directory * @param DetectorInterface $detector * @param int $depth */ private function scanDirectory($scanId, Directory $directory, $detector, $depth = 1) { $this->publisher->update(new ScanningDirStartedEvent($this, self::VERSION, $directory)); // scan a base directory $detector->perform($this->publisher, new SplFileInfo($directory->getPath())); // scan subdirectories /** @var RecursiveDirectoryIterator|RecursiveIteratorIterator $iterator */ $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $directory->getPath(), FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::SKIP_DOTS ), RecursiveIteratorIterator::SELF_FIRST ); $iterator->setMaxDepth($depth - 1); $files_counter = 0; $iterator->rewind(); while($files_counter <= AbstractDetector::FILES_PER_DIR_LIMIT && $iterator->valid()) { Stats::incrementCountFilesIteration(); $files_counter++; if ($iterator->isDir()) { Stats::incrementCountDirsOpened(); $detector->perform($this->publisher, new SplFileInfo($iterator->key())); } $iterator->next(); } $this->publisher->update(new ScanningDirEndedEvent($this)); } } sender; } /** * Sets an event sender. * * @param object $sender */ protected function setSender($sender) { $this->sender = $sender; } }publisher = $publisher; if (isset($this->detectors[0])) { $this->detectors[0]->perform($this, $splFileInfo); } } /** * Notifies about an event. * * @param AbstractEvent $event */ public function update(AbstractEvent $event) { $this->publisher->update($event); if (!($event instanceof DetectionEvent)) { return; } /** @var $event DetectionEvent */ foreach ($this->detectors as $key => $detector) { if ($key == 0) { continue; } $detector->perform($this->publisher, $event->getPath()); } } }setSender($sender); $this->name = $name; $this->version = $version; $this->path = $path; } /** * @return string */ public function getName() { return $this->name; } /** * @return int */ public function getUID() { if (is_null($this->uid) && file_exists($this->path) && !is_null(self::$fileOwners)) { $this->uid = self::$fileOwners->getFileOwner((string)$this->path); } return $this->uid; } /** * @return string */ public function getVersion() { return $this->version; } /** * @param string $version */ public function setVersion($version) { $this->version = $version; } /** * @return SplFileInfo */ public function getPath(): SplFileInfo { return $this->path; } } getPathname(); $detected = false; $detectionEvent = new DetectionEvent($this, $splFileInfo, $this->detector_name, DetectorInterface::UNKNOWN_VERSION); if ($this->isOsCom2x($dir)) { $detected = true; $tmp_ver = Helper::tryGetVersion($dir . '/includes/version.php', 1024, '~(.*)~'); if ($tmp_ver !== '') { $version = $tmp_ver[1]; $detectionEvent->setVersion($version); } else { $tmp_ver = Helper::tryGetVersion($dir . '/includes/application_top.php', 4096, '~define\(\'PROJECT_VERSION\',\s*\'[^\d]+([\w\.-]+)\'\);~msi'); if ($tmp_ver !== '') { $version = $tmp_ver[1]; $detectionEvent->setVersion($version); } } } else if ($this->isOsCom3x($dir)) { $detected = true; $tmp_ver = Helper::tryGetVersion($dir . '/osCommerce/OM/version.txt', 1024, '~(.*)~'); if ($tmp_ver !== '') { $version = $tmp_ver[1]; $detectionEvent->setVersion($version); } } return $detected ? $detectionEvent : null; } /** * Check is OsCommerce 2x * * @param string $dir * @return boolean */ private function isOsCom2x($dir) { if (file_exists($dir . '/includes/configure.php') && (file_exists($dir . '/includes/boxes/shopping_cart.php') || file_exists($dir . '/includes/classes/osc_template.php')) ) { return true; } return false; } /** * Check is OsCommerce 3x * * @param string $dir * @return boolean */ private function isOsCom3x($dir) { if (file_exists($dir . '/osCommerce/OM/Core/Site/Shop/Application/Index/Action/Manufacturers.php') && file_exists($dir . '/osCommerce/OM/Config/settings.ini') ) { return true; } return false; } }getPathname(); $detected = false; $detectionEvent = new DetectionEvent($this, $splFileInfo, $this->detector_name, DetectorInterface::UNKNOWN_VERSION); if (file_exists($dir . '/catalog') && file_exists($dir . '/system') && file_exists($dir . '/admin') && file_exists($dir . '/admin/config.php') && file_exists($dir . '/image') && file_exists($dir . '/index.php') && file_exists($dir . '/config.php') ) { $detected = true; $filepath = $dir . '/index.php'; if (is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 1024); if (preg_match('~define\(\'VERSION\',\s*\'(.+?)\'~smi', $tmp_content, $tmp_ver)) { $detectionEvent->setVersion($tmp_ver[1]); } } } return $detected ? $detectionEvent : null; } }getPathname(); $detected = false; $detectionEvent = new DetectionEvent($this, $splFileInfo, $this->detector_name, DetectorInterface::UNKNOWN_VERSION); $drupal_filepath = $dir . '/core/lib/Drupal.php'; if (file_exists($drupal_filepath) && is_file($drupal_filepath)) { $detected = true; Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($drupal_filepath, 8192); if (preg_match('|VERSION\s*=\s*\'(\d+\.\d+\.\d+)\'|smi', $tmp_content, $tmp_ver)) { $detectionEvent->setVersion($tmp_ver[1]); return $detectionEvent; } } if (file_exists($dir . '/sites/all') || file_exists($dir . '/sites/default') || file_exists($dir . '/modules/system.module') ) { $detected = true; $tmp_content = ''; $possibleFiles = [ $dir . '/CHANGELOG.txt', $dir . '/CHANGELOG', ]; foreach ($possibleFiles as $filepath) { if (!file_exists($filepath) || !is_file($filepath)) { continue; } Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); break; } if ($tmp_content && preg_match('~Drupal\s+(\d+\.\d+[\d.]+)~smi', $tmp_content, $tmp_ver)) { $detectionEvent->setVersion($tmp_ver[1]); return $detectionEvent; } } $systeminfo_filepath = $dir . '/modules/system/system.info'; if (file_exists($systeminfo_filepath) && is_file($systeminfo_filepath)) { $detected = true; Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($systeminfo_filepath, 8192); if (preg_match('|version\s*=\s*"(\d+\.\d+)"|smi', $tmp_content, $tmp_ver)) { $detectionEvent->setVersion($tmp_ver[1]); return $detectionEvent; } } return $detected ? $detectionEvent : null; } }version = $version; } /** * Implements finding and extracting info(version and name). * * If something is found it return AppVersionDetector\Core\Detector\Version\DetectionEventData. * If no luck it returns null. * * @param SplFileInfo $splFileInfo * @return DetectionEvent|null * @throws \Exception */ protected function findAndExtractData(SplFileInfo $splFileInfo) { if (is_null($this->version)) { return null; } return new DetectionEvent($this, new SplFileInfo(__DIR__), $this->detector_name, $this->version); } }getPathname(); $files['phpmailer'] = [ 'phpmailer.php', 'php-mailer.php', 'PHPMailer.php', 'PhpMailer.php', 'PHP-Mailer.php', 'Php-Mailer.php', ]; $files['timthumb'] = [ 'timthumb.php', 'thumb.php', 'Thumb.php', 'TimThumb.php', 'tim-thumb.php', ]; $files['phpMyAdmin'] = [ 'libraries/Config.class.php', 'libraries/classes/Config.php', 'libraries/classes/Version.php', ]; $data = []; $data = $this->checkAndReadPHPMailerVersion($dir, $files['phpmailer']); $data = array_merge($data, $this->checkAndReadTimThumbVersion($dir, $files['timthumb'])); $data = array_merge($data, $this->checkAndReadPhpMyAdminVersion($dir, $files['phpMyAdmin'])); return $data; } private function checkAndReadPHPMailerVersion($dir, $files) { $data = []; foreach ($files as $file) { $filepath = $dir . '/' . $file; if (!file_exists($filepath) || !is_readable($filepath) || !is_file($filepath)) { continue; } Stats::incrementCountFilesReaded(); $content = Helper::getPartOfFile($filepath, 32768); $version = ''; if (strpos($content, 'PHPMailer') === false) { continue; } $l_Found = preg_match('~Version:(\s*\d+\.\d+\.\d+)~', $content, $l_Match); if ($l_Found) { $version = $l_Match[1]; } if (!$l_Found) { $l_Found = preg_match('~Version\s*=\s*\'(\d+\.\d+\.\d+)~i', $content, $l_Match); if ($l_Found) { $version = $l_Match[1]; } } $data[] = new DetectionEvent( $this, new SplFileInfo($filepath), $this->prefix . 'phpmailer', $version ); } return $data; } private function checkAndReadTimThumbVersion($dir, $files) { $data = []; foreach ($files as $file) { $filepath = $dir . '/' . $file; if (!file_exists($filepath) || !is_readable($filepath) || !is_file($filepath)) { continue; } Stats::incrementCountFilesReaded(); $content = Helper::getPartOfFile($filepath, 8192); $version = ''; if (strpos($content, 'code.google.com/p/timthumb') === false) { continue; } $l_Found = preg_match('~define\s*\(\'version\'\s*,\s*\'([^\']+)\'\s*\);~i', $content, $l_Match); if ($l_Found) { $version = $l_Match[1]; } $data[] = new DetectionEvent( $this, new SplFileInfo($filepath), $this->prefix . 'timthumb', $version ); } return $data; } private function checkAndReadPhpMyAdminVersion($dir, $files) { $data = []; foreach ($files as $file) { $filepath = $dir . '/' . $file; if (!file_exists($filepath) || !is_readable($filepath) || !is_file($filepath)) { continue; } Stats::incrementCountFilesReaded(); $content = Helper::getPartOfFile($filepath, 8192); $version = ''; if (strpos($content, 'PMA_VERSION') !== false && preg_match('~\$this->set\(\'PMA_VERSION\'\s*,\s*\'([^\']+)\'\);~i', $content, $l_Match) ) { $version = $l_Match[1]; } elseif (strpos($content, 'final class Version') !== false && strpos($content, 'namespace PhpMyAdmin;') !== false && preg_match('~public\s*const\s*VERSION\s*=\s*\'([^\']+)\'\s*.\s*VERSION_SUFFIX;~i', $content, $l_Match) ) { $version = $l_Match[1]; } if ($version !== '') { $data[] = new DetectionEvent( $this, new SplFileInfo($dir), $this->prefix . 'phpmyadmin', $version ); } } return $data; } } getPathname(); if (!$this->isWpBase($dir)) { return null; } $components_data = []; $components = $this->wpComponentList($dir . '/wp-content/' . $this->components_folder); foreach ($components as $component => $data) { $components_data[] = new DetectionEvent($this, $splFileInfo, $this->prefix . $component, $data['Version']); } return $components_data; } /** * Check is the folder is root of WordPress site. * * @param string $dir * @return boolean */ private function isWpBase($dir) { return (bool)is_dir($dir . '/wp-content/' . $this->components_folder); } private function wpComponentList($dir) { $wp_components = []; $component_root = $dir; if (!is_dir($component_root)) { return $wp_components; } Stats::incrementCountDirsOpened(); $components_dir = @opendir($component_root); $component_files = []; if (!$components_dir) { return $wp_components; } $components_dir_file_counter = 0; while ($components_dir_file_counter <= AbstractDetector::FILES_PER_DIR_LIMIT && ($file = readdir($components_dir)) !== false) { Stats::incrementCountFilesIteration(); $components_dir_file_counter++; if (substr($file, 0, 1) == '.') { continue; } $component_dirpath = $component_root . '/' . $file; if (is_dir($component_dirpath)) { Stats::incrementCountDirsOpened(); $components_subdir = @opendir($component_dirpath); if (!$components_subdir) { continue; } $components_subdir_file_counter = 0; while ($components_subdir_file_counter <= AbstractDetector::FILES_PER_DIR_LIMIT && ($subfile = readdir($components_subdir)) !== false) { Stats::incrementCountFilesIteration(); $components_subdir_file_counter++; if (substr($subfile, 0, 1) == '.') { continue; } if ($this->isMainComponentFile($subfile)) { $component_files[] = "$file/$subfile"; } } closedir($components_subdir); } else { if ($this->isMainComponentFile($file)) { $component_files[] = $file; } } } closedir($components_dir); if (empty($component_files)) { return $wp_components; } foreach ($component_files as $component_file) { $component_filepath = $component_root . '/' . $component_file; if (!is_readable($component_filepath) || !is_file($component_filepath)) { continue; } $component_data = $this->getFileData($component_filepath); if (empty($component_data['Name'])) { continue; } if (empty($component_data['Version'])) { $component_data['Version'] = DetectorInterface::UNKNOWN_VERSION; } $package_name = explode('/', $component_file); $package = array_shift($package_name); $package = str_replace('.php', '', $package); $package = preg_replace('~[^a-z0-9]~is', '_', $package); $wp_components[$package] = $component_data; } return $wp_components; } abstract protected function isMainComponentFile($filename); private function getFileData($file) { $all_headers = [ 'Name' => $this->component_name . ' Name', 'Version' => 'Version' ]; Stats::incrementCountFilesReaded(); $file_data = Helper::getPartOfFile($file, 8192); $file_data = str_replace("\r", "\n", $file_data); foreach ($all_headers as $field => $regex) { if (preg_match('/^[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi', $file_data, $match) && $match[1]) { $all_headers[$field] = $this->cleanupHeaderComment($match[1]); } else { $all_headers[$field] = ''; } } return $all_headers; } private function cleanupHeaderComment($str) { return trim(preg_replace('/\s*(?:\*\/|\?>).*/', '', $str)); } } getPathname(); $detected = false; $detectionEvent = new DetectionEvent($this, $splFileInfo, $this->detector_name, DetectorInterface::UNKNOWN_VERSION); if (file_exists($dir . '/wp-admin/admin-functions.php')) { $detected = true; $filepath = $dir . '/wp-includes/version.php'; if (file_exists($filepath) && is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); if (preg_match('|\$wp_version\s*=\s*\'(.+?)\'|smi', $tmp_content, $tmp_ver)) { $detectionEvent->setVersion($tmp_ver[1]); } } } return $detected ? $detectionEvent : null; } }getPathname(); if (!$this->isJoomla($dir)) { return null; } $result = []; $cmsVersion = $this->checkCmsVersion($dir); if ($cmsVersion !== null) { $result[] = new DetectionEvent($this, $splFileInfo,self::JOOMLA_CMS_DETECTOR_NAME, $cmsVersion); } $platformVersion = $this->checkPlatformVersion($dir); if ($platformVersion !== null) { $result[] = new DetectionEvent($this, $splFileInfo,self::JOOMLA_PLATFORM_DETECTOR_NAME, $platformVersion); } return empty($result) ? new DetectionEvent($this, $splFileInfo,self::JOOMLA_CMS_DETECTOR_NAME, DetectorInterface::UNKNOWN_VERSION) : $result; } /** * Tries to get a version of Joomla CMS. * * @param $dir * @return string|null */ private function checkCmsVersion($dir) { // for 1.0.x $filepath = $dir . '/includes/version.php'; if (file_exists($filepath) && is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); if (preg_match('|var\s+\$RELEASE\s*=\s*\'(.+?)\'|smi', $tmp_content, $tmp_ver)) { $version = $tmp_ver[1]; if (preg_match('|var\s+\$DEV_LEVEL\s*=\s*\'(.+?)\'|smi', $tmp_content, $tmp_ver)) { $version .= '.' . $tmp_ver[1]; } return $version; } } // for 1.5.x $filepath = $dir . '/CHANGELOG.php'; if (file_exists($filepath) && is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); if (preg_match('~-------------------- (\d+\.\d+\.\d+) ~smi', $tmp_content, $tmp_ver)) { return $tmp_ver[1]; } } // for 2.5.x $filepath = $dir . '/joomla.xml'; if (file_exists($filepath) && is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); if (preg_match('~([\d.\-_a-z]+)~smi', $tmp_content, $tmp_ver)) { return $tmp_ver[1]; } } // for 3.x $filepath = $dir . '/administrator/manifests/files/joomla.xml'; if (file_exists($filepath) && is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); if (preg_match('~([\d.\-_a-z]+)~smi', $tmp_content, $tmp_ver)) { return $tmp_ver[1]; } } return null; } /** * * Check what is Joomla * * @param string $dir * @return boolean */ private function isJoomla($dir) { if (file_exists($dir . '/libraries/joomla') || file_exists($dir . '/includes/joomla.php')) { return true; } return false; } /** * Tries to get a version of Joomla Platform. * * @param string $dir * @return string|null */ private function checkPlatformVersion(string $dir) { // for Joomla Platform $filepath = $dir . '/libraries/platform.php'; if (!file_exists($filepath) || !is_file($filepath)) { return null; } Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); if (preg_match('~const\s+RELEASE\s+=\s+\'([\d.\-_a-z]+)\'~smi', $tmp_content, $tmp_ver)) { return $tmp_ver[1]; } return null; } }pluginsData = []; $this->pluginsFound = []; $foldePath = $splFileInfo->getPathname(); $this->searchInComponentFolder($foldePath . '/administrator/components'); $this->searchInManifest($foldePath . '/administrator/manifests/packages'); $this->searchInPluginFolder($foldePath . '/plugins'); if (!$this->pluginsData) { return null; } return array_map(function ($item) use ($splFileInfo) { return new DetectionEvent($this, $splFileInfo, $item[0], $item[1]); }, $this->pluginsData); } private function searchInComponentFolder($componentsFolder) { if (!file_exists($componentsFolder)) { return; } Stats::incrementCountDirsOpened(); $dir = dir($componentsFolder); while (false !== ($entry = $dir->read())) { Stats::incrementCountFilesIteration(); if ($entry == '.' || $entry == '..') { continue; } if (substr($entry, 0, 4) != 'com_') { continue; } $componentFolder = $componentsFolder . '/' . $entry; if (!is_dir($componentFolder)) { continue; } $pluginName = substr($entry, 4); $pluginMetaFilepath = $componentFolder . '/' . $pluginName . '.xml'; if (!file_exists($pluginMetaFilepath)) { $pluginName = $entry; $pluginMetaFilepath = $componentFolder . '/' . $entry . '.xml'; if (!file_exists($pluginMetaFilepath)) { continue; } } if (!is_file($pluginMetaFilepath)) { continue; } Stats::incrementCountFilesReaded(); $content = Helper::getPartOfFile($pluginMetaFilepath, 8192); $version = $this->getVersionFromXMLContent($content); $this->addDetector($pluginName, $version); } $dir->close(); } private function searchInManifest($manifestFolder) { if (!file_exists($manifestFolder)) { return; } Stats::incrementCountDirsOpened(); $dir = dir($manifestFolder); while (false !== ($entry = $dir->read())) { if ($entry == '.' || $entry == '..') { continue; } $filepath = $manifestFolder . '/' . $entry; if (!is_file($filepath) || strtolower(substr($filepath, -4)) != '.xml') { continue; } Stats::incrementCountFilesReaded(); $content = Helper::getPartOfFile($filepath, 8192); $pluginName = $this->getPluginNameFromXMLContent($content); if (!$pluginName) { continue; } $version = $this->getVersionFromXMLContent($content); $this->addDetector($pluginName, $version); } $dir->close(); } private function searchInPluginFolder($pluginsFolder) { if (!file_exists($pluginsFolder)) { return; } Stats::incrementCountDirsOpened(); $dir = dir($pluginsFolder); while (false !== ($entry = $dir->read())) { if ($entry == '.' || $entry == '..') { continue; } $filepath = $pluginsFolder . '/' . $entry; if (!is_dir($filepath)) { continue; } $xml_filepath = $filepath . '/' . $entry . '.xml'; if ($this->detectPluginFromXml($entry, $xml_filepath)) { continue; } Stats::incrementCountDirsOpened(); $subDir = dir($filepath); while (false !== ($subEntry = $subDir->read())) { if ($subEntry == '.' || $subEntry == '..') { continue; } $subFilepath = $filepath . '/' . $subEntry; if (!is_dir($subFilepath)) { continue; } $xmlFilepath = $subFilepath . '/' . $subEntry . '.xml'; $this->detectPluginFromXml($subEntry, $xmlFilepath, $entry); } $subDir->close(); } $dir->close(); } private function detectPluginFromXml($name, $xmlFilepath, $subDir = '') { if (!file_exists($xmlFilepath) || !is_file($xmlFilepath)) { return false; } if ($subDir != '') { $name = $subDir . '_' . $name; } Stats::incrementCountFilesReaded(); $content = Helper::getPartOfFile($xmlFilepath, 8192); $version = $this->getVersionFromXMLContent($content); $this->addDetector($name, $version, $subDir); return true; } private function getVersionFromXMLContent($content) { $version = DetectorInterface::UNKNOWN_VERSION; if (preg_match('~]*>(.*?)~is', $content, $m)) { $version = $m[1]; } return $version; } private function getPluginNameFromXMLContent($content) { if (preg_match('~(.*?)~is', $content, $m)) { return $m[1]; } return ''; } private function addDetector($pluginName, $version, $subPluginName = '') { $pluginName = preg_replace('~[^a-zA-Z0-9\-]+~', '_', $pluginName); if (substr($pluginName, 0, 4) == 'com_') { $pluginName = substr($pluginName, 4); } $pluginNameWithoutPrefix = $pluginName; if ($subPluginName) { $pluginPrefix = $subPluginName . '_'; if (substr($pluginName, 0, strlen($pluginPrefix)) == $pluginPrefix) { $pluginNameWithoutPrefix = substr($pluginName, strlen($pluginPrefix)); } } if (empty($subPluginName)) { $this->pluginsFound[$pluginName] = 1; } elseif(isset($this->pluginsFound[$subPluginName])) { return; } elseif(isset($this->pluginsFound[$pluginNameWithoutPrefix])) { return; } $key = $pluginName . $version; if (isset($this->plugins_data[$key])) { return; } $this->pluginsData[$key] = [$this->prefix . $pluginName, $version]; } }getPathname(); if (!$this->isDrupalBase($dir)) { return null; } $plugins_data = []; $plugins = array_merge( $this->drupalPluginList($dir . '/modules'), $this->drupalPluginList($dir . '/core/modules'), $this->drupalPluginList($dir . '/sites/all/modules') ); foreach ($plugins as $plugin => $data) { $plugins_data[] = new DetectionEvent($this, $splFileInfo, $this->prefix . $plugin, $data['Version']); } return $plugins_data; } /** * * Check what is Drupal * * @param string $dir * @return boolean */ private function isDrupalBase($dir) { if (is_dir($dir . '/modules')) { return true; } return false; } private function drupalPluginList($dir) { $drupal_plugins = []; $plugin_root = $dir; if (!is_dir($plugin_root)) { return $drupal_plugins; } // Files in modules directory. Stats::incrementCountDirsOpened(); $plugins_dir = @opendir($plugin_root); $plugin_files = []; if ($plugins_dir) { $plugins_dir_file_counter = 0; while ($plugins_dir_file_counter <= AbstractDetector::FILES_PER_DIR_LIMIT && ($file = readdir($plugins_dir)) !== false) { Stats::incrementCountFilesIteration(); $plugins_dir_file_counter++; if (substr($file, 0, 1) == '.') { continue; } $plugins_subdirpath = $plugin_root . '/' . $file; if (is_dir($plugins_subdirpath)) { Stats::incrementCountDirsOpened(); $plugins_subdir = @opendir($plugins_subdirpath); if ($plugins_subdir) { $plugins_subdir_file_counter = 0; while ($plugins_subdir_file_counter <= AbstractDetector::FILES_PER_DIR_LIMIT && ($subfile = readdir($plugins_subdir)) !== false) { Stats::incrementCountFilesIteration(); $plugins_subdir_file_counter++; if (substr($subfile, 0, 1) == '.') { continue; } if (substr($subfile, -5) == '.info') { $plugin_files[] = "$file/$subfile"; } elseif (substr($subfile, -9) == '.info.yml') { $plugin_files[] = "$file/$subfile"; } } closedir($plugins_subdir); } } else { if (substr($file, -7) == '.module') { $plugin_files[] = $file; } } } closedir($plugins_dir); } if (empty($plugin_files)) { return $drupal_plugins; } foreach ($plugin_files as $plugin_file) { $filepath = $plugin_root . '/' . $plugin_file; if (!is_readable($filepath) || !is_file($filepath)) { continue; } $plugin_data = $this->getFileData($filepath); if (empty($plugin_data['Name'])) { continue; } elseif (empty($plugin_data['Version'])) { $plugin_data['Version'] = DetectorInterface::UNKNOWN_VERSION; } $package = $plugin_data['Name']; $drupal_plugins[$package] = $plugin_data; } return $drupal_plugins; } private function getFileData($file) { $plugin_name = $this->getPluginName($file); $re = '@^\s* # Start at the beginning of a line, ignoring leading whitespace ((?: [^=:#;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets, \[[^\[\]]*\] # unless they are balanced and not nested )+?) \s*[=:]\s* # Key/value pairs are separated by equal signs (ignoring white-space) (?: "((?:[^"]|(?<=\\\\\\\\)")*)"| # Double-quoted string, which may contain slash-escaped quotes/slashes \'((?:[^\']|(?<=\\\\\\\\)\')*)\'| # Single-quoted string, which may contain slash-escaped quotes/slashes ([^\r\n]*?) # Non-quoted string )\s*$ # Stop at the next end of a line, ignoring trailing whitespace @msx'; if (substr($file, -7) == '.module') { return [ 'Name' => $plugin_name, 'Version' => DetectorInterface::UNKNOWN_VERSION ]; } else { $info = []; Stats::incrementCountFilesReaded(); $file_data = Helper::getPartOfFile($file, 8192); preg_match_all($re, $file_data, $matches, PREG_SET_ORDER); foreach ($matches as $prop) { if (isset($prop[2])) { $info[$prop[1]] = $prop[2]; } if (isset($prop[3])) { $info[$prop[1]] = $prop[3]; } if (isset($prop[4])) { $info[$prop[1]] = $prop[4]; } } if (!isset($info['version'])) { $info['version'] = DetectorInterface::UNKNOWN_VERSION; } return [ 'Name' => $plugin_name, 'Version' => $info['version'] ]; } } private function getPluginName($file) { if (substr($file, -7) == '.module') { $name = explode('/', substr($file, 0, -7)); return array_pop($name); } else { $name = explode('/', $file); return $name[sizeof($name) - 2]; } } } getPathname(); $detected = false; $detectionEvent = new DetectionEvent($this, $splFileInfo, $this->detector_name, DetectorInterface::UNKNOWN_VERSION); if ($this->isIPB1x2x($dir)) { $detected = true; $tmp_ver = Helper::tryGetVersion($dir . '/index.php', 8192, '~var\s*\$version\s*=\s*["\']([^\'"]+)["\'];~'); if ($tmp_ver !== '') { $version = str_replace('v', '', $tmp_ver[1]); $version = explode(' ', $version); $version = $version[0]; $detectionEvent->setVersion($version); } } else if ($this->isIPB3x($dir)) { $detected = true; $tmp_ver = Helper::tryGetVersion($dir . '/admin/applications/core/xml/versions.xml', 100, '~(?:\s*\s*([^<]+)\s*\d+\s*\s*)+~msi', -100); if ($tmp_ver !== '') { $version = $tmp_ver[1]; $detectionEvent->setVersion($version); } } else if ($this->isIPB4x($dir)) { $detected = true; $tmp_ver = Helper::tryGetVersion($dir . '/applications/core/data/versions.json', 100, '~(?:"\d+":\s*"([^"]+)",?\s*)+~msi', -100); if ($tmp_ver !== '') { $version = $tmp_ver[1]; $detectionEvent->setVersion($version); } } return $detected ? $detectionEvent : null; } /** * Check is IPB 1x-2x * * @param string $dir * @return boolean */ private function isIPB1x2x($dir) { if (file_exists($dir . '/ipchat.php') && file_exists($dir . '/modules/ipb_member_sync.php') && file_exists($dir . '/sources') ) { return true; } return false; } /** * Check is IPB 3x * * @param string $dir * @return boolean */ private function isIPB3x($dir) { if (file_exists($dir . '/initdata.php') && file_exists($dir . '/interface/ips.php') && file_exists($dir . '/hooks') ) { return true; } return false; } /** * Check is IPB 4x * * @param string $dir * @return boolean */ private function isIPB4x($dir) { if (file_exists($dir . '/init.php') && file_exists($dir . '/applications/core/interface') && file_exists($dir . '/system') ) { return true; } return false; } }getPathname(); if (!$this->isPHPBB3($dir)) { return null; } $result = []; $cmsVersion = $this->checkPHPBB3Version($dir); if ($cmsVersion !== null) { $result[] = new DetectionEvent($this, $splFileInfo,self::PHPBB3_CMS_DETECTOR_NAME, $cmsVersion); } return empty($result) ? new DetectionEvent($this, $splFileInfo,self::PHPBB3_CMS_DETECTOR_NAME, DetectorInterface::UNKNOWN_VERSION) : $result; } /** * Tries to get a version of PHPBB3 CMS. * * @param $dir * @return string|null */ private function checkPHPBB3Version($dir) { $tmp_ver = Helper::tryGetVersion($dir . '/install/schemas/schema_data.sql', 32000, '~INSERT\s*INTO\s*phpbb_config\s*\(config_name,\s*config_value\)\s*VALUES\s*\(\'version\',\s*\'([^\']+)\'\);~msi'); if ($tmp_ver !== '') { return $tmp_ver[1]; } $tmp_ver = Helper::tryGetVersion($dir . '/styles/prosilver/style.cfg', 2048, '~\b(?:phpbb_)?version\s*=\s*([^\s]+)~msi'); if ($tmp_ver !== '') { return $tmp_ver[1]; } $tmp_ver = Helper::tryGetVersion($dir . '/styles/prosilver/composer.json', 2048, '~"phpbb-version":\s*"([^"]+)",~msi'); if ($tmp_ver !== '') { return $tmp_ver[1]; } return null; } /** * * Check what is PHPBB3 * * @param string $dir * @return boolean */ private function isPHPBB3($dir) { if (file_exists($dir . '/includes/ucp/info/ucp_main.php') && file_exists($dir . '/adm/style/acp_bbcodes.html') && file_exists($dir . '/ucp.php')) { return true; } return false; } }getPathname(); if (!$this->isModx($dir)) { return null; } $result = []; $cmsVersion = $this->checkModxEvoVersion($dir); if ($cmsVersion !== null) { $result[] = new DetectionEvent($this, $splFileInfo,self::MODXEVO_CMS_DETECTOR_NAME, $cmsVersion); } $cmsVersion = $this->checkModxRevoVersion($dir); if ($cmsVersion !== null) { $result[] = new DetectionEvent($this, $splFileInfo,self::MODXREVO_CMS_DETECTOR_NAME, $cmsVersion); } return empty($result) ? null : $result; } /** * Tries to get a version of ModxRevo CMS. * * @param $dir * @return string|null */ private function checkModxRevoVersion($dir) { $filepath = $dir . '/core/docs/version.inc.php'; $revo_re = '~\$\w+\[\'version\'\]=\s*\'(\d+)\';\s*(?://\s*[^\n]+)?\s*\$\w+\[\'major_version\'\]=\s*\'(\d+)\';\s*(?://\s*[^\n]+)?\s*\$\w+\[\'minor_version\'\]=\s*\'(\d+)\';\s*(?://\s*[^\n]+)?\s*\$\w+\[\'patch_level\'\]=\s*\'(\w+)\';~msi'; $tmp_ver = Helper::tryGetVersion($filepath, 2048, $revo_re); if ($tmp_ver !== '') { $version = $tmp_ver[1] . '.' . $tmp_ver[2] . '.' . $tmp_ver[3] . '-' . $tmp_ver[4]; return $version; } return null; } /** * Tries to get a version of ModxRevo CMS. * * @param $dir * @return string|null */ private function checkModxEvoVersion($dir) { $tmp_ver = Helper::tryGetVersion($dir . '/core/factory/version.php', 2048, '~\'version\'\s*=>\s*\'([^\']+)\',~msi'); if ($tmp_ver !== '') { return $tmp_ver[1]; } $tmp_ver = Helper::tryGetVersion($dir . '/manager/includes/version.inc.php', 2048, '~version\s*=\s*\'([^\']+)\';\s*~msi'); if ($tmp_ver !== '') { return $tmp_ver[1]; } return null; } /** * * Check what is Modx * * @param string $dir * @return boolean */ private function isModx($dir) { if (file_exists($dir . '/core/model/modx/modx.class.php') || file_exists($dir . '/manager/processors/save_tmplvars.processor.php')) { return true; } return false; } }getPathname(); $detected = false; $detectionEvent = new DetectionEvent($this, $splFileInfo, $this->detector_name, DetectorInterface::UNKNOWN_VERSION); if ($this->isMagento1x($dir)) { $detected = true; $filepath = $dir . '/app/Mage.php'; if (file_exists($filepath) && is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 8192); if (preg_match('~return\s*array\(\s*\'major\'\s*=>\s*\'(\d*)\',\s*\'minor\'\s*=>\s*\'(\d*)\',\s*\'revision\'\s*=>\s*\'(\d*)\',\s*\'patch\'\s*=>\s*\'(\d*)\',\s*\'stability\'\s*=>\s*\'(\d*)\',\s*\'number\'\s*=>\s*\'(\d*)\',\s*~ms', $tmp_content, $tmp_ver)) { $version = trim("{$tmp_ver[1]}.{$tmp_ver[2]}.{$tmp_ver[3]}" . ($tmp_ver[4] != '' ? ".{$tmp_ver[4]}" : "") . "-{$tmp_ver[5]}{$tmp_ver[6]}", '.-'); $detectionEvent->setVersion($version); } } } else if ($this->isMagento2x($dir)) { $detected = true; $filepath = $dir . '/composer.json'; if (file_exists($filepath) && is_file($filepath)) { Stats::incrementCountFilesReaded(); $tmp_content = Helper::getPartOfFile($filepath, 512); if (preg_match('~"version":\s*"([^"]+)",~ms', $tmp_content, $tmp_ver)) { $version = $tmp_ver[1]; $detectionEvent->setVersion($version); } } } return $detected ? $detectionEvent : null; } /** * * Check is Magento1x * * @param string $dir * @return boolean */ private function isMagento1x($dir) { if (file_exists($dir . '/app/Mage.php') && file_exists($dir . '/lib/Magento')) { return true; } return false; } /** * * Check is Magento2x * * @param string $dir * @return boolean */ private function isMagento2x($dir) { if (file_exists($dir . '/app/etc/di.xml') && file_exists($dir . '/bin/magento') && file_exists($dir . '/composer.json') ) { return true; } return false; } }getPathname(); if (!$this->isBitrix($dir)) { return null; } $result = []; $cmsVersion = $this->checkBitrixVersion($dir); if ($cmsVersion !== null) { $result[] = new DetectionEvent($this, $splFileInfo,self::BITRIX_CMS_DETECTOR_NAME, $cmsVersion); } return empty($result) ? new DetectionEvent($this, $splFileInfo,self::BITRIX_CMS_DETECTOR_NAME, DetectorInterface::UNKNOWN_VERSION) : $result; } /** * Tries to get a version of Bitrix CMS. * * @param $dir * @return string|null */ private function checkBitrixVersion($dir) { $tmp_ver = Helper::tryGetVersion($dir . '/bitrix/modules/main/classes/general/version.php', 1024, '~define\("SM_VERSION",\s*"([^"]+)"\);~msi'); if ($tmp_ver !== '') { return $tmp_ver[1]; } return null; } /** * * Check what is Bitrix * * @param string $dir * @return boolean */ private function isBitrix($dir) { if (file_exists($dir . '/bitrix/modules/main/start.php') && file_exists($dir . '/bitrix/modules/main/bx_root.php')) { return true; } return false; } }findAndExtractData($splFileInfo); if (is_null($data)) { return; } $result = []; if (is_array($data)) { $result = $data; } else { $result[] = $data; } foreach ($result as $notify_data) { if (!$notify_data instanceof DetectionEvent) { throw new AppException('Wrong class in list!'); } $publisher->update($notify_data); } } /** * Implements finding and extracting info(version and name). * * If something is found it returns AppVersionDetector\Core\Detector\Version\DetectionEventData. * If no luck it returns null. * * @param SplFileInfo $splFileInfo * @return DetectionEvent|array|null */ abstract protected function findAndExtractData(SplFileInfo $splFileInfo); }detectors[] = $detector; } }detectors as $detector) { $detector->perform($publisher, $splFileInfo); } } }setSender($sender); $this->name = $name; $this->version = $version; $this->type = $type; } /** * Returns name of an outdated component. * * @return string */ public function getName() { return $this->name; } /** * Returns version of an outdated component. * * @return string */ public function getVersion() { return $this->version; } /** * Returns type of an outdated event. * * @return string */ public function getType(): string { return $this->type; } }normalize($versionParser, $left); $right = $this->normalize($versionParser, $right); $value = $this->normalize($versionParser, $value); switch (true) { case Comparator::equalTo($value, $right): return self::EQUALS_TO_RIGHT; break; case Comparator::greaterThanOrEqualTo($value, $left) && Comparator::lessThan($value, $right): return self::IN_RANGE_BUT_LOWER_THAN_RIGHT; break; default: return self::NOT_IN_RANGE; } } /** * Normalize version. * Replacing "~^(\d)\.x~" need for Drupal plugins * Replacing words at the beginning and at the end of the version before and after the number. Example: beta3.2.6FREE => 3.2.6 * * @param VersionParser $versionParser * @param string $version * @return string */ private function normalize(VersionParser $versionParser, $version) { $version = preg_replace('~^(\d)\.x-~', '$1', $version); $version = preg_replace('~^(.*[\d.-]*\d)[a-z]*$~i', '$1', $version); $version = preg_replace('~^[a-z]*(\d[\d.-]*)$~i', '$1', $version); $version = preg_replace('~^\D+$~i', '0.0', $version); try { $version = $versionParser->normalize($version); } catch (\Exception $ex) { $version = ''; } return $version; } }publisher = $publisher; $this->db = $db; $this->comparisonStrategy = $comparisonStrategy; } /** * Notifies a listener about an event raised. * * @param AbstractEvent $event */ public function notify(AbstractEvent $event) { if ($event instanceof DetectionEvent) { $this->reactOnDetection($event); } } /** * Implements checking if a component is outdated. * * @param DetectionEvent $event */ private function reactOnDetection(DetectionEvent $event) { if ($event->getVersion() == DetectorInterface::UNKNOWN_VERSION || !$this->db->hasKey($event->getName())) { return; } $versionPairs = $this->db->getByKey($event->getName()); while ($pair = array_shift($versionPairs)) { switch ($this->comparisonStrategy->compare($pair[0], $pair[1], $event->getVersion())) { case ComparisonStrategyInterface::IN_RANGE_BUT_LOWER_THAN_RIGHT: $this->publisher->update( new OutdatedEvent($this, $event->getName(), $event->getVersion(), OutdatedEvent::TYPE_BRANCH) ); break; case ComparisonStrategyInterface::EQUALS_TO_RIGHT: if (!empty($versionPairs)) { $this->publisher->update( new OutdatedEvent( $this, $event->getName(), $event->getVersion(), OutdatedEvent::TYPE_PRODUCT ) ); } break; } } } }write("{$event->getName()} (ver: {$event->getVersion()}) has been detected at {$event->getPath()->getPathname()}"); } /** * Reacts on start-scanning event. * * @param ScanningStartedEvent $event */ protected function reactOnStartScanning(ScanningStartedEvent $event) { $this->write("Scanner version: {$event->getScannerVersion()}"); } /** * Reacts on start-dir-scanning event. * * @param ScanningDirStartedEvent $event */ protected function reactOnStartDirScanning(ScanningDirStartedEvent $event) { $this->write("Started scanning against {$event->getDirectory()->getPath()} at " . date('r')); } /** * Reacts on the end-dir-scanning event. * * @param ScanningDirEndedEvent $event */ protected function reactOnEndDirScanning(ScanningDirEndedEvent $event) { } /** * Reacts on an outdated software detected. * * @param OutdatedEvent $event */ protected function reactOnOutdatedDetected(OutdatedEvent $event) { $this->write("{$event->getName()} (ver: {$event->getVersion()}) is outdated (type: {$event->getType()})."); } /** * Writes a message to console. * * @param $message */ protected function write($message) { file_put_contents($this->targetFile, $message . PHP_EOL, FILE_APPEND); } /** * Reacts on the end-scanning event. * * @param ScanningEndedEvent $event */ protected function reactOnEndScanning(ScanningEndedEvent $event) { } } null, 'doc_root' => null, 'scanning_started_at' => null, 'detected_domain_apps' => [], ]; /** * @var string */ private $directoryDomain = ''; /** * Reacts on a detection event. * * @param DetectionEvent $event */ protected function reactOnDetection(DetectionEvent $event) { $id = $event->getName(); if (!isset($this->report['detected_domain_apps'][$id])) { $this->report['detected_domain_apps'][$id] = []; } $user = $event->getPath()->getOwner(); if (extension_loaded('posix')) { $user = posix_getpwuid($user)['name']; } $this->report['detected_domain_apps'][$id][] = [ 'ver' => $event->getVersion(), 'path' => $event->getPath()->getPathname(), 'user' => $user, 'domain' => $this->directoryDomain, ]; } /** * Reacts on start-scanning event. * * @param ScanningStartedEvent $event */ protected function reactOnStartScanning(ScanningStartedEvent $event) { $this->report['app_detector_version'] = $event->getScannerVersion(); $this->report['scanning_started_at'] = date('r'); } /** * Reacts on start-dir-scanning event. * * @param ScanningDirStartedEvent $event */ protected function reactOnStartDirScanning(ScanningDirStartedEvent $event) { $directory = $event->getDirectory(); $this->report['doc_root'] = $directory->getPath(); $this->directoryDomain = $directory->getDomain(); } /** * Reacts on the end-dir-scanning event. * * @param ScanningDirEndedEvent $event */ protected function reactOnEndDirScanning(ScanningDirEndedEvent $event) { } /** * Reacts on an outdated software detection event. * * @param OutdatedEvent $event */ protected function reactOnOutdatedDetected(OutdatedEvent $event) { $id = $event->getName(); $this->report['outdated'][$id][] = [ [$event->getType(), $event->getVersion()] ]; } /** * Reacts on the end-scanning event. * * @param ScanningEndedEvent $event */ protected function reactOnEndScanning(ScanningEndedEvent $event) { if (Stats::isAccumulateStats()) { $this->report['stats'] = [ 'memory_usage_start' => Stats::getMemoryUsageStart(), 'memory_usage_end' => Stats::getMemoryUsageEnd(), 'memory_usage_peak' => Stats::getMemoryUsagePeak(), 'count_file_readed' => Stats::getCountFilesReaded(), 'count_file_interation' => Stats::getCountFilesIteration(), 'count_dir_opened' => Stats::getCountDirsOpened(), ]; } file_put_contents($this->targetFile, json_encode($this->report)); } }iaid_token = $iaid_token; $this->timeout = $timeout; } /** * @param $data * @return object */ public function request($data) { $result = ''; $data = [ 'data' => [$data], ]; $json_data = json_encode($data); $headers = ['Content-Type: application/json']; if (isset($this->iaid_token)) { $headers[] = 'X-Auth: ' . $this->iaid_token; } try { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, (isset($this->iaid_token) ? self::API_V2_URL : self::API_URL)); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data); $result = curl_exec($ch); curl_close($ch); } catch (\Exception $e) { } return @json_decode($result); } } [ WpPluginDetector::class => '', WpThemeDetector::class => '', ], DrupalCoreDetector::class => [ DrupalPluginDetector::class => '', ], JoomlaCoreDetector::class => [ JoomlaPluginDetector::class => '', ], ]; /** * @var int */ private $timestampStarted; /** * @var int */ private $timestampEnded; /** * SqliteDbReport constructor. * * @param SqliteDbReportConnection $connection */ public function __construct(SqliteDbReportConnection $connection) { $this->connection = $connection; } /** * Reacts on a detection event. * * @param DetectionEvent $event */ protected function reactOnDetection(DetectionEvent $event) { $senderClass = get_class($event->getSender()); $path = (string)$event->getPath(); $realPath = realpath($path); $filename = ($senderClass == CommonScriptDetector::class ? basename($path) : null); $this->data[] = [ 'appName' => $event->getName(), 'appVersion' => $event->getVersion(), 'appPath' => $path, 'appFilename' => $filename, 'appUID' => $event->getUID(), 'appRealPath' => $realPath, 'appSenderClass' => $senderClass, ]; } /** * Reacts on start-scanning event. * * @param ScanningStartedEvent $event */ protected function reactOnStartScanning(ScanningStartedEvent $event) { $this->scanId = $event->getScanId(); } /** * Reacts on start-dir-scanning event. * * @param ScanningDirStartedEvent $event */ protected function reactOnStartDirScanning(ScanningDirStartedEvent $event) { $this->timestampStarted = time(); $this->directory = $event->getDirectory(); $this->dir = $this->directory->getPath(); $this->domain = $this->directory->getDomain(); $this->uid = $this->directory->getOwner(); $this->data = []; } /** * Reacts on the end-dir-scanning event. * * @param ScanningDirEndedEvent $event */ protected function reactOnEndDirScanning(ScanningDirEndedEvent $event) { $this->timestampEnded = time(); $reportData = []; $parent = null; $lastParentIndex = null; $i = 0; foreach ($this->data as $app) { if ($this->isParent($app)) { $parent = $app; $reportData[$i] = $app; $lastParentIndex = $i; $i++; } elseif($parent && $this->isSubApp($parent, $app)) { $reportData[$lastParentIndex]['subApp'][] = $app; } else { $reportData[$i] = $app; $i++; } } $this->connection->insertDirData($this->scanId, $this->timestampStarted, $this->timestampEnded, $this->uid, $this->dir, $this->domain, $reportData); $this->connection->clearOutdatedData($this->dir); } /** * Reacts on an outdated software detected. * * @param OutdatedEvent $event */ protected function reactOnOutdatedDetected(OutdatedEvent $event) { } /** * Reacts on the end-scanning event. * * @param ScanningEndedEvent $event * * @throws \Exception */ protected function reactOnEndScanning(ScanningEndedEvent $event) { } /** * Is current app parent * * @param array $app * * @return bool */ private function isParent($app): bool { return isset($this->parenthood[$app['appSenderClass']]); } /** * Is current app child for $parentApp * * @param array $parentApp * @param array $subApp * * @return bool */ private function isSubApp($parentApp, $subApp): bool { if (!isset($this->parenthood[$parentApp['appSenderClass']])) { return false; } if (!isset($this->parenthood[$parentApp['appSenderClass']][$subApp['appSenderClass']])) { return false; } if (strpos($subApp['appPath'], $parentApp['appPath']) === 0) { return true; } return false; } }targetFile = $targetFile; } } '', 'scan_id' => null, 'uid' => null, 'timestamp' => null, 'apps' => [], ]; /** * @var RemoteStatsRequest */ private $request = null; /** * @var string */ private $domain = ''; /** * @var int */ private $folderCounter = 0; /** * @var string */ private $currentDirpath = ''; /** * RemoteStatsReport constructor. * * @param RemoteStatsRequest $request */ public function __construct($request) { $this->request = $request; } /** * Reacts on a detection event. * * @param DetectionEvent $event */ protected function reactOnDetection(DetectionEvent $event) { $id = $event->getName(); $this->report['apps'][] = [ 'name' => $id, 'version' => $event->getVersion(), 'path' => $event->getPath()->getPathname(), 'uid' => $event->getUID(), 'domain' => $this->domain, ]; $this->folderCounter++; } /** * Reacts on an outdated software detection event. * * @param OutdatedEvent $event */ protected function reactOnOutdatedDetected(OutdatedEvent $event) { } /** * Reacts on start-scanning event. * * @param ScanningStartedEvent $event */ protected function reactOnStartScanning(ScanningStartedEvent $event) { $this->report['system_id'] = $this->getSystemId(); $this->report['scan_id'] = $event->getScanId(); $this->report['timestamp'] = time(); $this->report['uid'] = getmyuid(); } /** * Reacts on start-dir-scanning event. * * @param ScanningDirStartedEvent $event */ protected function reactOnStartDirScanning(ScanningDirStartedEvent $event) { $this->folderCounter = 0; $this->domain = $event->getDirectory()->getDomain(); $this->currentDirpath = $event->getDirectory()->getPath(); } /** * Reacts on the end-dir-scanning event. * * @param ScanningDirEndedEvent $event */ protected function reactOnEndDirScanning(ScanningDirEndedEvent $event) { if ($this->folderCounter != 0) { return; } $dirpathOwner = file_exists($this->currentDirpath) ? @fileowner($this->currentDirpath) : null; $this->report['apps'][] = [ 'name' => 'unknown_app', 'version' => 'unknown_version', 'path' => $this->currentDirpath, 'uid' => $dirpathOwner, 'domain' => $this->domain, ]; } /** * Get system id from CloudLinux OS * * @return string */ public function getSystemId() { $config_filepath = '/etc/sysconfig/rhn/systemid'; $system_id = ''; if (!file_exists($config_filepath)) { return $system_id; } $content = @file_get_contents($config_filepath); if (preg_match('~\s*system_id\s*\s*\s*\s*([^<]+)\s*~is', $content, $m)) { $system_id = $m[1]; } return $system_id; } /** * Reacts on the end-scanning event. * * @param ScanningEndedEvent $event */ protected function reactOnEndScanning(ScanningEndedEvent $event) { $apps = array_chunk($this->report['apps'], self::MAX_RECORDS_PER_REQUEST); foreach($apps as $chunk) { $this->report['apps'] = $chunk; $this->request->request($this->report); } } } reactOnDetection($event); break; case $event instanceof OutdatedEvent: $this->reactOnOutdatedDetected($event); break; case $event instanceof ScanningStartedEvent: $this->reactOnStartScanning($event); break; case $event instanceof ScanningDirStartedEvent: $this->reactOnStartDirScanning($event); break; case $event instanceof ScanningDirEndedEvent: $this->reactOnEndDirScanning($event); break; case $event instanceof ScanningEndedEvent: $this->reactOnEndScanning($event); break; } } /** * Reacts on a detection event. * * @param DetectionEvent $event */ abstract protected function reactOnDetection(DetectionEvent $event); /** * Reacts on start-scanning event. * * @param ScanningStartedEvent $event */ abstract protected function reactOnStartScanning(ScanningStartedEvent $event); /** * Reacts on start-dir-scanning event. * * @param ScanningDirStartedEvent $event */ abstract protected function reactOnStartDirScanning(ScanningDirStartedEvent $event); /** * Reacts on an outdated software detected. * * @param OutdatedEvent $event */ abstract protected function reactOnOutdatedDetected(OutdatedEvent $event); /** * Reacts on the end-dir-scanning event. * * @param ScanningDirEndedEvent $event */ abstract protected function reactOnEndDirScanning(ScanningDirEndedEvent $event); /** * Reacts on the end-scanning event. * * @param ScanningEndedEvent $event */ abstract protected function reactOnEndScanning(ScanningEndedEvent $event); }SQLite format 3@ . !Dm!J%eindexi_branch_keybranchCREATE INDEX i_branch_key ON branch (app_id)ytablebranchbranchCREATE TABLE branch ( app_id INTEGER NOT NULL,first_version TEXT NOT NULL,last_version TEXT NOT NULL)Ccindexi_app_keyappCREATE UNIQUE INDEX i_app_key ON app (name)P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)h7tableappappCREATE TABLE app ( id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL) + FvY<mM. m K ,  d G .  t ] F+1drupal_plugin_date*1drupal_plugin_imce)Cdrupal_plugin_jquery_update(7drupal_plugin_webform'5drupal_plugin_entity&;drupal_plugin_libraries%3drupal_plugin_views$9drupal_plugin_pathauto#3drupal_plugin_token"5drupal_plugin_ctools!=joomla_plugin_jdownloads Ajoomla_plugin_smartslider37joomla_plugin_sigplus5joomla_plugin_akeeba'Ujoomla_plugin_system_modulesanywhere;joomla_plugin_arkeditor7joomla_plugin_j2store1joomla_plugin_acymAjoomla_plugin_jch_optimize Gjoomla_plugin_advancedmodules9wp_plugin_wpforms-lite=wp_plugin_wp-super-cache;wp_plugin_wordpress-seoEwp_plugin_wordpress-importer3wp_plugin_wordfence7wp_plugin_woocommerce7wp_plugin_updraftplusAwp_plugin_tinymce-advancedCwp_plugin_really-simple-ssl/wp_plugin_jetpack% Qwp_plugin_google-sitemap-generator+ ]wp_plugin_google-analytics-for-wordpress. cwp_plugin_google-analytics-dashboard-for-wp 3wp_plugin_elementor =wp_plugin_duplicate-post=wp_plugin_contact-form-7=wp_plugin_classic-editor$Owp_plugin_all-in-one-wp-migration Gwp_plugin_all-in-one-seo-pack/wp_plugin_akismet#drupal_core#joomla_core wp_core app+ + G / G ^ u    L - H e wZ= nN/ n1drupal_plugin_date+1drupal_plugin_imce*Cdrupal_plugin_jquery_update)7drupal_plugin_webform(5drupal_plugin_entity';drupal_plugin_libraries&3drupal_plugin_views%9drupal_plugin_pathauto$3drupal_plugin_token#5drupal_plugin_ctools"=joomla_plugin_jdownloads!Ajoomla_plugin_smartslider3 7joomla_plugin_sigplus5joomla_plugin_akeeba(Ujoomla_plugin_system_modulesanywhere;joomla_plugin_arkeditor7joomla_plugin_j2store1joomla_plugin_acymAjoomla_plugin_jch_optimize!Gjoomla_plugin_advancedmodules9wp_plugin_wpforms-lite=wp_plugin_wp-super-cache;wp_plugin_wordpress-seo Ewp_plugin_wordpress-importer3wp_plugin_wordfence7wp_plugin_woocommerce7wp_plugin_updraftplusAwp_plugin_tinymce-advancedCwp_plugin_really-simple-ssl/wp_plugin_jetpack&Qwp_plugin_google-sitemap-generator ,]wp_plugin_google-analytics-for-wordpress /cwp_plugin_google-analytics-dashboard-for-wp 3wp_plugin_elementor =wp_plugin_duplicate-post =wp_plugin_contact-form-7=wp_plugin_classic-editor%Owp_plugin_all-in-one-wp-migration!Gwp_plugin_all-in-one-seo-pack/wp_plugin_akismet#drupal_core#joomla_core  wp_core ^ o_O?/ziXG6% | j X G 6 %   v e T C 2 !  u d R A 0  } h R = (  ^+7.x-0.07.x-2.10]*8.x-0.08.x-1.7\)7.x-0.07.x-2.7[(8.x-0.08.x-5.5Z'7.x-0.07.x-1.9Y&7.x-0.07.x-2.5X%7.x-0.07.x-3.23W$8.x-0.08.x-1.5V#8.x-0.08.x-1.5U"7.x-0.07.x-1.15T"8.x-0.08.x-3.2S!0.0.03.2.65R 0.0.03.3.22Q0.0.01.5.0.277P0.0.06.6.1O0.0.07.8.2N0.0.02.6.10M0.0.03.3.11L0.0.06.5.1K0.0.05.4.3J0.0.07.12.3I0.0.01.5.6H0.0.01.7.0G0.0.012.4.0F0.0.00.6.4E0.0.07.4.0D0.0.03.7.1C0.0.01.16.17B0.0.05.2.1A0.0.03.2.6@0.0.07.8.0? 0.0.04.1.0> 0.0.07.9.0= 0.0.05.3.9< 0.0.02.7.5; 0.0.03.2.3:0.0.05.1.490.0.01.5.080.0.07.9.070.0.03.2.960.0.04.1.258.0.08.7.847.0.07.67.036.0.06.38.025.0.05.23.014.7.04.7.1104.6.04.6.11/4.5.04.5.8.4.4.04.4.3-4.3.04.3.2,4.2.04.2.0+4.1.04.1.0*4.0.04.0.0)3.0.03.9.12(2.5.02.5.28'1.7.01.7.5&1.6.01.6.6%1.5.01.5.26$1.0.01.0.15# 5.2.05.2.4" 5.1.05.1.3! 5.0.05.0.7  4.9.04.9.12 4.8.04.8.11 4.7.04.7.15 4.6.04.6.16 4.5.04.5.19 4.4.04.4.20 4.3.04.3.21 4.2.04.2.25 4.1.04.1.28 4.0.04.0.28 3.9.03.9.29 3.8.03.8.31 3.7.03.7.31 3.6.03.6.1 3.5.03.5.2 3.4.03.4.2 3.3.03.3.3 3.2.03.2.1 3.1.03.1.4  3.0.03.0.6  2.9.02.9.2  2.8.02.8.6  2.7.02.7.1  2.6.02.6.5 2.5.02.5.1 2.3.02.3.3 2.2.02.2.3 2.1.02.1.3 2.0.02.0.11 1.5.01.5.2 1.2.01.2.2 1.0.01.0.2 ^ zupkfa\WRLF@:4.(" ztnhb\VPJD>82,&  +^*])\(['Z&Y%X$W#V"U"T!S RQPONMLKJIHGFEDCBA@ ? > = < ;:9876543210/.-,+*)('&%$ # " !                                      { "wp_core": { "meta": { "source": "https://wordpress.org/download/releases/" }, "branches": [ ["1.0.0", "1.0.2"], ["1.2.0", "1.2.2"], ["1.5.0", "1.5.2"], ["2.0.0", "2.0.11"], ["2.1.0", "2.1.3"], ["2.2.0", "2.2.3"], ["2.3.0", "2.3.3"], ["2.5.0", "2.5.1"], ["2.6.0", "2.6.5"], ["2.7.0", "2.7.1"], ["2.8.0", "2.8.6"], ["2.9.0", "2.9.2"], ["3.0.0", "3.0.6"], ["3.1.0", "3.1.4"], ["3.2.0", "3.2.1"], ["3.3.0", "3.3.3"], ["3.4.0", "3.4.2"], ["3.5.0", "3.5.2"], ["3.6.0", "3.6.1"], ["3.7.0", "3.7.31"], ["3.8.0", "3.8.31"], ["3.9.0", "3.9.29"], ["4.0.0", "4.0.28"], ["4.1.0", "4.1.28"], ["4.2.0", "4.2.25"], ["4.3.0", "4.3.21"], ["4.4.0", "4.4.20"], ["4.5.0", "4.5.19"], ["4.6.0", "4.6.16"], ["4.7.0", "4.7.15"], ["4.8.0", "4.8.11"], ["4.9.0", "4.9.12"], ["5.0.0", "5.0.7"], ["5.1.0", "5.1.3"], ["5.2.0", "5.2.4"] ] }, "joomla_core": { "meta": { "source": "https://www.joomla.org/announcements/release-news.html", "top_plugins": "https://extensions.joomla.org/browse/top-rated/" }, "branches": [ ["1.0.0", "1.0.15"], ["1.5.0", "1.5.26"], ["1.6.0", "1.6.6"], ["1.7.0", "1.7.5"], ["2.5.0", "2.5.28"], ["3.0.0", "3.9.12"] ] }, "drupal_core": { "meta": { "source": "https://www.drupal.org/project/drupal/releases", "top_plugins": "https://www.drupal.org/project/project_module/?solrsort=iss_project_release_usage%20desc&f%5B4%5D=sm_field_project_type%3Afull" }, "branches": [ ["4.0.0", "4.0.0"], ["4.1.0", "4.1.0"], ["4.2.0", "4.2.0"], ["4.3.0", "4.3.2"], ["4.4.0", "4.4.3"], ["4.5.0", "4.5.8"], ["4.6.0", "4.6.11"], ["4.7.0", "4.7.11"], ["5.0.0", "5.23.0"], ["6.0.0", "6.38.0"], ["7.0.0", "7.67.0"], ["8.0.0", "8.7.8"] ] }, "wp_plugin_akismet": { "meta": { "source": "https://wordpress.org/plugins/akismet/" }, "branches": [ ["0.0.0", "4.1.2"] ] }, "wp_plugin_all-in-one-seo-pack": { "meta": { "source": "https://wordpress.org/plugins/all-in-one-seo-pack/" }, "branches": [ ["0.0.0", "3.2.9"] ] }, "wp_plugin_all-in-one-wp-migration": { "meta": { "source": "https://wordpress.org/plugins/all-in-one-wp-migration/" }, "branches": [ ["0.0.0", "7.9.0"] ] }, "wp_plugin_classic-editor": { "meta": { "source": "https://wordpress.org/plugins/classic-editor/" }, "branches": [ ["0.0.0", "1.5.0"] ] }, "wp_plugin_contact-form-7": { "meta": { "source": "https://wordpress.org/plugins/contact-form-7/" }, "branches": [ ["0.0.0", "5.1.4"] ] }, "wp_plugin_duplicate-post": { "meta": { "source": "https://wordpress.org/plugins/duplicate-post/" }, "branches": [ ["0.0.0", "3.2.3"] ] }, "wp_plugin_elementor": { "meta": { "source": "https://wordpress.org/plugins/elementor/" }, "branches": [ ["0.0.0", "2.7.5"] ] }, "wp_plugin_google-analytics-dashboard-for-wp": { "meta": { "source": "https://wordpress.org/plugins/google-analytics-dashboard-for-wp/" }, "branches": [ ["0.0.0", "5.3.9"] ] }, "wp_plugin_google-analytics-for-wordpress": { "meta": { "source": "https://wordpress.org/plugins/google-analytics-for-wordpress/" }, "branches": [ ["0.0.0", "7.9.0"] ] }, "wp_plugin_google-sitemap-generator": { "meta": { "source": "https://wordpress.org/plugins/google-sitemap-generator/" }, "branches": [ ["0.0.0", "4.1.0"] ] }, "wp_plugin_jetpack": { "meta": { "source": "https://wordpress.org/plugins/jetpack/" }, "branches": [ ["0.0.0", "7.8.0"] ] }, "wp_plugin_really-simple-ssl": { "meta": { "source": "https://wordpress.org/plugins/really-simple-ssl/" }, "branches": [ ["0.0.0", "3.2.6"] ] }, "wp_plugin_tinymce-advanced": { "meta": { "source": "https://wordpress.org/plugins/tinymce-advanced/" }, "branches": [ ["0.0.0", "5.2.1"] ] }, "wp_plugin_updraftplus": { "meta": { "source": "https://wordpress.org/plugins/updraftplus/" }, "branches": [ ["0.0.0", "1.16.17"] ] }, "wp_plugin_woocommerce": { "meta": { "source": "https://wordpress.org/plugins/woocommerce/" }, "branches": [ ["0.0.0", "3.7.1"] ] }, "wp_plugin_wordfence": { "meta": { "source": "https://wordpress.org/plugins/wordfence/" }, "branches": [ ["0.0.0", "7.4.0"] ] }, "wp_plugin_wordpress-importer": { "meta": { "source": "https://wordpress.org/plugins/wordpress-importer/" }, "branches": [ ["0.0.0", "0.6.4"] ] }, "wp_plugin_wordpress-seo": { "meta": { "source": "https://wordpress.org/plugins/wordpress-seo/" }, "branches": [ ["0.0.0", "12.4.0"] ] }, "wp_plugin_wp-super-cache": { "meta": { "source": "https://wordpress.org/plugins/wp-super-cache/" }, "branches": [ ["0.0.0", "1.7.0"] ] }, "wp_plugin_wpforms-lite": { "meta": { "source": "https://wordpress.org/plugins/wpforms-lite/" }, "branches": [ ["0.0.0", "1.5.6"] ] }, "joomla_plugin_advancedmodules": { "meta": { "source": "https://www.regularlabs.com/extensions/advancedmodulemanager" }, "branches": [ ["0.0.0", "7.12.3"] ] }, "joomla_plugin_jch_optimize": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/core-enhancements/performance/jch-optimize/" }, "branches": [ ["0.0.0", "5.4.3"] ] }, "joomla_plugin_acym": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/marketing/newsletter/acymailing-starter/" }, "branches": [ ["0.0.0", "6.5.1"] ] }, "joomla_plugin_j2store": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/e-commerce/shopping-cart/j2store/" }, "branches": [ ["0.0.0", "3.3.11"] ] }, "joomla_plugin_arkeditor": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/edition/editors/jck-editor/" }, "branches": [ ["0.0.0", "2.6.10"] ] }, "joomla_plugin_system_modulesanywhere": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/core-enhancements/coding-a-scripts-integration/modules-anywhere/" }, "branches": [ ["0.0.0", "7.8.2"] ] }, "joomla_plugin_akeeba": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/access-a-security/site-security/akeeba-backup/" }, "branches": [ ["0.0.0", "6.6.1"] ] }, "joomla_plugin_sigplus": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/photos-a-images/galleries/sigplus/" }, "branches": [ ["0.0.0", "1.5.0.277"] ] }, "joomla_plugin_smartslider3": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/photos-a-images/slideshow/smart-slider-2/" }, "branches": [ ["0.0.0", "3.3.22"] ] }, "joomla_plugin_jdownloads": { "meta": { "source": "https://extensions.joomla.org/extensions/extension/directory-a-documentation/downloads/jdownloads/" }, "branches": [ ["0.0.0", "3.2.65"] ] }, "drupal_plugin_ctools": { "meta": { "source": "https://www.drupal.org/project/ctools" }, "branches": [ ["8.x-0.0", "8.x-3.2"], ["7.x-0.0", "7.x-1.15"] ] }, "drupal_plugin_token": { "meta": { "source": "https://www.drupal.org/project/token" }, "branches": [ ["8.x-0.0", "8.x-1.5"] ] }, "drupal_plugin_pathauto": { "meta": { "source": "https://www.drupal.org/project/pathauto" }, "branches": [ ["8.x-0.0", "8.x-1.5"] ] }, "drupal_plugin_views": { "meta": { "source": "https://www.drupal.org/project/views" }, "branches": [ ["7.x-0.0", "7.x-3.23"] ] }, "drupal_plugin_libraries": { "meta": { "source": "https://www.drupal.org/project/libraries" }, "branches": [ ["7.x-0.0", "7.x-2.5"] ] }, "drupal_plugin_entity": { "meta": { "source": "https://www.drupal.org/project/entity" }, "branches": [ ["7.x-0.0", "7.x-1.9"] ] }, "drupal_plugin_webform": { "meta": { "source": "https://www.drupal.org/project/webform" }, "branches": [ ["8.x-0.0", "8.x-5.5"] ] }, "drupal_plugin_jquery_update": { "meta": { "source": "https://www.drupal.org/project/jquery_update" }, "branches": [ ["7.x-0.0", "7.x-2.7"] ] }, "drupal_plugin_imce": { "meta": { "source": "https://www.drupal.org/project/imce" }, "branches": [ ["8.x-0.0", "8.x-1.7"] ] }, "drupal_plugin_date": { "meta": { "source": "https://www.drupal.org/project/date" }, "branches": [ ["7.x-0.0", "7.x-2.10"] ] } } ^Wz ;--7GBMB