AlkantarClanX12

Your IP : 3.142.250.86


Current Path : /proc/self/root/opt/app-version-detector/
Upload File :
Current File : //proc/self/root/opt/app-version-detector/app-version-detector.phar

<?php

if (in_array('phar', stream_get_wrappers()) && class_exists('Phar', 0)) {
    Phar::interceptFileFuncs();
    set_include_path('phar://' . __FILE__ . PATH_SEPARATOR . get_include_path());
    include 'phar://' . __FILE__ . '/' . Extract_Phar::START;
    return;
}

class Extract_Phar
{
    static $temp;
    static $origdir;
    const GZ = 0x1000;
    const BZ2 = 0x2000;
    const MASK = 0x3000;
    const START = 'bin/cli.php';
    const LEN = 6636;

    static function go($return = false)
    {
        $fp = fopen(__FILE__, 'rb');
        fseek($fp, self::LEN);
        $L = unpack('V', $a = fread($fp, 4));
        $m = '';

        do {
            $read = 8192;
            if ($L[1] - strlen($m) < 8192) {
                $read = $L[1] - strlen($m);
            }
            $last = fread($fp, $read);
            $m .= $last;
        } while (strlen($last) && strlen($m) < $L[1]);

        if (strlen($m) < $L[1]) {
            die('ERROR: manifest length read was "' .
                strlen($m) .'" should be "' .
                $L[1] . '"');
        }

        $info = self::_unpack($m);
        $f = $info['c'];

        if ($f & self::GZ) {
            if (!function_exists('gzinflate')) {
                die('Error: zlib extension is not enabled -' .
                    ' gzinflate() function needed for zlib-compressed .phars');
            }
        }

        if ($f & self::BZ2) {
            if (!function_exists('bzdecompress')) {
                die('Error: bzip2 extension is not enabled -' .
                    ' bzdecompress() function needed for bz2-compressed .phars');
            }
        }

        $temp = self::tmpdir();

        if (!$temp || !is_writable($temp)) {
            $sessionpath = session_save_path();
            if (strpos ($sessionpath, ";") !== false)
                $sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1);
            if (!file_exists($sessionpath) || !is_dir($sessionpath)) {
                die('Could not locate temporary directory to extract phar');
            }
            $temp = $sessionpath;
        }

        $temp .= '/pharextract/'.basename(__FILE__, '.phar');
        self::$temp = $temp;
        self::$origdir = getcwd();
        @mkdir($temp, 0777, true);
        $temp = realpath($temp);

        if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) {
            self::_removeTmpFiles($temp, getcwd());
            @mkdir($temp, 0777, true);
            @file_put_contents($temp . '/' . md5_file(__FILE__), '');

            foreach ($info['m'] as $path => $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.pharbin/cli.php��Vf�v��L�"vendor/composer/platform_check.php��Vf���%��%vendor/composer/InstalledVersions.php?�Vf? 2�Ŵ!vendor/composer/autoload_psr4.phpF�VfF1F�vendor/composer/LICENSE.�Vf. ��#vendor/composer/autoload_static.phpN0�VfN0̣�J�vendor/composer/installed.php8�Vf8���%vendor/composer/autoload_classmap.phpT'�VfT'�j&��vendor/composer/ClassLoader.php�?�Vf�?2@u�!vendor/composer/autoload_real.php��Vf�_mǚ�'vendor/composer/autoload_namespaces.php��Vf��/t�vendor/composer/installed.json��Vf�6�c�$vendor/composer/semver/composer.jsonL�VfLP����,vendor/composer/semver/src/VersionParser.phpKT�VfKT�v�ʴ=vendor/composer/semver/src/Constraint/ConstraintInterface.php^�Vf^)��X�9vendor/composer/semver/src/Constraint/EmptyConstraint.php:�Vf:@M�<vendor/composer/semver/src/Constraint/AbstractConstraint.phpn�Vfn��;�9vendor/composer/semver/src/Constraint/MultiConstraint.php<
�Vf<
ª�	�4vendor/composer/semver/src/Constraint/Constraint.php�Vf��V�%vendor/composer/semver/src/Semver.php��Vf�o�I��)vendor/composer/semver/src/Comparator.php�	�Vf�	�g3	�vendor/composer/semver/LICENSE�VfBh� vendor/composer/semver/README.mdk�VfkO�
�#vendor/composer/semver/CHANGELOG.md��Vf�Mg˴vendor/autoload.php�Vf)N��%src/Event/ScanningDirStartedEvent.phpP�VfPG���#src/Event/ScanningDirEndedEvent.php��Vf��:��"src/Event/ScanningStartedEvent.php��Vf����Ф src/Event/ScanningEndedEvent.php�Vf��`��src/Event/EventManager.php��Vf�6�� src/Storage/ActualVersionsDb.phpR�VfR� 1�(src/Storage/SqliteDbReportConnection.php��Vf��Æ?�&src/Storage/ActualVersionsSQLiteDb.php��Vf���src/Application/Config.php��Vf�P雪�src/Application/Profiler.php]�Vf]�B}�#src/Application/DetectorBuilder.php��Vf�WE�A�src/Application/Helper.php,�Vf,�T ޤ src/Application/AppException.php��Vf�w_��src/Application/Stats.php��Vf�2S��src/Application/AVDCliParse.php=-�Vf=-*@ۜ�src/Application/Directory.phpy�Vfy���src/Application/Migraitor.php��Vf�'�uؤsrc/Application/CliParse.phpj�Vfjt��src/Application/Factory.phpq�Vfq����&src/Application/Error/ErrorHandler.php�Vf�YhƤ(src/Application/ConfigParamException.php��Vf���F=�src/Application/FileOwners.php_�Vf_Pl+Ԥsrc/Application/AVDConfig.php�
�Vf�
�R���src/Scanner.php�Vf� �o�src/Core/PublisherInterface.php@�Vf@�E��src/Core/AbstractEvent.php��Vf�7ޞ��:src/Core/VersionDetection/DependencyCollectionDetector.php��Vf�­G"�,src/Core/VersionDetection/DetectionEvent.php��Vf���xu�=src/Core/VersionDetection/Detector/OsCommerceCoreDetector.php��Vf��$�I�;src/Core/VersionDetection/Detector/OpenCartCoreDetector.phpW�VfW�7Z�9src/Core/VersionDetection/Detector/DrupalCoreDetector.php��Vf��
~��4src/Core/VersionDetection/Detector/DummyDetector.phpO�VfO=~��;src/Core/VersionDetection/Detector/CommonScriptDetector.phpL�VfL�5��:src/Core/VersionDetection/Detector/WpComponentDetector.php��Vf�����5src/Core/VersionDetection/Detector/WpCoreDetector.phpm�Vfm��
�6src/Core/VersionDetection/Detector/WpThemeDetector.php��Vf����u�9src/Core/VersionDetection/Detector/JoomlaCoreDetector.php��Vf��g���;src/Core/VersionDetection/Detector/JoomlaPluginDetector.php��Vf�ES���;src/Core/VersionDetection/Detector/DrupalPluginDetector.php`�Vf`F����6src/Core/VersionDetection/Detector/IPBCoreDetector.php�
�Vf�
d��`�9src/Core/VersionDetection/Detector/PHPBB3CoreDetector.php�
�Vf�
~C�5�7src/Core/VersionDetection/Detector/ModxCoreDetector.php
�Vf
�]Ch�:src/Core/VersionDetection/Detector/MagentoCoreDetector.phpp
�Vfp
u�eW�7src/Core/VersionDetection/Detector/WpPluginDetector.php��Vf����Ť9src/Core/VersionDetection/Detector/BitrixCoreDetector.php��Vf� �Ϥ.src/Core/VersionDetection/AbstractDetector.php��Vf�ކG��/src/Core/VersionDetection/DetectorInterface.php��Vf��e�k�7src/Core/VersionDetection/AbstractCompositeDetector.php��Vf�]�[�5src/Core/VersionDetection/PlainCollectionDetector.php��Vf��^�e�src/Core/ListenerInterface.php_�Vf_�6� �src/Core/MediatorInterface.php�Vf��ë�,src/Core/OutdatedDetection/OutdatedEvent.phpQ�VfQ�0�ɤ8src/Core/OutdatedDetection/GenericComparisonStrategy.php��Vf�m�Z�:src/Core/OutdatedDetection/ComparisonStrategyInterface.phpd�Vfd�{|�1src/Core/OutdatedDetection/VersionDbInterface.php��Vf��(lr�.src/Core/OutdatedDetection/OutdatedChecker.php�
�Vf�
�]p�src/Report/TextReport.php��Vf�R�xP�src/Report/JsonReport.phpF�VfF�D4�*src/Report/Includes/RemoteStatsRequest.php�Vf2�W��src/Report/SqliteDbReport.php��Vf���u��&src/Report/AbstractFileBasedReport.php��Vf��L�s� src/Report/RemoteStatsReport.php3�Vf36籤src/Report/AbstractReport.php�Vf/����#data/actual-versions-sqlite-db.json`�Vf`k���data/actual-versions-db.json�$�Vf�$/��<?php

use AppVersionDetector\Application\Config;
use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Profiler;
use AppVersionDetector\Application\DetectorBuilder;
use AppVersionDetector\Application\Error\ErrorHandler;
use AppVersionDetector\Application\Factory;
use AppVersionDetector\Core\AbstractEvent;
use AppVersionDetector\Core\OutdatedDetection\ComparisonStrategyInterface;
use AppVersionDetector\Core\OutdatedDetection\OutdatedChecker;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Event\EventManager;
use AppVersionDetector\Report\AbstractFileBasedReport;
use AppVersionDetector\Report\RemoteStatsReport;
use AppVersionDetector\Report\Includes\RemoteStatsRequest;
use AppVersionDetector\Report\SqliteDbReport;
use AppVersionDetector\Scanner;
use AppVersionDetector\Storage\ActualVersionsDb;
use AppVersionDetector\Storage\SqliteDbReportConnection;
use AppVersionDetector\Application\AVDConfig;
use AppVersionDetector\Application\AVDCliParse;
use AppVersionDetector\Application\FileOwners;

require_once __DIR__ . '/../vendor/autoload.php';

const IAID_TOKEN_PATH = '/var/imunify360/iaid-token';

$errorHandler = new ErrorHandler();
set_error_handler([$errorHandler, 'handleError']);
set_exception_handler([$errorHandler, 'handleException']);

if (!isset($argv)) {
    $argv = $_SERVER['argv'];
}

$config = new AVDConfig();
$cli = new AVDCliParse($argv, $config);

Profiler::startProfiling();
Factory::configure($config->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();
<?php

// platform_check.php @generated by Composer

$issues = array();

if (!(PHP_VERSION_ID >= 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
    );
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     */
    private static $installed;

    /**
     * @var bool|null
     */
    private static $canGetVendors;

    /**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    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<string>
     */
    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<string>
     */
    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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    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<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();
    }

    /**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }

        if (self::$installed !== array()) {
            $installed[] = self::$installed;
        }

        return $installed;
    }
}
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Composer\\Semver\\' => 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.

<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit431d7b3dd0d6aa4f493c20cfbb8de03f
{
    public static $prefixLengthsPsr4 = array (
        'C' => 
        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);
    }
}
<?php return array(
    'root' => 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,
        ),
    ),
);
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'AppVersionDetector\\Application\\AVDCliParse' => $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',
);
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * 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 <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @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<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    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<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $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>|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>|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>|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>|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<string, self>
     */
    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);
    }
}
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit431d7b3dd0d6aa4f493c20cfbb8de03f
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        require __DIR__ . '/platform_check.php';

        spl_autoload_register(array('ComposerAutoloaderInit431d7b3dd0d6aa4f493c20cfbb8de03f', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInit431d7b3dd0d6aa4f493c20cfbb8de03f', 'loadClassLoader'));

        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInit431d7b3dd0d6aa4f493c20cfbb8de03f::getInitializer($loader));

        $loader->setClassMapAuthoritative(true);
        $loader->register(true);

        return $loader;
    }
}
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
);
{
    "packages": [
        {
            "name": "composer/semver",
            "version": "1.7.2",
            "version_normalized": "1.7.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/composer/semver.git",
                "reference": "647490bbcaf7fc4891c58f47b825eb99d19c377a"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/composer/semver/zipball/647490bbcaf7fc4891c58f47b825eb99d19c377a",
                "reference": "647490bbcaf7fc4891c58f47b825eb99d19c377a",
                "shasum": ""
            },
            "require": {
                "php": "^5.3.2 || ^7.0 || ^8.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.5 || ^5.0.5"
            },
            "time": "2020-12-03T15:47:16+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.x-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Composer\\Semver\\": "src"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Nils Adermann",
                    "email": "naderman@naderman.de",
                    "homepage": "http://www.naderman.de"
                },
                {
                    "name": "Jordi Boggiano",
                    "email": "j.boggiano@seld.be",
                    "homepage": "http://seld.be"
                },
                {
                    "name": "Rob Bast",
                    "email": "rob.bast@gmail.com",
                    "homepage": "http://robbast.nl"
                }
            ],
            "description": "Semver library that offers utilities, version constraint parsing and validation.",
            "keywords": [
                "semantic",
                "semver",
                "validation",
                "versioning"
            ],
            "support": {
                "irc": "irc://irc.freenode.org/composer",
                "issues": "https://github.com/composer/semver/issues",
                "source": "https://github.com/composer/semver/tree/1.7.2"
            },
            "funding": [
                {
                    "url": "https://packagist.com",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/composer",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
                    "type": "tidelift"
                }
            ],
            "install-path": "./semver"
        }
    ],
    "dev": false,
    "dev-package-names": []
}
{
    "name": "composer/semver",
    "description": "Semver library that offers utilities, version constraint parsing and validation.",
    "type": "library",
    "license": "MIT",
    "keywords": [
        "semver",
        "semantic",
        "versioning",
        "validation"
    ],
    "authors": [
        {
            "name": "Nils Adermann",
            "email": "naderman@naderman.de",
            "homepage": "http://www.naderman.de"
        },
        {
            "name": "Jordi Boggiano",
            "email": "j.boggiano@seld.be",
            "homepage": "http://seld.be"
        },
        {
            "name": "Rob Bast",
            "email": "rob.bast@gmail.com",
            "homepage": "http://robbast.nl"
        }
    ],
    "support": {
        "irc": "irc://irc.freenode.org/composer",
        "issues": "https://github.com/composer/semver/issues"
    },
    "require": {
        "php": "^5.3.2 || ^7.0 || ^8.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^4.5 || ^5.0.5"
    },
    "autoload": {
        "psr-4": {
            "Composer\\Semver\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Composer\\Semver\\": "tests"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.x-dev"
        }
    },
    "scripts": {
        "test": "phpunit"
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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 <j.boggiano@seld.be>
 */
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<version>(\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('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
            if (count($andConstraints) > 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<from>' . $versionRegex . ') +- +(?P<to>' . $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;
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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();
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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 '[]';
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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(<SpecificConstraintType> $provider);
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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) . ']';
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * 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
<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, $err);
        } elseif (!headers_sent()) {
            echo $err;
        }
    }
    trigger_error(
        $err,
        E_USER_ERROR
    );
}

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit431d7b3dd0d6aa4f493c20cfbb8de03f::getLoader();
<?php
/**
 * ScanningDirStartedEvent class file.
 */

namespace AppVersionDetector\Event;

use AppVersionDetector\Application\Directory;
use AppVersionDetector\Core\AbstractEvent;

/**
 * Class ScanningDirStartedEvent.
 */
class ScanningDirStartedEvent extends AbstractEvent
{
    /**
     * @var string
     */
    private $scannerVersion;
    
    /**
     * @var string
     */
    private $directory;
    
    /**
     * ScanningDirStartedEvent constructor.
     *
     * @param object $sender
     * @param string $scannerVersion
     * @param string $directory
     */
    public function __construct($sender, $scannerVersion, Directory $directory)
    {
        $this->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();
    }
}
<?php
/**
 * ScanningDirEndedEvent class file.
 */

namespace AppVersionDetector\Event;

use AppVersionDetector\Core\AbstractEvent;

/**
 * Class ScanningDirEndedEvent.
 */
class ScanningDirEndedEvent extends AbstractEvent
{
    /**
     * ScanningDirEndedEvent constructor.
     *
     * @param object $sender
     */
    public function __construct($sender)
    {
        $this->setSender($sender);
    }
}
<?php
/**
 * ScanningStartedEvent class file.
 */

namespace AppVersionDetector\Event;


use AppVersionDetector\Core\AbstractEvent;

/**
 * Class ScanningStartedEvent.
 */
class ScanningStartedEvent extends AbstractEvent
{
    /**
     * @var string
     */
    private $scannerVersion;
    
    /**
     * @var string
     */
    private $scan_id;

    /**
     * ScanningStartedEvent constructor.
     *
     * @param object $sender
     * @param string $scannerVersion
     * @param string $scanId
     */
    public function __construct($sender, $scannerVersion, $scanId)
    {
        $this->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;
    }
}
<?php
/**
 * ScanningEndedEvent class file.
 */

namespace AppVersionDetector\Event;

use AppVersionDetector\Core\AbstractEvent;

/**
 * Class ScanningEndedEvent.
 */
class ScanningEndedEvent extends AbstractEvent
{
    /**
     * @var string
     */
    private $scanId;

    /**
     * ScanningEndedEvent constructor.
     *
     * @param object $sender
     * @param $scanId
     */
    public function __construct($sender, $scanId)
    {
        $this->setSender($sender);
        $this->scanId = $scanId;
    }

    /**
     * @return string
     */
    public function getScanId(): string
    {
        return $this->scanId;
    }
}
<?php
/**
 * EventManager class file.
 */

namespace AppVersionDetector\Event;

use AppVersionDetector\Core\AbstractEvent;
use AppVersionDetector\Core\ListenerInterface;
use AppVersionDetector\Core\PublisherInterface;
use SplObjectStorage;

/**
 * Class EventManager.
 */
class EventManager implements PublisherInterface
{
    /**
     * @var ListenerInterface[][] list of listeners
     */
    private $listeners;

    /**
     * Subscribe a listener on events.
     *
     * Usage example:
     * $eventManger->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;
    }
}<?php
/**
 * ActualVersionsDb class file.
 */

namespace AppVersionDetector\Storage;

use AppVersionDetector\Core\OutdatedDetection\VersionDbInterface;
use AppVersionDetector\Application\AppException;
use SplFileInfo;

/**
 * Class ActualVersionsDb.
 */
class ActualVersionsDb implements VersionDbInterface
{
    /**
     * @var array|null
     */
    private $db;
    /**
     * @var SplFileInfo path to a JSON DB file
     */
    private $path;

    /**
     * ActualVersionsDb constructor.
     *
     * @param SplFileInfo $path
     * @throws Exception
     */
    public function __construct(SplFileInfo $path)
    {
        $this->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;
    }
}<?php
/**
 * SqliteDbReportConnection class file.
 */

namespace AppVersionDetector\Storage;

use Exception;
use SQLite3;

/**
 * Class SqliteDbReportConnection.
 */
class SqliteDbReportConnection
{
    const BUSY_TIMEOUT_MSEC = 10000;

    /**
     * @var SQLite3Stmt
     */
    private $reportStatement;

    /**
     * @var SQLite3Stmt
     */
    private $appsStatement;

    /**
     * @var SQLite3
     */
    private $dbh;

    /**
     * SqliteDbReportConnection constructor.
     *
     * @param $path
     */
    public function __construct($path)
    {
        $this->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
            )
        ');
    }
}
<?php
/**
 * ActualVersionsDb class file.
 */

namespace AppVersionDetector\Storage;

use AppVersionDetector\Core\OutdatedDetection\VersionDbInterface;
use AppVersionDetector\Application\AppException;
use SQLite3;
use SplFileInfo;

/**
 * Class ActualVersionsSQLiteDb.
 */
class ActualVersionsSQLiteDb implements VersionDbInterface
{
    /**
     * @var SQLite3
     */
    private $dbh;
    
    /**
     * ActualVersionsDb constructor.
     *
     * @param SplFileInfo $path
     * @throws Exception
     */
    public function __construct(SplFileInfo $path)
    {
        if (!file_exists($path)) {
            throw new AppException('Failed loading DB from "' . $path->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();
    }
}
<?php

namespace AppVersionDetector\Application;

/**
 * Class Config.
 */
class Config
{
    /**
     * @var array Configuration data 
     */
    private $config = [];

    /**
     * Returns valued of a particular option.
     *
     * @param string $key
     * @return mixed
     * @throws Exception
     */
    public function get($key)
    {
        if (!array_key_exists($key, $this->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;
    }
}<?php

namespace AppVersionDetector\Application;

/**
 * Class for using profiling
 */
class Profiler
{
    /**
     * @var bool Use profiler
     */
    private static $useProfiler     = false;
    
    /**
     * @var bool Profiler is started
     */
    private static $ProfilerStarted = false;
    
    /**
     * Set on profiler
     *
     * @return void
     */
    public static function onProfiler()
    {
        self::$useProfiler = true;
    }

    /**
     * Set off profiler
     *
     * @return void
     */
    public static function offProfiler()
    {
        self::$useProfiler = false;
    }
    
    /**
     * Start profiling
     *
     * @return void
     */
    public static function startProfiling()
    {
        if (!self::$useProfiler) {
            return;
        }
        if (!function_exists('tideways_xhprof_enable')) {
            return;
        }
        tideways_xhprof_enable();
        self::$ProfilerStarted = true;
    }
    
    /**
     * Stop profiling
     *
     * @return void
     */
    public static function stopProfilling()
    {
        if (!self::$ProfilerStarted) {
            return;
        }
        if (!function_exists('tideways_xhprof_disable')) {
            return;
        }
        file_put_contents(sys_get_temp_dir() . "/" . uniqid() . ".yourapp.xhprof", serialize(tideways_xhprof_disable()));
    }
}<?php
/**
 * DetectorBuilder class file.
 */

namespace AppVersionDetector\Application;

use AppVersionDetector\Core\VersionDetection\DependencyCollectionDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\PlainCollectionDetector;

/**
 * Class DetectorBuilder.
 *
 * @package AppVersionDetector\Application
 */
class DetectorBuilder
{
    /**
     * @return DetectorInterface
     */
    public function build()
    {
        $detector = new PlainCollectionDetector();

        // Drupal
        $drupalDependencyDetector = new DependencyCollectionDetector();
        $drupalDependencyDetector->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;
    }
}<?php

namespace AppVersionDetector\Application;

/**
 * Helper class
 */
class Helper
{
    /**
     * Read part of file end return this data
     *
     * @param string $filepath
     * @param int $bytes
     * @param int $start
     * @return string
     */
    public static function getPartOfFile($filepath, $bytes, $start = 0)
    {
        return file_get_contents($filepath, false, NULL, $start, $bytes);
    }

    /**
     * Try to read version from file $filepath with reading $len bytes and extract version with $regexp
     * @param $filepath
     * @param $len
     * @param $regexp
     * @param $start
     * @return mixed|string
     */
    public static function tryGetVersion($filepath, $len, $regexp, $start = 0)
    {
        if (file_exists($filepath) && is_file($filepath)) {
            Stats::incrementCountFilesReaded();
            $tmp_content = Helper::getPartOfFile($filepath, $len, $start);
            if (preg_match($regexp, $tmp_content, $tmp_ver)) {
                return $tmp_ver;
            }
        }
        return '';
    }
}<?php
/**
 * AppException class file.
 */

namespace AppVersionDetector\Application;

use Exception;

/**
 * Class ConfigException.
 */
class AppException extends Exception
{
    const EXCEPTION_UNKNOWN = 1;
    const EXCEPTION_CONFIG  = 2;
    
    public function __construct(string $message = "", int $code = 0, Throwable $previous = null) 
    {
        $code = !$code ? self::EXCEPTION_UNKNOWN : $code;
        parent::__construct($message, $code, $previous);
    }
}<?php

namespace AppVersionDetector\Application;

/**
 * Class file with static stats data
 */
class Stats 
{
    /**
     * @var bool Accumulate stats or not
     */
    private static $accumulateStats     = false;

    /**
     * @var int The value of memory with which we begin
     */
    private static $memoryUsageStart    = 0;

    /**
     * @var int The value of memory with which we end
     */
    private static $memoryUsageEnd      = 0;

    /**
     * @var int Peak usage memory
     */
    private static $memoryUsagePeak     = 0;
    
    /**
     * @var int Count of readed files
     */
    private static $countFilesReaded    = 0;

    /**
     * @var int Count of opened directories
     */
    private static $countDirsOpened     = 0;

    /**
     * @var int Count of iterated files in diretory
     */
    private static $countFilesIteration = 0;


    /**
     * Set on for accumulate stats
     *
     * @return void
     */
    public static function onAccumulateStats()
    {
        self::$accumulateStats = true;
    }

    /**
     * Set off for accumulate stats
     *
     * @return void
     */
    public static function offAccumulateStats()
    {
        self::$accumulateStats = false;
    }
    
    /**
     * Do we accumulate stats
     *
     * @return bool
     */
    public static function isAccumulateStats()
    {
        return self::$accumulateStats;
    }

    /**
     * To record the current value of the used memory
     *
     * @return void
     */
    public static function setCurrentMemoryUsageStart()
    {
        if (!self::$accumulateStats) {
            return;
        }
        self::$memoryUsageStart = memory_get_usage(true);
    }

    /**
     * Return the amount of memory used at the beginning
     *
     * @return int
     */
    public static function getMemoryUsageStart()
    {
        return self::$memoryUsageStart;
    }
    
    /**
     * To record the peak value of the used memory
     *
     * @return int
     */
    public static function setCurrentMemoryUsagePeak()
    {
        if (!self::$accumulateStats) {
            return;
        }
        self::$memoryUsagePeak = memory_get_usage(true);
    }
    
    /**
     * Return the amount of memory used in the peak
     *
     * @return int
     */
    public static function getMemoryUsagePeak()
    {
        
        return self::$memoryUsagePeak;
    }
    
    /**
     * To record the current value of the used memory. 
     *
     * @return void
     */
    public static function setCurrentMemoryUsageEnd()
    {
        if (!self::$accumulateStats) {
            return;
        }
        self::$memoryUsageEnd = memory_get_peak_usage(true);
    }
    
    /**
     *  Return the amount of memory used in the end
     *
     * @return int
     */
    public static function getMemoryUsageEnd()
    {
        return self::$memoryUsageEnd;
    }
    
    /**
     * Increment file readed counter 
     *
     * @return void
     */
    public static function incrementCountFilesReaded()
    {
        self::$countFilesReaded++;
    }
    
    /**
     * Return file readed counter 
     *
     * @return int
     */
    public static function getCountFilesReaded()
    {
        return self::$countFilesReaded;
    }

    /**
     * Increment dir opened counter 
     *
     * @return void
     */
    public static function incrementCountDirsOpened()
    {
        self::$countDirsOpened++;
    }
    
    /**
     * Return dir opened counter 
     *
     * @return int
     */
    public static function getCountDirsOpened()
    {
        return self::$countDirsOpened;
    }

    /**
     * Increment file iteration counter 
     *
     * @return void
     */
    public static function incrementCountFilesIteration()
    {
        self::$countFilesIteration++;
    }
    
    /**
     * Return file iteration counter 
     *
     * @return int
     */
    public static function getCountFilesIteration()
    {
        return self::$countFilesIteration;
    }
}<?php

namespace AppVersionDetector\Application;

use AppVersionDetector\Scanner;
use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Migraitor;
use AppVersionDetector\Application\Profiler;
use AppVersionDetector\Application\Directory;
use AppVersionDetector\Core\OutdatedDetection\ComparisonStrategyInterface;
use AppVersionDetector\Core\OutdatedDetection\GenericComparisonStrategy;
use AppVersionDetector\Core\OutdatedDetection\OutdatedChecker;
use AppVersionDetector\Event\EventManager;
use AppVersionDetector\Report\AbstractFileBasedReport;
use AppVersionDetector\Report\SqliteDbReport;
use AppVersionDetector\Report\TextReport;
use AppVersionDetector\Report\JsonReport;
use AppVersionDetector\Report\RemoteStatsReport;
use AppVersionDetector\Storage\ActualVersionsDb;
use AppVersionDetector\Storage\SqliteDbReportConnection;

/*
 * Abstract class for MDS which can parse cli command
 */
class AVDCliParse extends CliParse
{
    /**
     * @var array Project options for cli
     */
    protected $opts = [
        AVDConfig::PARAM_HELP               => ['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 <<<HELP
AppVersionDetector allows you to find common CMS and determine their versions.

Usage: php {$_SERVER['PHP_SELF']} [OPTIONS] [PATH]

      --stdin-dirs                      Get a list of directories(or a list of domains and directories separated by ",") to scan from STDIN
      --scan-depth=<NUM>                Nesting Level for CMS search. Default 1
      --json-report=<FILEPATH>          File path with json report
      --text-report                     Output in stdout
      --sqlite-db-report=<FILEPATH>     Path to sqlite database file with report. If an existing database is specified, data is added to it.
      --since=<TIMESTAMP>               Used only with --sqlite-db-report, allows you to scan only new folders and those that were scanned before <TIMESTAMP>.
      --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");
    }
}<?php

namespace AppVersionDetector\Application;

class Directory
{
    /**
     * @var string Dir path
     */
    private $path;
    
    /**
     * @var string Domain name
     */
    private $domain = '';
    
    /**
     * @var int Dir owner id
     */
    private $owner  = null;

    /**
     * Directory constructor.
     *
     * @param string $path 
     * @param string $domain 
     */
    public function __construct(string $path, string $domain = '')
    {
        $this->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;
    }
}<?php

namespace AppVersionDetector\Application;

use Exception;
use SQLite3;

/**
 * Class Migraitor.
 */
class Migraitor
{
    public function migrate(Config $config)
    {
        if ($config->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];
    }
}
<?php

namespace AppVersionDetector\Application;

/*
 * Abstract class for parse cli command
 */
abstract class CliParse
{
    /**
     * @var Config Config for fill
     */
    protected $config = null;
    
    /**
     * @var array List of options. Example of one element: ['short' => '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;
    }
}<?php

namespace AppVersionDetector\Application;

use AppVersionDetector\Application\AppException;

/**
 * Class Factory.
 */
class Factory
{
    /**
     * @var Factory
     */
    private static $instance;
    /**
     * @var array
     */
    private static $config;

    /**
     * Factory constructor.
     *
     * @throws Exception
     */
    private function __construct()
    {

    }

    /**
     * Instantiate and return a factory.
     *
     * @return Factory
     * @throws Exception
     */
    public static function instance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * Configure a factory.
     *
     * This method can be called only once.
     *
     * @param array $config
     * @throws Exception
     */
    public static function configure($config = [])
    {
        if (self::isConfigured()) {
            throw new AppException('The Factory::configure() method can be called only once.');
        }

        self::$config = $config;
    }

    /**
     * Return whether a factory is configured or not.
     *
     * @return bool
     */
    public static function isConfigured()
    {
        return self::$config !== null;
    }

    /**
     * Creates and returns an instance of a particular class.
     *
     * @param string $class
     *
     * @param array $constructorArgs
     * @return mixed
     * @throws Exception
     */
    public function create($class, $constructorArgs = [])
    {
        if (!isset(self::$config[$class])) {
            throw new AppException("The factory is not contains configuration for '{$class}'.");
        }

        if (is_callable(self::$config[$class])) {
            return call_user_func(self::$config[$class], $constructorArgs);
        }
        
        return new self::$config[$class](...$constructorArgs);
    }
}<?php
/**
 * ErrorHandler class file.
 */

namespace AppVersionDetector\Application\Error;

use AppVersionDetector\Application\ConfigParamException;
use AppVersionDetector\Application\AppException;
use Throwable;

/**
 * Class ErrorHandler.
 */
class ErrorHandler
{
    const ERRNUM_CONFIG_PARAM_EXCEPTION = 1;
    const ERRNUM_APP_EXCEPTION          = 2;
    
    /**
     * Handle an error.
     *
     * @param int $errno
     * @param string $errstr
     */
    public function handleError(string $errno, string $errstr)
    {
        $this->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);
    }
}<?php
/**
 * ConfigException class file.
 */

namespace AppVersionDetector\Application;


use AppVersionDetector\Application\AppException;

/**
 * Class ConfigException.
 */
class ConfigParamException extends AppException
{
    public function __construct(string $message = "", int $code = 0, Throwable $previous = null) 
    {
        parent::__construct($message, AppException::EXCEPTION_CONFIG, $previous);
    }
}<?php

namespace AppVersionDetector\Application;

class FileOwners
{
    private $cache = [];
    
    public function getFileOwner($filepath)
    {
        if (!array_key_exists($filepath, $this->cache) && file_exists($filepath)) {
            $this->cache[$filepath] = @fileowner($filepath);
        }
        return $this->cache[$filepath];
    }
}<?php

namespace AppVersionDetector\Application;

use AppVersionDetector\Report\Includes\RemoteStatsRequest;
use AppVersionDetector\Scanner;
use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Profiler;
use AppVersionDetector\Core\OutdatedDetection\ComparisonStrategyInterface;
use AppVersionDetector\Core\OutdatedDetection\GenericComparisonStrategy;
use AppVersionDetector\Core\OutdatedDetection\OutdatedChecker;
use AppVersionDetector\Event\EventManager;
use AppVersionDetector\Report\AbstractFileBasedReport;
use AppVersionDetector\Report\SqliteDbReport;
use AppVersionDetector\Report\TextReport;
use AppVersionDetector\Report\JsonReport;
use AppVersionDetector\Report\RemoteStatsReport;
use AppVersionDetector\Storage\ActualVersionsDb;
use AppVersionDetector\Storage\SqliteDbReportConnection;


class AVDConfig extends Config
{
    const PARAM_HELP                = 'help';
    const PARAM_VERSION             = 'version';
    const PARAM_JSON_REPORT         = 'json-output';
    const PARAM_TEXT_REPORT         = 'text-output';
    const PARAM_SQLITE_DB_REPORT    = 'sqlite-db-report';
    const PARAM_SINCE               = 'since';
    const PARAM_STDIN_DIR           = 'stdin-dirs';
    const PARAM_PATHES_IN_BASE64    = 'pathes-in-base64';
    const PARAM_CHACK_OUTDATED      = 'check-outdated';
    const PARAM_SCAN_DEPTH          = 'scan-depth';
    const PARAM_SEND_STATS          = 'send-stats';
    const PARAM_DB_FILE             = 'db-file';
    const PARAM_DEBUG               = 'debug';
    const PARAM_FACTORY_CONFIG      = 'factory-config';
    const PARAM_TARGET_DIRECTORIES  = 'target-directories';
    const PARAM_MIGRATE             = 'migrate';
    
    /**
     * @var array Default config
     */
    protected $defaultConfig = [
        self::PARAM_HELP                => 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);
    }
}<?php
/**
 * Scanner class file.
 */

namespace AppVersionDetector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Directory;
use AppVersionDetector\Application\AppException;
use AppVersionDetector\Core\PublisherInterface;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Event\ScanningEndedEvent;
use AppVersionDetector\Event\ScanningStartedEvent;
use AppVersionDetector\Event\ScanningDirStartedEvent;
use AppVersionDetector\Event\ScanningDirEndedEvent;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;

/**
 * Class Scanner.
 */
class Scanner
{

    const VERSION = '30.1.2-1';

    /**
     * @var PublisherInterface
     */
    private $publisher;

    /**
     * Scanner constructor.
     *
     * @param PublisherInterface $publisher
     */
    public function __construct(PublisherInterface $publisher)
    {
        $this->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));
    }
}
<?php
/**
 * PublisherInterface file.
 */

namespace AppVersionDetector\Core;


/**
 * Interface PublisherInterface.
 */
interface PublisherInterface
{
    /**
     * An event sender publish an event using this method.
     *
     * @param AbstractEvent $event
     */
    public function update(AbstractEvent $event);
}<?php
/**
 * AbstractEvent class file.
 */

namespace AppVersionDetector\Core;


/**
 * Class AbstractEvent.
 */
class AbstractEvent
{
    /**
     * @var mixed
     */
    private $sender;

    /**
     * Returns an event sender.
     *
     * @return mixed
     */
    public function getSender()
    {
        return $this->sender;
    }

    /**
     * Sets an event sender.
     *
     * @param object $sender
     */
    protected function setSender($sender)
    {
        $this->sender = $sender;
    }
}<?php
/**
 * DependencyCollectionDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection;

use AppVersionDetector\Core\AbstractEvent;
use AppVersionDetector\Core\PublisherInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Core\VersionDetection\AbstractCompositeDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\MediatorInterface;
use SplFileInfo;

/**
 * Class DependencyCollectionDetector.
 *
 * Check if first element detects something. If so, perform other detectors.
 * If first detector doesn't detect any, it doesn't perform other detectors.
 *
 * @package AppVersionDetector\Core
 */
class DependencyCollectionDetector extends AbstractCompositeDetector implements PublisherInterface
{
    /**
     * @var PublisherInterface
     */
    private $publisher;

    /**
     * @param PublisherInterface $publisher
     * @param SplFileInfo $splFileInfo
     */
    public function perform(PublisherInterface $publisher, SplFileInfo $splFileInfo)
    {
        $this->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());
        }
    }
}<?php

namespace AppVersionDetector\Core\VersionDetection;

use AppVersionDetector\Core\AbstractEvent;
use AppVersionDetector\Application\FileOwners;
use SplFileInfo;

/**
 * Class DetectionEventData.
 * 
 * Data transfer object. Need for transfer data about objects(cms, plugins and other)
 */
class DetectionEvent extends AbstractEvent
{
    /*
     * @var string Object name
     */
    private $name = '';
    
    /*
     * @var string Object version
     */
    private $version = '';
    
    /**
     * @var SplFileInfo
     */
    private $path;

    /*
     * @var int Object uid
     */
    private $uid = null;
    
    private static $fileOwners = null;
    
    public static function setFileOwners(FileOwners $fileOwners)
    {
        self::$fileOwners = $fileOwners;
    }

    /**
     * @param object $sender
     * @param SplFileInfo $path
     * @param string $name
     * @param string $version
     */
    public function __construct($sender, SplFileInfo $path, $name, $version)
    {
        $this->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;
    }
}
<?php
/**
 * OsCommerceCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class OsCommerceCoreDetector.
 */
class OsCommerceCoreDetector extends AbstractDetector
{

    private $detector_name = 'oscom_core';

    /**
     * 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|array|null
     * @throws \Exception
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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;
    }
}<?php
/**
 * OpenCartCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Detector\AppVersionDetector;
use AppVersionDetector\Detector\Exception;
use SplFileInfo;

/**
 * Class OpenCartCoreDetector.
 */
class OpenCartCoreDetector extends AbstractDetector
{
    /*
     * @var string Detector name
     */
    private $detector_name = 'opencart_core';

    /**
     * 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)
    {
        $dir        = $splFileInfo->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;
    }
}<?php
/**
 * DrupalCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class DrupalCoreDetector.
 */
class DrupalCoreDetector extends AbstractDetector
{
    /*
     * @var string Detector name
     */
    
    private $detector_name = 'drupal_core';
    
    /**
     * @param SplFileInfo $splFileInfo
     * @return DetectionEvent|array|null
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir            = $splFileInfo->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;
    }
}<?php
/**
 * DummyDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Detector\AppVersionDetector;
use AppVersionDetector\Detector\Exception;
use SplFileInfo;

/**
 * Class DummyDetector.
 */
class DummyDetector extends AbstractDetector
{
    /*
     * @var string Detector name
     */
    
    private $detector_name = 'dummy_core';
    
    /**
     * @var string
     */
    private $version;

    /**
     * DummyDetector constructor.
     *
     * @param string $version
     */
    public function __construct($version = '0.0.0')
    {
        $this->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);
    }
}<?php
/**
 * CommonScriptDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Detector\AppVersionDetector;
use SplFileInfo;

/**
 * Class CommonScriptDetector.
 */
class CommonScriptDetector extends AbstractDetector
{
    /*
     * @var string Detector name
     */

    private $prefix = 'common_script_';

    /**
    * 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 AppVersionDetector\Core\Detector\Version\DetectionEventData|null
    * @throws \Exception
    */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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;
    }
}
<?php
/**
 * WpPluginDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Abstract Class WpComponentDetector.
 */
abstract class WpComponentDetector extends AbstractDetector
{
    protected $prefix               = '';
    protected $components_folder    = '';
    protected $component_name       = '';
    
    /**
     * Implements finding and extracting version.
     *
     * If something is found it return version as a string or array of strings.
     * If no luck it returns null.
     *
     * @param SplFileInfo $splFileInfo
     * @return string|array|null
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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));
    }
}
<?php
/**
 * WpCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Detector\AppVersionDetector;
use AppVersionDetector\Detector\Exception;
use SplFileInfo;

/**
 * Class WpCoreDetector.
 */
class WpCoreDetector extends AbstractDetector
{
    /*
     * @var string Detector name
     */
    
    private $detector_name = 'wp_core';

    /**
     * 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)
    {
        $dir        = $splFileInfo->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;
    }
}<?php
/**
 * WpPluginDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class WpThemeDetector.
 */
class WpThemeDetector extends WpComponentDetector
{
    protected $prefix               = 'wp_theme_';
    protected $components_folder    = 'themes';
    protected $component_name       = 'Theme';
    
    protected function isMainComponentFile($filename)
    {
        return ($filename == 'style.css');
    }    
            
}
<?php
/**
 * JoomlaCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class JoomlaCoreDetector.
 */
class JoomlaCoreDetector extends AbstractDetector
{
    const JOOMLA_CMS_DETECTOR_NAME = 'joomla_core';
    const JOOMLA_PLATFORM_DETECTOR_NAME = 'joomla_platform';

    /**
     * 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|array|null
     * @throws \Exception
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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('~<version>([\d.\-_a-z]+)</version>~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('~<version>([\d.\-_a-z]+)</version>~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;
    }
}<?php

/**
 * JoomlaPluginDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

class JoomlaPluginDetector extends AbstractDetector {

    private $prefix = 'joomla_plugin_';
    private $pluginsData = [];
    private $pluginsFound = [];

    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $this->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('~<version[^>]*>(.*?)</version>~is', $content, $m)) {
            $version = $m[1];
        }
        return $version;
    }

    private function getPluginNameFromXMLContent($content)
    {
        if (preg_match('~<packagename>(.*?)</packagename>~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];
    }
}<?php
/**
 * DrupalPluginDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class DrupalPluginDetector.
 */
class DrupalPluginDetector extends AbstractDetector
{
    private $prefix = 'drupal_plugin_';
    /**
     * Implements finding and extracting version.
     *
     * If something is found it return version as a string or array of strings.
     * If no luck it returns null.
     *
     * @param SplFileInfo $splFileInfo
     * @return string|array|null
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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];
        }
    }
}
<?php
/**
 * IPBCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class IPBCoreDetector.
 */
class IPBCoreDetector extends AbstractDetector
{

    private $detector_name = 'ipb_core';

    /**
     * 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|array|null
     * @throws \Exception
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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, '~(?:<version>\s*\s*<human>([^<]+)</human>\s*<long>\d+</long>\s*</version>\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;
    }
}<?php
/**
 * PHPBB3CoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class PHPBB3CoreDetector.
 */
class PHPBB3CoreDetector extends AbstractDetector
{
    const PHPBB3_CMS_DETECTOR_NAME = 'phpbb3_core';

    /**
     * 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|array|null
     * @throws \Exception
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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;
    }
}<?php
/**
 * ModxCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class ModxCoreDetector.
 */
class ModxCoreDetector extends AbstractDetector
{
    const MODXEVO_CMS_DETECTOR_NAME = 'modxevo_core';
    const MODXREVO_CMS_DETECTOR_NAME = 'modxrevo_core';

    /**
     * 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|array|null
     * @throws \Exception
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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;
    }
}<?php
/**
 * MagentoCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Detector\AppVersionDetector;
use AppVersionDetector\Detector\Exception;
use SplFileInfo;

/**
 * Class MagentoCoreDetector.
 */
class MagentoCoreDetector extends AbstractDetector
{
    /*
     * @var string Detector name
     */
    
    private $detector_name = 'magento_core';

    /**
     * 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)
    {
        $dir        = $splFileInfo->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;
    }
}<?php
/**
 * WpPluginDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class WpPluginDetector.
 */
class WpPluginDetector extends WpComponentDetector
{
    protected $prefix               = 'wp_plugin_';
    protected $components_folder    = 'plugins';
    protected $component_name       = 'Plugin';
    
    protected function isMainComponentFile($filename)
    {
        return (substr($filename, -4) == '.php');
    }    
}
<?php
/**
 * BitrixCoreDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection\Detector;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Application\Helper;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class BitrixCoreDetector.
 */
class BitrixCoreDetector extends AbstractDetector
{
    const BITRIX_CMS_DETECTOR_NAME = 'bitrix_core';

    /**
     * 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|array|null
     * @throws \Exception
     */
    protected function findAndExtractData(SplFileInfo $splFileInfo)
    {
        $dir = $splFileInfo->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;
    }
}<?php
/**
 * AbstractDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection;

use AppVersionDetector\Core\PublisherInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\MediatorInterface;
use AppVersionDetector\Application\AppException;
use SplFileInfo;

/**
 * Class AbstractDetector.
 */
abstract class AbstractDetector implements DetectorInterface
{
    /**
     * Number of files detectors are allowed to iterate per directory.
     *
     * @var int 
     */
    const FILES_PER_DIR_LIMIT = 1000;

    /**
     * Perform detection.
     *
     * @param PublisherInterface $publisher
     * @param SplFileInfo $splFileInfo
     * @throws AppException
     */
    public function perform(PublisherInterface $publisher, SplFileInfo $splFileInfo)
    {
        $data = $this->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);
}<?php
/**
 * DetectorInterface class file.
 */

namespace AppVersionDetector\Core\VersionDetection;

use AppVersionDetector\Core\PublisherInterface;
use SplFileInfo;

/**
 * Class DetectorInterface.
 */
interface DetectorInterface
{
    const UNKNOWN_VERSION = 'unknown';

    /**
     * Perform detection.
     *
     * @param PublisherInterface $publisher
     * @param SplFileInfo $splFileInfo
     */
    public function perform(PublisherInterface $publisher, SplFileInfo $splFileInfo);
}<?php
/**
 * AbstractCompositeDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection;

use AppVersionDetector\Core\PublisherInterface;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\MediatorInterface;
use SplFileInfo;

/**
 * Class AbstractCompositeDetector.
 *
 * @package AppVersionDetector\Core
 */
abstract class AbstractCompositeDetector implements DetectorInterface
{
    /**
     * @var DetectorInterface[]
     */
    protected $detectors;

    /**
     * @param PublisherInterface $publisher
     * @param SplFileInfo $splFileInfo
     */
    abstract public function perform(PublisherInterface $publisher, SplFileInfo $splFileInfo);

    /**
     * Adds a detector to the collection.
     *
     * @param DetectorInterface $detector
     */
    public function add(DetectorInterface $detector)
    {
        $this->detectors[] = $detector;
    }
}<?php
/**
 * PlainCollectionDetector class file.
 */

namespace AppVersionDetector\Core\VersionDetection;

use AppVersionDetector\Core\PublisherInterface;
use AppVersionDetector\Core\VersionDetection\AbstractCompositeDetector;
use SplFileInfo;

/**
 * Class PlainCollectionDetector.
 *
 * @package AppVersionDetector\Core
 */
class PlainCollectionDetector extends AbstractCompositeDetector
{
    /**
     * @param PublisherInterface $publisher
     * @param SplFileInfo $splFileInfo
     */
    public function perform(PublisherInterface $publisher, SplFileInfo $splFileInfo)
    {
        foreach ($this->detectors as $detector) {
            $detector->perform($publisher, $splFileInfo);
        }
    }
}<?php
/**
 * ListenerInterface file.
 */

namespace AppVersionDetector\Core;

use AppVersionDetector\Core\AbstractEvent;

/**
 * Interface ListenerInterface.
 */
interface ListenerInterface
{
    /**
     * Notifies a listener about an event raised.
     *
     * @param AbstractEvent $event
     */
    public function notify(AbstractEvent $event);
}<?php
/**
 * MediatorInterface class file.
 */

namespace AppVersionDetector\Core;

use AppVersionDetector\Core\VersionDetection\DetectorInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use SplFileInfo;

/**
 * Class MediatorInterface.
 */
interface MediatorInterface
{
    /**
     * Notifies about a detection event.
     *
     * @param DetectorInterface $sender
     * @param SplFileInfo $splFileInfo
     * @param DetectionEvent $data
     */
    public function notifyAboutDetection($sender, $splFileInfo, VersionDetection\DetectionEvent $data);

    /**
     * Notifies about a start-detection event.
     *
     * @param string $scannerVersion
     * @param string $directory
     */
    public function notifyAboutScanningStarted($scannerVersion, $directory);
}<?php
/**
 * OutdatedEvent class file.
 */

namespace AppVersionDetector\Core\OutdatedDetection;


use AppVersionDetector\Core\AbstractEvent;

/**
 * Class OutdatedEvent.
 */
class OutdatedEvent extends AbstractEvent
{
    const TYPE_BRANCH   = 'branch_outdated';
    const TYPE_PRODUCT  = 'product_outdated';

    /**
     * @var string
     */
    private $version;
    /**
     * @var string
     */
    private $name;
    /**
     * @var string
     */
    private $type;

    /**
     * OutdatedEvent constructor.
     *
     * @param object $sender
     * @param string $name
     * @param string $version
     * @param string $type
     */
    public function __construct($sender, $name, $version, $type)
    {
        $this->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;
    }
}<?php
/**
 * GenericComparisonStrategy class file.
 */

namespace AppVersionDetector\Core\OutdatedDetection;

use Composer\Semver\Comparator;
use Composer\Semver\VersionParser;

/**
 * Class GenericComparisonStrategy.
 */
class GenericComparisonStrategy implements ComparisonStrategyInterface
{
    /**
     * Check $value lays between $left and $right.
     *
     * Return the followings:
     * 0 - $value is not in range
     * 1 - $left <= $value < $right
     * 2 - $value equals to $right
     *
     * @param string $left
     * @param string $right
     * @param string $value
     * @return integer
     */
    public function compare($left, $right, $value)
    {
        $versionParser = new VersionParser();

        $left   = $this->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;
    }
}<?php

namespace AppVersionDetector\Core\OutdatedDetection;

interface ComparisonStrategyInterface
{
    const NOT_IN_RANGE                  = 0;
    const IN_RANGE_BUT_LOWER_THAN_RIGHT = 1;
    const EQUALS_TO_RIGHT               = 2;

    /**
     * Check $value lays between $left and $right.
     *
     * Return the followings:
     * 0 - $value is not in range
     * 1 - $left <= $value < $right
     * 2 - $value equals to $right
     *
     * @param string $left
     * @param string $right
     * @param string $value
     * @return integer
     */
    public function compare($left, $right, $value);
}<?php

namespace AppVersionDetector\Core\OutdatedDetection;

interface VersionDbInterface
{
    /**
     * Checks if a $name key exists.
     *
     * @param string $key
     * @return boolean
     */
    public function hasKey(string $key);

    /**
     * Returns pairs for a specified key.
     *
     * Note: this method should return an ORDERED list of pairs.
     *
     * @param string $key
     * @return array list of pairs
     */
    public function getByKey(string $key);
}<?php
/**
 * OutdatedChecker class file.
 */

namespace AppVersionDetector\Core\OutdatedDetection;

use AppVersionDetector\Core\AbstractEvent;
use AppVersionDetector\Core\ListenerInterface;
use AppVersionDetector\Core\PublisherInterface;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Core\VersionDetection\DetectorInterface;

/**
 * Class OutdatedChecker.
 */
class OutdatedChecker implements ListenerInterface
{
    /**
     * @var ComparisonStrategyInterface
     */
    private $comparisonStrategy;
    /**
     * @var PublisherInterface
     */
    private $publisher;
    /**
     * @var VersionDbInterface
     */
    private $db;

    /**
     * OutdatedChecker constructor.
     *
     * @param PublisherInterface $publisher
     * @param VersionDbInterface $db
     * @param ComparisonStrategyInterface $comparisonStrategy
     */
    public function __construct($publisher, $db, $comparisonStrategy)
    {
        $this->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;
           }
       }
    }
}<?php
/**
 * TextReport class file.
 */

namespace AppVersionDetector\Report;

use AppVersionDetector\Core\OutdatedDetection\OutdatedEvent;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Event\ScanningEndedEvent;
use AppVersionDetector\Event\ScanningStartedEvent;
use AppVersionDetector\Event\ScanningDirStartedEvent;
use AppVersionDetector\Event\ScanningDirEndedEvent;

/**
 * Class TextReport.
 */
class TextReport extends AbstractFileBasedReport
{
    /**
     * Reacts on a detection event.
     *
     * @param DetectionEvent $event
     */
    protected function reactOnDetection(DetectionEvent $event)
    {
        $this->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)
    {
    }
}<?php
/**
 * JsonReport class file.
 */

namespace AppVersionDetector\Report;

use AppVersionDetector\Application\Stats;
use AppVersionDetector\Core\OutdatedDetection\OutdatedEvent;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Event\ScanningEndedEvent;
use AppVersionDetector\Event\ScanningStartedEvent;
use AppVersionDetector\Event\ScanningDirStartedEvent;
use AppVersionDetector\Event\ScanningDirEndedEvent;

/**
 * Class JsonReport.
 */
class JsonReport extends AbstractFileBasedReport
{
    /**
     * @var array
     */
    private $report = [
        'app_detector_version'  => 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));
    }
}<?php

namespace AppVersionDetector\Report\Includes;

/**
 * Class RemoteStatsRequest Send statistics to remote server
 * @package AppVersionDetector\Report\Includes
 */
class RemoteStatsRequest
{
    const API_URL    = 'https://api.imunify360.com/api/app-version-save';
    const API_V2_URL = 'https://api.imunify360.com/api/v2/app-version-save';

    private $timeout;
    private $iaid_token;

    /**
     * RemoteStatsRequest constructor.
     * @param int $timeout
     */
    public function __construct($iaid_token = null, $timeout = 10)
    {
        $this->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);
    }
}
<?php
/**
 * SqliteDbReport class file.
 */

namespace AppVersionDetector\Report;

use AppVersionDetector\Core\OutdatedDetection\OutdatedEvent;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Event\ScanningStartedEvent;
use AppVersionDetector\Event\ScanningDirStartedEvent;
use AppVersionDetector\Event\ScanningDirEndedEvent;
use AppVersionDetector\Event\ScanningEndedEvent;
use AppVersionDetector\Storage\SqliteDbReportConnection;

use AppVersionDetector\Core\VersionDetection\Detector\WpCoreDetector;
use AppVersionDetector\Core\VersionDetection\Detector\WpPluginDetector;
use AppVersionDetector\Core\VersionDetection\Detector\WpThemeDetector;
use AppVersionDetector\Core\VersionDetection\Detector\JoomlaCoreDetector;
use AppVersionDetector\Core\VersionDetection\Detector\JoomlaPluginDetector;
use AppVersionDetector\Core\VersionDetection\Detector\DrupalCoreDetector;
use AppVersionDetector\Core\VersionDetection\Detector\DrupalPluginDetector;
use AppVersionDetector\Core\VersionDetection\Detector\CommonScriptDetector;

/**
 * Class SqliteDbReport.
 */
class SqliteDbReport extends AbstractReport
{
    /**
     * @var SqliteDbReportConnection
     */
    private $connection;
    
    /**
     * @var array
     */
    private $data = [];

    /**
     * @var int
     */
    private $uid = null;

    /**
     * @var string
     */
    private $dir = '';
    
    /**
     * @var string
     */
    private $scanId;

    /**
     * @var string
     */
    private $domain;

    /**
     * @var \AppVersionDetector\Application\Directory
     */
    private $directory;

    /**
     * @var array
     */
    private $parenthood = [
        WpCoreDetector::class => [
            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;
    }
}<?php
/**
 * AbstractFileBasedReport class file.
 */

namespace AppVersionDetector\Report;


/**
 * Class AbstractFileBasedReport.
 */
abstract class AbstractFileBasedReport extends AbstractReport
{
    /**
     * @var string
     */
    protected $targetFile;

    /**
     * AbstractReport constructor.
     *
     * @param string $targetFile
     */
    public function __construct($targetFile = 'php://stdout')
    {
        $this->targetFile = $targetFile;
    }
}<?php
/**
 * RemoteStatsReport class file.
 */

namespace AppVersionDetector\Report;

use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Event\ScanningStartedEvent;
use AppVersionDetector\Event\ScanningEndedEvent;
use AppVersionDetector\Event\ScanningDirStartedEvent;
use AppVersionDetector\Event\ScanningDirEndedEvent;
use AppVersionDetector\Core\OutdatedDetection\OutdatedEvent;
use AppVersionDetector\Report\Includes\RemoteStatsRequest;
use SplFileInfo;

/**
 * Class RemoteStatsReport.
 */
class RemoteStatsReport extends AbstractReport
{

    const MAX_RECORDS_PER_REQUEST = 1000;

    /**
     * @var array
     */
    private $report = [
        'system_id'  => '',
        '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('~<name>\s*system_id\s*</name>\s*<value>\s*<string>\s*([^<]+)\s*</string>~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);
        }
    }
}
<?php
/**
 * AbstractReport class file.
 */

namespace AppVersionDetector\Report;

use AppVersionDetector\Core\AbstractEvent;
use AppVersionDetector\Core\OutdatedDetection\OutdatedEvent;
use AppVersionDetector\Core\VersionDetection\AbstractDetector;
use AppVersionDetector\Core\VersionDetection\DetectionEvent;
use AppVersionDetector\Core\ListenerInterface;
use AppVersionDetector\Event\ScanningEndedEvent;
use AppVersionDetector\Event\ScanningStartedEvent;
use AppVersionDetector\Event\ScanningDirStartedEvent;
use AppVersionDetector\Event\ScanningDirEndedEvent;
use SplFileInfo;

/**
 * Class AbstractReport.
 */
abstract class AbstractReport implements ListenerInterface
{
    const UNKNOWN_DETECTOR = 'unknown';

    /**
     * Notifies about an event.
     *
     * @param AbstractEvent $event
     */
    final public function notify(AbstractEvent $event)
    {
        switch (true) {
            case $event instanceof DetectionEvent:
                $this->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@  ��.�
!�D�m!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)h�7tableappappCREATE TABLE app ( id       INTEGER PRIMARY KEY AUTOINCREMENT,name     TEXT NOT NULL)
+F�����vY<���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-wp3wp_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!��������udRA0
�
�
�
�
�
�
�
}
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