<?php

namespace OxidCommunity\ModuleInternals\Core;

use OxidEsales\Eshop\Core\Config;
use OxidEsales\Eshop\Core\DatabaseProvider;
use OxidEsales\Eshop\Core\Registry;
use OxidEsales\Eshop\Core\SettingsHandler;
use OxidEsales\Eshop\Core\Module\Module;
use OxidEsales\Eshop\Core\Module\ModuleInstaller;
use OxidEsales\Eshop\Core\Module\ModuleCache;
use OxidEsales\Eshop\Core\Exception\ModuleValidationException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
use OxidEsales\Eshop\Core\Module\ModuleVariablesLocator;
/**
 * Module state fixer
 */
class ModuleStateFixer extends ModuleInstaller
{

    public function __construct($cache = null, $cleaner = null){
        $cleaner = oxNew(ModuleExtensionCleanerDebug::class);
        $this->output = $this->getLogger();
        parent::__construct($cache, $cleaner);
    }

    /**
     * @var $output LoggerInterface
     */
    protected $output;

    protected $needCacheClear = false;
    protected $initialCacheClearDone = false;
    protected $initialDone = false;
    protected $isRunning = false;

    /**
     * @var null|Module $module
     */
    protected $module = null;

    protected $dryRun = false;

    protected $moduleList;
    protected $modules;

    public function setConfig($config)
    {
        parent::setConfig($config);
        Registry::set(\OxidEsales\Eshop\Core\Config::class, $config);
    }

    public function disableInitialCacheClear(){
        $this->initialCacheClearDone = true;
    }

    protected function init()
    {
        if (!$this->initDone) {
            if ($this->isRunning) {
                return false;
            }
            $this->isRunning = true;
            $this->moduleList = Registry::get('oxModuleList');
            $this->moduleList->getModulesFromDir(Registry::getConfig()->getModulesDir());
            $this->modules = $this->moduleList->getList();
            $this->initDone = true;

            if (!$this->initialCacheClearDone) {

                //clearing some cache to be sure that fix runs not against a stale cache
                ModuleVariablesLocator::resetModuleVariables();
                if (extension_loaded('apc') && ini_get('apc.enabled')) {
                    apc_clear_cache();
                }
                $this->output->debug("initial cache cleared");
                $this->initialCacheClearDone = true;
            }
            $this->isRunning = false;
        }
        return true;
    }

    /**
     * Fix module states task runs version, extend, files, templates, blocks,
     * settings and events information fix tasks
     *
     * @param Module      $module
     * @param Config|null $oConfig If not passed uses default base shop config
     */
    public function fix($module, $oConfig = null)
    {
        if ($oConfig !== null) {
            $this->setConfig($oConfig);
        }

        $moduleId = $module->getId();
        $this->needCacheClear = false;

        if ($this->init()) {
            $this->module = $module;
            $this->restoreModuleInformation($module, $moduleId);
        }
        $somethingWasFixed = $this->needCacheClear;
        $this->clearCache($module);
        return $somethingWasFixed;
    }

    /**
     * After fixing all modules call this method to clean up trash that is not related to any installed module
     */
    public function cleanUp() {
        if ($this->init()) {
            $this->cleanUpControllers();
            $this->cleanUpExtensions();
        }
    }

    /**
     * remove extensions that are not registered by any module
     */
    public function cleanUpExtensions(){

        //get all extions from all metadata
        $oxModuleList = $this->moduleList;
        $aModules = $oxModuleList->getList();

        //get extensions from metadata file
        $moduleClassesMf = [];
        foreach ($aModules as $module) {
            $extensions = $module->getExtensions();
            foreach ($extensions as $moduleClass) {
                $moduleClassesMf[$moduleClass] = 1;
            }
        }

        //get all extesions from db
        $extensionChainDb = Registry::getConfig()->getConfigParam('aModules');
        $extensionChainDb = $oxModuleList->parseModuleChains($extensionChainDb);

        //calculate trash as extensions that are only in db
        foreach ($extensionChainDb as $oxidClass => &$arrayOfExtendingClasses) {

            foreach ($arrayOfExtendingClasses as $key => $extendingClass){
                if (!isset($moduleClassesMf[$extendingClass])) {
                    $this->output->warning("module extension trash found: '$extendingClass'' (registered for $oxidClass)");
                    unset($arrayOfExtendingClasses[$key]);
                }
            }
            $arrayOfExtendingClasses = array_values($arrayOfExtendingClasses);
            if (empty($arrayOfExtendingClasses)) {
                unset($extensionChainDb[$oxidClass]);
            }
        }

        if (!$this->dryRun) {
            $extensionChainDb = $this->buildModuleChains($extensionChainDb);
            $this->_saveToConfig('aModules', $extensionChainDb);
        }

    }

    /**
     * Add module template files to config for smarty.
     *
     * @param array  $aModuleTemplates Module templates array
     * @param string $sModuleId        Module id
     */
    protected function _addTemplateFiles($aModuleTemplates, $sModuleId)
    {
        $aTemplates = (array) Registry::getConfig()->getConfigParam('aModuleTemplates');
        $old = isset($aTemplates[$sModuleId]) ? $aTemplates[$sModuleId] : null;
        if (is_array($aModuleTemplates)) {
            $diff = $this->diff($old,$aModuleTemplates);
            if ($diff) {
                $what = $old === null ? ' everything ' :  var_export($diff, true);
                $this->output->warning("$sModuleId fixing templates");
                $this->output->debug(" $what");
                $aTemplates[$sModuleId] = $aModuleTemplates;
                $this->_saveToConfig('aModuleTemplates', $aTemplates);
                $this->needCacheClear = true;
            }
        } else {
            if ($old) {
                $this->output->warning("$sModuleId unregister templates:");
                $this->_deleteTemplateFiles($sModuleId);
                $this->needCacheClear = true;
            }
        }
    }

    /**
     * Add module files to config for auto loader.
     *
     * @param array  $aModuleFiles Module files array
     * @param string $sModuleId    Module id
     */
    protected function _addModuleFiles($aModuleFiles, $sModuleId)
    {
        $aFiles = (array) Registry::getConfig()->getConfigParam('aModuleFiles');

        $old =  isset($aFiles[$sModuleId]) ? $aFiles[$sModuleId] : null;
        if ($aModuleFiles !== null) {
            $aModuleFiles = array_change_key_case($aModuleFiles, CASE_LOWER);
        }

        if (is_array($aModuleFiles)) {
            $diff = $this->diff($old,$aModuleFiles);
            if ($diff) {
                $what = $old === null ? ' everything' : var_export($diff, true);
                $this->output->warning("$sModuleId fixing files");
                $this->output->debug(" $what");
                $aFiles[$sModuleId] = $aModuleFiles;
                $this->_saveToConfig('aModuleFiles', $aFiles);
                $this->needCacheClear = true;
            }
        } else {
            if ($old) {
                $this->output->warning("$sModuleId unregister files");
                $this->_deleteModuleFiles($sModuleId);
                $this->needCacheClear = true;
            }
        }

    }

    /**
     * Add module events to config.
     *
     * @param array  $aModuleEvents Module events
     * @param string $sModuleId     Module id
     */
    protected function _addModuleEvents($aModuleEvents, $sModuleId)
    {
        $aEvents = (array) Registry::getConfig()->getConfigParam('aModuleEvents');
        $old =  isset($aEvents[$sModuleId]) ? $aEvents[$sModuleId] : null;
        if (is_array($aModuleEvents) && count($aModuleEvents)) {
            $diff = $this->diff($old,$aModuleEvents);
            if ($diff) {
                $aEvents[$sModuleId] = $aModuleEvents;
                $what = $old == null ? ' everything ' : var_export($diff, true);
                $this->output->warning("$sModuleId fixing module events");
                $this->output->debug(" $what");
                $this->_saveToConfig('aModuleEvents', $aEvents);
                $this->needCacheClear = true;
            }
        } else {
            if ($old) {
                $this->output->info("$sModuleId unregister events");
                $this->_deleteModuleEvents($sModuleId);
                $this->needCacheClear = true;
            }
        }

    }

    /**
     * Add module id with extensions to config.
     *
     * @param array  $moduleExtensions Module version
     * @param string $moduleId         Module id
     */
    protected function _addModuleExtensions($moduleExtensions, $moduleId)
    {
        $extensions = (array) Registry::getConfig()->getConfigParam('aModuleExtensions');
        $old = isset($extensions[$moduleId]) ? $extensions[$moduleId] : null;
        $old = (array) $old;
        $new = $moduleExtensions === null ? [] : array_values($moduleExtensions);
        if (is_array($moduleExtensions)) {
            $diff = $this->diff($old, $new);
            if ($diff) {
                $extensions[$moduleId] = array_values($moduleExtensions);
                $what =  $old === null ? ' everything ' : var_export($diff, true);
                $this->output->warning("$moduleId fixing module extensions");
                $this->output->debug(" $what");

                $this->_saveToConfig('aModuleExtensions', $extensions);
                $this->needCacheClear = true;
            }
        } else {
            $this->output->warning("$moduleId unregister module extensions");
            $this->needCacheClear = true;
            $this->_saveToConfig('aModuleExtensions', []);
        }
    }

    /**
     * Add module version to config.
     *
     * @param string $sModuleVersion Module version
     * @param string $sModuleId      Module id
     */
    protected function _addModuleVersion($sModuleVersion, $sModuleId)
    {
        $aVersions = (array) Registry::getConfig()->getConfigParam('aModuleVersions');
        $old =  isset($aVersions[$sModuleId]) ? $aVersions[$sModuleId] : '';
        if (isset($sModuleVersion)) {
            if ($old !== $sModuleVersion) {
                $aVersions[$sModuleId] = $sModuleVersion;
                if($old == '') {
                    $this->output->info("register module '$sModuleId' with version $sModuleVersion");
                } else {
                    $this->output->warning("$sModuleId fixing module version from $old to $sModuleVersion");
                }
                $this->_saveToConfig('aModuleVersions', $aVersions);
                $this->needCacheClear = true;
            }
        } else {
            if ($old) {
                $this->output->info("$sModuleId unregister module version");
                $this->_deleteModuleVersions($sModuleId);
                $this->needCacheClear = true;
            }
        }

    }

    /**
     * compares 2 assoc arrays
     * true if there is something changed
     * @param $array1
     * @param $array2
     * @return bool
     */
    protected function diff($array1,$array2){
        if ($array1 === null) {
            if ($array2 === null) {
                return false; //indicate no diff
            }
            return $array2; //full array2 is new
        }
        if ($array2 === null) {
            //indicate that diff is there  (so return a true value) but everthing should be droped
            return 'null';
        }
        $diff = array_merge(array_diff_assoc($array1,$array2),array_diff_assoc($array2,$array1));
        return $diff;
    }

    /**
     * Code taken from OxidEsales\EshopCommunity\Core\Module::activate
     *
     * @param Module $module
     * @param string $moduleId
     *
     * @throws \OxidEsales\Eshop\Core\Exception\StandardException
     */
    private function restoreModuleInformation($module, $moduleId)
    {
        $active = $this->isActive($moduleId);

        $this->fixExtensions($module);
        $metaDataVersion = $module->getMetaDataVersion();
        $metaDataVersion = $metaDataVersion == '' ? $metaDataVersion = "1.0" : $metaDataVersion;
        if (version_compare($metaDataVersion, '2.0', '<')) {
            $this->_addModuleFiles($active ? $module->getInfo("files") : [], $moduleId);
        }
        $this->_addTemplateBlocks($active ? $module->getInfo("blocks") : [], $moduleId);
        $this->_addTemplateFiles($active ? $module->getInfo("templates"): [], $moduleId);
        $this->_addModuleSettings($module->getInfo("settings"), $moduleId);
        $this->_addModuleVersion($active ? $module->getInfo("version") : null, $moduleId);

        $this->_addModuleEvents($active ? $module->getInfo("events") : [], $moduleId);

        if (version_compare($metaDataVersion, '2.0', '>=')) {
            try {
                $moduleControllers = $active ? $module->getControllers() : [];
                $this->setModuleControllers($moduleControllers, $moduleId, $module);
            } catch (ModuleValidationException $exception) {
                print "[ERROR]: duplicate controllers:" . $exception->getMessage() ."\n";
            }
        }
    }

    protected function isActive($sId)
    {
        return !in_array($sId, (array) Registry::getConfig()->getConfigParam('aDisabledModules'));
    }

    /**
     * Get config tables specific module id
     *
     * @param string $moduleId
     * @return string
     */
    protected function getModuleConfigId($moduleId)
    {
        return 'module:' . $moduleId;
    }

    /**
     * Adds settings to database.
     *
     * @param array  $moduleSettings Module settings array
     * @param string $moduleId       Module id
     */
    protected function _addModuleSettings($moduleSettings, $moduleId)
    {
        $config = Registry::getConfig();
        $shopId = $config->getShopId();
        if (is_array($moduleSettings)) {
            $diff = false;
            foreach ($moduleSettings as $setting) {

                $module = $this->getModuleConfigId($moduleId);
                $name = $setting["name"];
                $type = $setting["type"];

                if (isset($setting["value"]) && is_null($config->getConfigParam($name))){
                    $diff = true;
                    $value = $setting["value"];
                    $config->saveShopConfVar($type, $name, $value, $shopId, $module);
                    $this->output->debug("$moduleId: setting for '$name' fixed'");
                } ;
            }
            if ($diff) {
                $this->output->warning("$moduleId: settings fixed'");
                $this->needCacheClear = true;
            }
        }
    }

    /**
     * Add controllers map for a given module Id to config
     *
     * @param array  $moduleControllers Map of controller ids and class names
     * @param string $moduleId          The Id of the module
     */
    protected function setModuleControllers($moduleControllers, $moduleId, $module)
    {
        $controllersForThatModuleInDb = $this->getModuleControllerEntries($moduleId);
        $diff = $this->diff($controllersForThatModuleInDb, $moduleControllers);

        if ($diff) {
            $shopId = Registry::getConfig()->getShopId();
            $this->output->warning("in shop $shopId: $moduleId fixing module controllers");
            $this->output->warning(" (in md):"  . var_export($moduleControllers, true));
            $this->output->warning(" (in db):"  . var_export($controllersForThatModuleInDb, true));

            $this->deleteModuleControllers($moduleId);
            $this->resetModuleCache($module);
            if ($moduleControllers) {
                $this->validateModuleMetadataControllersOnActivation($moduleControllers);

                $classProviderStorage = $this->getClassProviderStorage();

                $classProviderStorage->add($moduleId, $moduleControllers);
            }

            $afterControllersForThatModuleInDb = $this->getModuleControllerEntries($moduleId);

            $this->needCacheClear = true;
        }

    }

    public function getModuleControllerEntries($moduleId){
        $dbMap = $this->getAllControllers();
        //for some unknown reasons the core uses lowercase module id to reference controllers
        $moduleIdLc = strtolower($moduleId);
        $controllersForThatModuleInDb = isset($dbMap[$moduleIdLc]) ? $dbMap[$moduleIdLc] : [];
        return $controllersForThatModuleInDb;
    }

    /**
     * @return mixed
     */
    private function getAllControllers()
    {
        $classProviderStorage = $this->getClassProviderStorage();
        $dbMap = $classProviderStorage->get();
        return $dbMap;
    }

    public function cleanUpControllers(){
        $allFromDb = $this->getAllControllers();
        $modules = $this->modules;

        //? is aModuleVersions fixed already in that place
        $modules = array_change_key_case($modules,CASE_LOWER);
        $cleaned = array_intersect_key($allFromDb, $modules);
        if ($this->diff($allFromDb, $cleaned)) {
            $this->needCacheClear = true;
            $this->output->warning(" cleaning up controllers");
            $classProviderStorage = $this->getClassProviderStorage();
            $classProviderStorage->set($cleaned);
        }
    }

    /**
     * Reset module cache
     *
     * @param Module $module
     */
    private function resetModuleCache($module)
    {
        $moduleCache = oxNew(ModuleCache::class, $module);
        $moduleCache->resetCache();
    }

    /**
     * @param $o OutputInterface
     * @deprecated
     */
    public function setDebugOutput($o)
    {
        $this->_debugOutput = $o;
    }

    /**
     * @param $o OutputInterface
     */
    public function setOutput($output)
    {
        $o  = new ConsoleLogger($output);
        $this->output = $o;
        $this->getModuleCleaner()->setOutput($o);
    }

    /**
     * Add extension to module
     *
     * @param \OxidEsales\Eshop\Core\Module\Module $module
     */
    protected function _addExtensions(\OxidEsales\Eshop\Core\Module\Module $module)
    {
        $needFix = $this->checkExtensions($module, $aModulesDefault, $aModules);
        if ($needFix) {
            $this->needCacheClear = true;
            $onlyInAfterFix = array_diff($aModules, $aModulesDefault);
            $onlyInBeforeFix = array_diff($aModulesDefault, $aModules);
            $this->output->info("fixing " . $module->getId());
            foreach ($onlyInAfterFix as $core => $ext) {
                if ($oldChain = $onlyInBeforeFix[$core]) {
                    $newExt = substr($ext, strlen($oldChain));
                    if (!$newExt) {
                        //$newExt = substr($ext, strlen($oldChain));
                        $this->output->debug(" remove ext for $core");
                        $this->output->debug(" old: $oldChain");
                        $this->output->debug(" new: $ext");
                        //return;
                        continue;
                    } else {
                        $this->output->debug(" append $core => ...$newExt");
                    }
                    unset($onlyInBeforeFix[$core]);
                } else {
                    $this->output->debug(" add $core => $ext");
                }
            }
            foreach ($onlyInBeforeFix as $core => $ext) {
                $this->output->debug(" remove $core => $ext");
            }
            $this->_saveToConfig('aModules', $aModules);
        }
    }

    /**
     * Add module templates to database.
     *
     * @deprecated please use setTemplateBlocks this method will be removed because
     * the combination of deleting and adding does unnessery writes and so it does not scale
     * also it's more likely to get race conditions (in the moment the blocks are deleted)
     *
     * @param array  $moduleBlocks Module blocks array
     * @param string $moduleId     Module id
     */
    protected function _addTemplateBlocks($moduleBlocks, $moduleId)
    {
        if (!is_array($moduleBlocks)) {
            $moduleBlocks = array();
        }
        $shopId = Registry::getConfig()->getShopId();
        $db = \OxidEsales\Eshop\Core\DatabaseProvider::getDb();
        $knownBlocks = ['dummy']; // Start with a dummy value to prevent having an empty list in the NOT IN statement.
        $rowsEffected = 0;
        foreach ($moduleBlocks as $moduleBlock) {
            $blockId = md5($moduleId . json_encode($moduleBlock) . $shopId);
            $knownBlocks[] = $blockId;

            $template = $moduleBlock["template"];
            $position = isset($moduleBlock['position']) && is_numeric($moduleBlock['position']) ?
                intval($moduleBlock['position']) : 1;

            $block = $moduleBlock["block"];
            $filePath = $moduleBlock["file"];
            $theme = isset($moduleBlock['theme']) ? $moduleBlock['theme'] : '';

            $sql = "INSERT INTO `oxtplblocks` (`OXID`, `OXSHOPID`, `OXTHEME`, `OXTEMPLATE`, `OXBLOCKNAME`, `OXPOS`, `OXFILE`, `OXMODULE`)
                     VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                     ON DUPLICATE KEY UPDATE
                      `OXID` = VALUES(OXID),
                      `OXSHOPID` = VALUES(OXSHOPID),
                      `OXTHEME` = VALUES(OXTHEME),
                      `OXTEMPLATE` = VALUES(OXTEMPLATE),
                      `OXBLOCKNAME` = VALUES(OXBLOCKNAME),
                      `OXPOS` = VALUES(OXPOS),
                      `OXFILE` = VALUES(OXFILE),
                      `OXMODULE` = VALUES(OXMODULE)";

            $rowsEffected += $db->execute($sql, array(
                $blockId,
                $shopId,
                $theme,
                $template,
                $block,
                $position,
                $filePath,
                $moduleId
            ));
        }

        $listOfKnownBlocks = join(',', $db->quoteArray($knownBlocks));
        $deleteblocks = "DELETE FROM oxtplblocks WHERE OXSHOPID = ? AND OXMODULE = ? AND OXID NOT IN ({$listOfKnownBlocks});";

        $rowsEffected += $db->execute(
            $deleteblocks,
            array($shopId, $moduleId)
        );

        if ($rowsEffected) {
            $this->needCacheClear = true;
        }
    }

    /**
     * @param Module $module
     * @param $aModulesDefault
     * @param $aModules
     */
    protected function checkExtensions(\OxidEsales\Eshop\Core\Module\Module $module, &$aModulesDefault, &$aModules)
    {
        $aModulesDefault = Registry::getConfig()->getConfigParam('aModules');
        //in case someone deleted values from the db using a empty array avoids php warnings
        $aModulesDefault = is_null($aModulesDefault) ? [] : $aModulesDefault;
        $aModules = $this->getModulesWithExtendedClass();

        if ($module->hasExtendClass()) {
            $this->validateMetadataExtendSection($module);
            $aAddModules = $module->getExtensions();
            $aModules = $this->_mergeModuleArrays($aModules, $aAddModules);
        }

        $aModules = $this->buildModuleChains($aModules);
        return $aModulesDefault != $aModules;
    }

    /**
     * @param $module
     */
    public function fixExtensions($module)
    {
        $this->_addExtensions($module);
        $this->_addModuleExtensions($module->getExtensions(), $module->getId());
    }

    /**
     * @param $module Module
     */
    private function clearCache($module)
    {
        if ($this->needCacheClear) {
            $this->resetModuleCache($module);
            $this->output->info("cache cleared for " . $module->getId());
        }
    }

    public function getLogger(){
        if(function_exists("\getLogger")){
            return \getLogger();
        }
        return new FallbackLogger();
    }

}


<?php

namespace OxidCommunity\ModuleInternals\Core;

use Composer\Factory;
use Composer\IO\NullIO;
use Composer\Json\JsonFile;
use Composer\Package\PackageInterface;
use Composer\Repository\FilesystemRepository;
use Composer\Repository\InstalledFilesystemRepository;
use OxidEsales\EshopCommunity\Core\Module\ModuleList;
use OxidEsales\EshopCommunity\Core\Registry;

/**
 * Class Resposible for abstracting Composer access
 * and mapping Oxid modules to Composer packages
 */
class OxidComposerModulesService
{
    protected $list;
    /**
     * get List of packages
     */
    public function getList(){
        if ($this->list !== null){
            return $this->list;
        }
        $list = [];
        $packages = $this->getOxidModulePackages();
        //$moduleList = Registry::get(ModuleList::class);
        //$pathList = $moduleList->getModuleConfigParametersByKey(ModuleList::MODULE_KEY_PATHS);
        //$path2Id = array_flip($pathList);
        $config = Registry::getConfig();
        $paths = $config->getConfigParam('aModulePaths');
        $path2id = array_flip($paths);
        foreach ($packages as $package) {
            $extra = $package->getExtra();
            $oxideshop = isset($extra['oxideshop']) ? $extra['oxideshop'] : [];
            if (isset($oxideshop['target-directory'])) {
                $id = $path2id[$oxideshop['target-directory']];
                $list[$id] = $package;
            }
        }
        $this->list = $list;
        return $list;
    }

    /**
     * @param $moduleId
     * @return bool|PackageInterface
     */
    public function getPackage($moduleId) {
        $list = $this->getList();
        if (!isset($list[$moduleId])) {
            return false;
        }
        return $list[$moduleId];
    }

    /**
     * @param $metadataPath
     * @return string
     */
    protected function getIdFromMetadata($metadataPath)
    {
        include $metadataPath;
        if (isset($aModule)) {
            return $aModule['id'];
        }
        return '';
    }

    /**
     * @return array|\Composer\Package\PackageInterface[]
     */
    public function getOxidModulePackages()
    {
        $packages = $this->getPackages();
        $packages = array_filter($packages, function (PackageInterface $package) {
            return $package->getType() == 'oxideshop-module';
        }
        );
        return $packages;
    }

    /**
     * @return \Composer\Package\PackageInterface[]
     */
    public function getPackages(){
        $localRepository = new InstalledFilesystemRepository(new JsonFile(VENDOR_PATH.'/composer/installed.json'));
        $packages = $localRepository->getPackages();
        return $packages;
    }

    /**
     * @return array[\OxidCommunity\ModuleInternals\Core\InternalModule]
     */
    public function getActiveModules()
    {

        $oConfig  = Registry::getConfig();
        $sModulesDir = $oConfig->getModulesDir();

        $oModuleList = oxNew(ModuleList::Class);
        $aModules = $oModuleList->getModulesFromDir($sModulesDir);
        $aActiveModules =  array_filter($aModules, function($module){return $module->isActive();});

        return $aActiveModules;
    }
}


<?php
/**
 * @package   moduleinternals
 * @category  OXID Module
 * @version   1.0.1
 * @license   GPL3 License http://opensource.org/licenses/GPL
 * @author    Alfonsas Cirtautas / OXID Community
 * @link      https://github.com/OXIDprojects/ocb_cleartmp
 * @see       https://github.com/acirtautas/oxid-module-internals
 */

namespace OxidCommunity\ModuleInternals\Controller\Admin;

use OxidCommunity\ModuleInternals\Core\FixHelper as FixHelper;
use OxidCommunity\ModuleInternals\Core\InternalModule;
use OxidCommunity\ModuleInternals\Core\ModuleStateFixer;
use OxidEsales\Eshop\Application\Controller\Admin\AdminController;
use OxidEsales\Eshop\Core\DatabaseProvider;
use OxidEsales\Eshop\Core\Module\ModuleCache as ModuleCache;
use OxidEsales\Eshop\Core\Module\ModuleList as ModuleList;
use OxidEsales\Eshop\Core\Module\Module as Module;
use OxidEsales\Eshop\Core\Registry;
use Symfony\Component\Console\Output\NullOutput;

/**
 * Module internals tools.
 *
 * @author Oxid Community
 */

/**
 * Module state checker, compares module data across different storage levels (metadata file / database / configuration).
 */
class State extends AdminController
{

    /**
     * @var string
     */
    public $sTemplate = 'state.tpl';

    /** @var Module */
    protected $_oModule;

    /**
     * init current Module
     * State constructor.
     */
    public function __construct()
    {
        $this->getModule();
    }

    /**
     * Get active module object.
     *
     * @return InternalModule
     */
    public function getModule()
    {
        if ($this->_oModule === null) {
            $sModuleId = $this->getEditObjectId();

            $this->addTplParam('oxid', $sModuleId);

            $module = oxNew(Module::class);
            $module->load($sModuleId);
            $this->_oModule = $module;
        }

        return $this->_oModule;
    }

    /**
     * Collect info about module state.
     *
     * @return string
     */
    public function render()
    {
        //valid for all metadata versions
        $module = $this->getModule();
        $state = $module->checkState();
        foreach ($state as $paramName => $paramValue) {
            $this->addTplParam($paramName, $paramValue);
        }

        return $this->sTemplate;
    }

    /**
     * Fix module settings.
     */
    public function block()
    {
        $request = Registry::getRequest();
        $data = $request->getRequestParameter('data');
        DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->execute(
        "UPDATE oxtplblocks SET OXACTIVE = NOT OXACTIVE WHERE OXID = ?",[$data]);
    }

}


<?php
/**
 * Part of the Joomla Framework Http Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Http\Transport;

use Joomla\Http\Exception\InvalidResponseCodeException;
use Joomla\Http\Response;
use Joomla\Http\TransportInterface;
use Joomla\Uri\Uri;
use Joomla\Uri\UriInterface;

/**
 * HTTP transport class for using sockets directly.
 *
 * @since  1.0
 */
class Socket implements TransportInterface
{
	/**
	 * Reusable socket connections.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $connections;

	/**
	 * The client options.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   array|\ArrayAccess  $options  Client options array.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('Cannot use a socket transport when fsockopen() is not available.');
		}

		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;
	}

	/**
	 * Send a request to the server and return a Response object with the response.
	 *
	 * @param   string        $method     The HTTP method for sending the request.
	 * @param   UriInterface  $uri        The URI to the resource to request.
	 * @param   mixed         $data       Either an associative array or a string to be sent with the request.
	 * @param   array         $headers    An array of request headers to send with the request.
	 * @param   integer       $timeout    Read timeout in seconds.
	 * @param   string        $userAgent  The optional user agent string to send with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function request($method, UriInterface $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
	{
		$connection = $this->connect($uri, $timeout);

		// Make sure the connection is alive and valid.
		if (\is_resource($connection))
		{
			// Make sure the connection has not timed out.
			$meta = stream_get_meta_data($connection);

			if ($meta['timed_out'])
			{
				throw new \RuntimeException('Server connection timed out.');
			}
		}
		else
		{
			throw new \RuntimeException('Not connected to server.');
		}

		// Get the request path from the URI object.
		$path = $uri->toString(array('path', 'query'));

		// If we have data to send make sure our request is setup for it.
		if (!empty($data))
		{
			// If the data is not a scalar value encode it to be sent with the request.
			if (!is_scalar($data))
			{
				$data = http_build_query($data);
			}

			if (!isset($headers['Content-Type']))
			{
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
			}

			// Add the relevant headers.
			$headers['Content-Length'] = \strlen($data);
		}

		// Configure protocol version, use transport's default if not set otherwise.
		$protocolVersion = isset($this->options['protocolVersion']) ? $this->options['protocolVersion'] : '1.0';

		// Build the request payload.
		$request   = array();
		$request[] = strtoupper($method) . ' ' . ((empty($path)) ? '/' : $path) . ' HTTP/' . $protocolVersion;

		if (!isset($headers['Host']))
		{
			$request[] = 'Host: ' . $uri->getHost();
		}

		// If an explicit user agent is given use it.
		if (isset($userAgent))
		{
			$headers['User-Agent'] = $userAgent;
		}

		// If we have a username then we include basic authentication credentials.
		if ($uri->getUser())
		{
			$authString               = $uri->getUser() . ':' . $uri->getPass();
			$headers['Authorization'] = 'Basic ' . base64_encode($authString);
		}

		// If there are custom headers to send add them to the request payload.
		if (\is_array($headers))
		{
			foreach ($headers as $k => $v)
			{
				$request[] = $k . ': ' . $v;
			}
		}

		// Authentication, if needed
		if (isset($this->options['userauth'], $this->options['passwordauth']))
		{
			$request[] = 'Authorization: Basic ' . base64_encode($this->options['userauth'] . ':' . $this->options['passwordauth']);
		}

		// Set any custom transport options
		if (isset($this->options['transport.socket']))
		{
			foreach ($this->options['transport.socket'] as $value)
			{
				$request[] = $value;
			}
		}

		// If we have data to send add it to the request payload.
		if (!empty($data))
		{
			$request[] = null;
			$request[] = $data;
		}

		// Send the request to the server.
		fwrite($connection, implode("\r\n", $request) . "\r\n\r\n");

		// Get the response data from the server.
		$content = '';

		while (!feof($connection))
		{
			$content .= fgets($connection, 4096);
		}

		$content = $this->getResponse($content);

		// Follow Http redirects
		if ($content->code >= 301 && $content->code < 400 && isset($content->headers['Location']))
		{
			return $this->request($method, new Uri($content->headers['Location']), $data, $headers, $timeout, $userAgent);
		}

		return $content;
	}

	/**
	 * Method to get a response object from a server response.
	 *
	 * @param   string  $content  The complete server response, including headers.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 * @throws  \UnexpectedValueException
	 * @throws  InvalidResponseCodeException
	 */
	protected function getResponse($content)
	{
		// Create the response object.
		$return = new Response;

		if (empty($content))
		{
			throw new \UnexpectedValueException('No content in response.');
		}

		// Split the response into headers and body.
		$response = explode("\r\n\r\n", $content, 2);

		// Get the response headers as an array.
		$headers = explode("\r\n", $response[0]);

		// Set the body for the response.
		$return->body = empty($response[1]) ? '' : $response[1];

		// Get the response code from the first offset of the response headers.
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);
		$code = $matches[0];

		if (is_numeric($code))
		{
			$return->code = (int) $code;
		}
		else
		{
			// No valid response code was detected.
			throw new InvalidResponseCodeException('No HTTP response code found.');
		}

		// Add the response headers to the response object.
		foreach ($headers as $header)
		{
			$pos                                             = strpos($header, ':');
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
		}

		return $return;
	}

	/**
	 * Method to connect to a server and get the resource.
	 *
	 * @param   UriInterface  $uri      The URI to connect with.
	 * @param   integer       $timeout  Read timeout in seconds.
	 *
	 * @return  resource  Socket connection resource.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function connect(UriInterface $uri, $timeout = null)
	{
		$errno = null;
		$err   = null;

		// Get the host from the uri.
		$host = ($uri->isSsl()) ? 'ssl://' . $uri->getHost() : $uri->getHost();

		// If the port is not explicitly set in the URI detect it.
		if (!$uri->getPort())
		{
			$port = ($uri->getScheme() == 'https') ? 443 : 80;
		}

		// Use the set port.
		else
		{
			$port = $uri->getPort();
		}

		// Build the connection key for resource memory caching.
		$key = md5($host . $port);

		// If the connection already exists, use it.
		if (!empty($this->connections[$key]) && \is_resource($this->connections[$key]))
		{
			// Connection reached EOF, cannot be used anymore
			$meta = stream_get_meta_data($this->connections[$key]);

			if ($meta['eof'])
			{
				if (!fclose($this->connections[$key]))
				{
					throw new \RuntimeException('Cannot close connection');
				}
			}

			// Make sure the connection has not timed out.
			elseif (!$meta['timed_out'])
			{
				return $this->connections[$key];
			}
		}

		if (!is_numeric($timeout))
		{
			$timeout = ini_get('default_socket_timeout');
		}

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		// PHP sends a warning if the uri does not exists; we silence it and throw an exception instead.
		// Attempt to connect to the server
		$connection = @fsockopen($host, $port, $errno, $err, $timeout);

		if (!$connection)
		{
			if (!$php_errormsg)
			{
				// Error but nothing from php? Create our own
				$php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
			}

			// Restore error tracking to give control to the exception handler
			ini_set('track_errors', $trackErrors);

			throw new \RuntimeException($php_errormsg);
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $trackErrors);

		// Since the connection was successful let's store it in case we need to use it later.
		$this->connections[$key] = $connection;

		// If an explicit timeout is set, set it.
		if (isset($timeout))
		{
			stream_set_timeout($this->connections[$key], (int) $timeout);
		}

		return $this->connections[$key];
	}

	/**
	 * Method to check if http transport socket available for use
	 *
	 * @return  boolean   True if available else false
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return \function_exists('fsockopen') && \is_callable('fsockopen');
	}
}


<?php
/**
 * Part of the Joomla Framework Http Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Http;

/**
 * HTTP factory class.
 *
 * @since  1.0
 */
class HttpFactory
{
	/**
	 * Method to create an Http instance.
	 *
	 * @param   array|\ArrayAccess  $options   Client options array.
	 * @param   array|string        $adapters  Adapter (string) or queue of adapters (array) to use for communication.
	 *
	 * @return  Http
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 * @throws  \RuntimeException
	 */
	public static function getHttp($options = array(), $adapters = null)
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		if (!$driver = self::getAvailableDriver($options, $adapters))
		{
			throw new \RuntimeException('No transport driver available.');
		}

		return new Http($options, $driver);
	}

	/**
	 * Finds an available TransportInterface object for communication
	 *
	 * @param   array|\ArrayAccess  $options  Options for creating TransportInterface object
	 * @param   array|string        $default  Adapter (string) or queue of adapters (array) to use
	 *
	 * @return  TransportInterface|boolean  Interface sub-class or boolean false if no adapters are available
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public static function getAvailableDriver($options = array(), $default = null)
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		if ($default === null)
		{
			$availableAdapters = self::getHttpTransports();
		}
		else
		{
			settype($default, 'array');
			$availableAdapters = $default;
		}

		// Check if there is at least one available http transport adapter
		if (!\count($availableAdapters))
		{
			return false;
		}

		foreach ($availableAdapters as $adapter)
		{
			/** @var  $class  TransportInterface */
			$class = 'Joomla\\Http\\Transport\\' . ucfirst($adapter);

			if (class_exists($class))
			{
				if ($class::isSupported())
				{
					return new $class($options);
				}
			}
		}

		return false;
	}

	/**
	 * Get the HTTP transport handlers
	 *
	 * @return  array  An array of available transport handlers
	 *
	 * @since   1.0
	 */
	public static function getHttpTransports()
	{
		$names    = array();
		$iterator = new \DirectoryIterator(__DIR__ . '/Transport');

		/** @var  $file  \DirectoryIterator */
		foreach ($iterator as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if ($file->isFile() && $file->getExtension() == 'php')
			{
				$names[] = substr($fileName, 0, strrpos($fileName, '.'));
			}
		}

		// Keep alphabetical order across all environments
		sort($names);

		// If curl is available set it to the first position
		$key = array_search('Curl', $names);

		if ($key)
		{
			unset($names[$key]);
			array_unshift($names, 'Curl');
		}

		return $names;
	}
}


<?php
/**
 * Part of the Joomla Framework Http Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Http\Transport;

use Composer\CaBundle\CaBundle;
use Joomla\Http\Exception\InvalidResponseCodeException;
use Joomla\Http\Response;
use Joomla\Http\TransportInterface;
use Joomla\Uri\UriInterface;

/**
 * HTTP transport class for using cURL.
 *
 * @since  1.0
 */
class Curl implements TransportInterface
{
	/**
	 * The client options.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options;

	/**
	 * Constructor. CURLOPT_FOLLOWLOCATION must be disabled when open_basedir or safe_mode are enabled.
	 *
	 * @param   array|\ArrayAccess  $options  Client options array.
	 *
	 * @link    https://www.php.net/manual/en/function.curl-setopt.php
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 * @throws  \RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!\function_exists('curl_init') || !\is_callable('curl_init'))
		{
			throw new \RuntimeException('Cannot use a cURL transport when curl_init() is not available.');
		}

		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;
	}

	/**
	 * Send a request to the server and return a Response object with the response.
	 *
	 * @param   string        $method     The HTTP method for sending the request.
	 * @param   UriInterface  $uri        The URI to the resource to request.
	 * @param   mixed         $data       Either an associative array or a string to be sent with the request.
	 * @param   array         $headers    An array of request headers to send with the request.
	 * @param   integer       $timeout    Read timeout in seconds.
	 * @param   string        $userAgent  The optional user agent string to send with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function request($method, UriInterface $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
	{
		// Setup the cURL handle.
		$ch = curl_init();

		// Initialize the certificate store
		$this->setCAOptionAndValue($ch);

		$options = array();

		// Set the request method.
		switch (strtoupper($method))
		{
			case 'GET':
				$options[CURLOPT_HTTPGET] = true;

				break;

			case 'POST':
				$options[CURLOPT_POST] = true;

				break;

			default:
				$options[CURLOPT_CUSTOMREQUEST] = strtoupper($method);

				break;
		}

		// Don't wait for body when $method is HEAD
		$options[CURLOPT_NOBODY] = ($method === 'HEAD');

		// If data exists let's encode it and make sure our Content-type header is set.
		if (isset($data))
		{
			// If the data is a scalar value simply add it to the cURL post fields.
			if (is_scalar($data) || (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart/form-data') === 0))
			{
				$options[CURLOPT_POSTFIELDS] = $data;
			}
			else
			{
				// Otherwise we need to encode the value first.
				$options[CURLOPT_POSTFIELDS] = http_build_query($data);
			}

			if (!isset($headers['Content-Type']))
			{
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
			}

			// Add the relevant headers.
			if (is_scalar($options[CURLOPT_POSTFIELDS]))
			{
				$headers['Content-Length'] = \strlen($options[CURLOPT_POSTFIELDS]);
			}
		}

		// Build the headers string for the request.
		$headerArray = array();

		if (isset($headers))
		{
			foreach ($headers as $key => $value)
			{
				$headerArray[] = $key . ': ' . $value;
			}

			// Add the headers string into the stream context options array.
			$options[CURLOPT_HTTPHEADER] = $headerArray;
		}

		// Curl needs the accepted encoding header as option
		if (isset($headers['Accept-Encoding']))
		{
			$options[CURLOPT_ENCODING] = $headers['Accept-Encoding'];
		}

		// If an explicit timeout is given user it.
		if (isset($timeout))
		{
			$options[CURLOPT_TIMEOUT]        = (int) $timeout;
			$options[CURLOPT_CONNECTTIMEOUT] = (int) $timeout;
		}

		// If an explicit user agent is given use it.
		if (isset($userAgent))
		{
			$options[CURLOPT_USERAGENT] = $userAgent;
		}

		// Set the request URL.
		$options[CURLOPT_URL] = (string) $uri;

		// We want our headers. :-)
		$options[CURLOPT_HEADER] = true;

		// Return it... echoing it would be tacky.
		$options[CURLOPT_RETURNTRANSFER] = true;

		// Override the Expect header to prevent cURL from confusing itself in its own stupidity.
		// Link: http://the-stickman.com/web-development/php-and-curl-disabling-100-continue-header/
		$options[CURLOPT_HTTPHEADER][] = 'Expect:';

		// Follow redirects if server config allows
		if ($this->redirectsAllowed())
		{
			$options[CURLOPT_FOLLOWLOCATION] = (bool) isset($this->options['follow_location']) ? $this->options['follow_location'] : true;
		}

		// Authentication, if needed
		if (isset($this->options['userauth'], $this->options['passwordauth']))
		{
			$options[CURLOPT_USERPWD]  = $this->options['userauth'] . ':' . $this->options['passwordauth'];
			$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
		}

		// Configure protocol version
		if (isset($this->options['protocolVersion']))
		{
			$options[CURLOPT_HTTP_VERSION] = $this->mapProtocolVersion($this->options['protocolVersion']);
		}

		// Set any custom transport options
		if (isset($this->options['transport.curl']))
		{
			foreach ($this->options['transport.curl'] as $key => $value)
			{
				$options[$key] = $value;
			}
		}

		// Set the cURL options.
		curl_setopt_array($ch, $options);

		// Execute the request and close the connection.
		$content = curl_exec($ch);

		// Check if the content is a string. If it is not, it must be an error.
		if (!\is_string($content))
		{
			$message = curl_error($ch);

			if (empty($message))
			{
				// Error but nothing from cURL? Create our own
				$message = 'No HTTP response received';
			}

			throw new \RuntimeException($message);
		}

		// Get the request information.
		$info = curl_getinfo($ch);

		// Close the connection.
		curl_close($ch);

		return $this->getResponse($content, $info);
	}

	/**
	 * Configure the cURL resources with the appropriate root certificates.
	 *
	 * @param   resource  $ch  The cURL resource you want to configure the certificates on.
	 *
	 * @return  void
	 *
	 * @since   1.3.2
	 */
	protected function setCAOptionAndValue($ch)
	{
		if (isset($this->options['curl.certpath']))
		{
			// Option is passed to a .PEM file.
			curl_setopt($ch, CURLOPT_CAINFO, $this->options['curl.certpath']);

			return;
		}

		$caPathOrFile = CaBundle::getSystemCaRootBundlePath();

		if (is_dir($caPathOrFile) || (is_link($caPathOrFile) && is_dir(readlink($caPathOrFile))))
		{
			curl_setopt($ch, CURLOPT_CAPATH, $caPathOrFile);

			return;
		}

		curl_setopt($ch, CURLOPT_CAINFO, $caPathOrFile);
	}

	/**
	 * Method to get a response object from a server response.
	 *
	 * @param   string  $content  The complete server response, including headers
	 *                            as a string if the response has no errors.
	 * @param   array   $info     The cURL request information.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 * @throws  InvalidResponseCodeException
	 */
	protected function getResponse($content, $info)
	{
		// Create the response object.
		$return = new Response;

		// Try to get header size
		if (isset($info['header_size']))
		{
			$headerString = trim(substr($content, 0, $info['header_size']));
			$headerArray  = explode("\r\n\r\n", $headerString);

			// Get the last set of response headers as an array.
			$headers = explode("\r\n", array_pop($headerArray));

			// Set the body for the response.
			$return->body = substr($content, $info['header_size']);
		}
		// Fallback and try to guess header count by redirect count
		else
		{
			// Get the number of redirects that occurred.
			$redirects = isset($info['redirect_count']) ? $info['redirect_count'] : 0;

			/*
			 * Split the response into headers and body. If cURL encountered redirects, the headers for the redirected requests will
			 * also be included. So we split the response into header + body + the number of redirects and only use the last two
			 * sections which should be the last set of headers and the actual body.
			 */
			$response = explode("\r\n\r\n", $content, 2 + $redirects);

			// Set the body for the response.
			$return->body = array_pop($response);

			// Get the last set of response headers as an array.
			$headers = explode("\r\n", array_pop($response));
		}

		// Get the response code from the first offset of the response headers.
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);

		$code = \count($matches) ? $matches[0] : null;

		if (is_numeric($code))
		{
			$return->code = (int) $code;
		}

		// No valid response code was detected.
		else
		{
			throw new InvalidResponseCodeException('No HTTP response code found.');
		}

		// Add the response headers to the response object.
		foreach ($headers as $header)
		{
			$pos                                             = strpos($header, ':');
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
		}

		return $return;
	}

	/**
	 * Method to check if HTTP transport cURL is available for use
	 *
	 * @return  boolean  True if available, else false
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return \function_exists('curl_version') && curl_version();
	}

	/**
	 * Get the cURL constant for a HTTP protocol version
	 *
	 * @param   string  $version  The HTTP protocol version to use
	 *
	 * @return  integer
	 *
	 * @since   1.3.1
	 */
	private function mapProtocolVersion($version)
	{
		switch ($version)
		{
			case '1.0':
				return CURL_HTTP_VERSION_1_0;

			case '1.1':
				return CURL_HTTP_VERSION_1_1;

			case '2.0':
			case '2':
				if (\defined('CURL_HTTP_VERSION_2'))
				{
					return CURL_HTTP_VERSION_2;
				}
		}

		return CURL_HTTP_VERSION_NONE;
	}

	/**
	 * Check if redirects are allowed
	 *
	 * @return  boolean
	 *
	 * @since   1.2.1
	 */
	private function redirectsAllowed()
	{
		// There are no issues on PHP 5.6 and later
		if (version_compare(PHP_VERSION, '5.6', '>='))
		{
			return true;
		}

		// For PHP 5.3, redirects are not allowed if safe_mode and open_basedir are enabled
		if (PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3)
		{
			if (!ini_get('safe_mode') && !ini_get('open_basedir'))
			{
				return true;
			}
		}

		// For PHP 5.4 and 5.5, we only need to check if open_basedir is disabled
		return !ini_get('open_basedir');
	}
}


<?php
/**
 * Part of the Joomla Framework Http Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Http\Transport;

use Composer\CaBundle\CaBundle;
use Joomla\Http\Exception\InvalidResponseCodeException;
use Joomla\Http\Response;
use Joomla\Http\TransportInterface;
use Joomla\Uri\Uri;
use Joomla\Uri\UriInterface;

/**
 * HTTP transport class for using PHP streams.
 *
 * @since  1.0
 */
class Stream implements TransportInterface
{
	/**
	 * The client options.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   array|\ArrayAccess  $options  Client options array.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function __construct($options = array())
	{
		// Verify that fopen() is available.
		if (!self::isSupported())
		{
			throw new \RuntimeException('Cannot use a stream transport when fopen() is not available.');
		}

		// Verify that URLs can be used with fopen();
		if (!ini_get('allow_url_fopen'))
		{
			throw new \RuntimeException('Cannot use a stream transport when "allow_url_fopen" is disabled.');
		}

		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;
	}

	/**
	 * Send a request to the server and return a Response object with the response.
	 *
	 * @param   string        $method     The HTTP method for sending the request.
	 * @param   UriInterface  $uri        The URI to the resource to request.
	 * @param   mixed         $data       Either an associative array or a string to be sent with the request.
	 * @param   array         $headers    An array of request headers to send with the request.
	 * @param   integer       $timeout    Read timeout in seconds.
	 * @param   string        $userAgent  The optional user agent string to send with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function request($method, UriInterface $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
	{
		// Create the stream context options array with the required method offset.
		$options = array('method' => strtoupper($method));

		// If data exists let's encode it and make sure our Content-Type header is set.
		if (isset($data))
		{
			// If the data is a scalar value simply add it to the stream context options.
			if (is_scalar($data))
			{
				$options['content'] = $data;
			}
			else
			{
				// Otherwise we need to encode the value first.
				$options['content'] = http_build_query($data);
			}

			if (!isset($headers['Content-Type']))
			{
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
			}

			// Add the relevant headers.
			$headers['Content-Length'] = \strlen($options['content']);
		}

		// If an explicit timeout is given user it.
		if (isset($timeout))
		{
			$options['timeout'] = (int) $timeout;
		}

		// If an explicit user agent is given use it.
		if (isset($userAgent))
		{
			$options['user_agent'] = $userAgent;
		}

		// Ignore HTTP errors so that we can capture them.
		$options['ignore_errors'] = 1;

		// Follow redirects.
		$options['follow_location'] = isset($this->options['follow_location']) ? (int) $this->options['follow_location'] : 1;

		// Configure protocol version, use transport's default if not set otherwise.
		$options['follow_location'] = isset($this->options['protocolVersion']) ? $this->options['protocolVersion'] : '1.0';

		// Add the proxy configuration if enabled
		$proxyEnabled = isset($this->options['proxy.enabled']) ? (bool) $this->options['proxy.enabled'] : false;

		if ($proxyEnabled)
		{
			$options['request_fulluri'] = true;

			if (isset($this->options['proxy.host'], $this->options['proxy.port']))
			{
				$options['proxy'] = $this->options['proxy.host'] . ':' . (int) $this->options['proxy.port'];
			}

			// If authentication details are provided, add those as well
			if (isset($this->options['proxy.user'], $this->options['proxy.password']))
			{
				$headers['Proxy-Authorization'] = 'Basic ' . base64_encode($this->options['proxy.user'] . ':' . $this->options['proxy.password']);
			}
		}

		// Build the headers string for the request.
		$headerString = null;

		if (isset($headers))
		{
			foreach ($headers as $key => $value)
			{
				$headerString .= $key . ': ' . $value . "\r\n";
			}

			// Add the headers string into the stream context options array.
			$options['header'] = trim($headerString, "\r\n");
		}

		// Authentication, if needed
		if ($uri instanceof Uri && isset($this->options['userauth'], $this->options['passwordauth']))
		{
			$uri->setUser($this->options['userauth']);
			$uri->setPass($this->options['passwordauth']);
		}

		// Set any custom transport options
		if (isset($this->options['transport.stream']))
		{
			foreach ($this->options['transport.stream'] as $key => $value)
			{
				$options[$key] = $value;
			}
		}

		// Get the current context options.
		$contextOptions = stream_context_get_options(stream_context_get_default());

		// Add our options to the currently defined options, if any.
		$contextOptions['http'] = isset($contextOptions['http']) ? array_merge($contextOptions['http'], $options) : $options;

		// Create the stream context for the request.
		$streamOptions = array(
			'http' => $options,
			'ssl'  => array(
				'verify_peer'  => true,
				'verify_depth' => 5,
			),
		);

		// Ensure the ssl peer name is verified where possible
		if (version_compare(PHP_VERSION, '5.6.0') >= 0)
		{
			$streamOptions['ssl']['verify_peer_name'] = true;
		}

		// The cacert may be a file or path
		$certpath = isset($this->options['stream.certpath']) ? $this->options['stream.certpath'] : CaBundle::getSystemCaRootBundlePath();

		if (is_dir($certpath))
		{
			$streamOptions['ssl']['capath'] = $certpath;
		}
		else
		{
			$streamOptions['ssl']['cafile'] = $certpath;
		}

		$context = stream_context_create($streamOptions);

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		// Open the stream for reading.
		$stream = @fopen((string) $uri, 'r', false, $context);

		if (!$stream)
		{
			if (!$php_errormsg)
			{
				// Error but nothing from php? Create our own
				// @todo $err and $errno are undefined variables.
				$php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
			}

			// Restore error tracking to give control to the exception handler
			ini_set('track_errors', $trackErrors);

			throw new \RuntimeException($php_errormsg);
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $trackErrors);

		// Get the metadata for the stream, including response headers.
		$metadata = stream_get_meta_data($stream);

		// Get the contents from the stream.
		$content = stream_get_contents($stream);

		// Close the stream.
		fclose($stream);

		if (isset($metadata['wrapper_data']['headers']))
		{
			$headers = $metadata['wrapper_data']['headers'];
		}
		elseif (isset($metadata['wrapper_data']))
		{
			$headers = $metadata['wrapper_data'];
		}
		else
		{
			$headers = array();
		}

		return $this->getResponse($headers, $content);
	}

	/**
	 * Method to get a response object from a server response.
	 *
	 * @param   array   $headers  The response headers as an array.
	 * @param   string  $body     The response body as a string.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 * @throws  InvalidResponseCodeException
	 */
	protected function getResponse(array $headers, $body)
	{
		// Create the response object.
		$return = new Response;

		// Set the body for the response.
		$return->body = $body;

		// Get the response code from the first offset of the response headers.
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);
		$code = $matches[0];

		if (is_numeric($code))
		{
			$return->code = (int) $code;
		}
		// No valid response code was detected.
		else
		{
			throw new InvalidResponseCodeException('No HTTP response code found.');
		}

		// Add the response headers to the response object.
		foreach ($headers as $header)
		{
			$pos                                             = strpos($header, ':');
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
		}

		return $return;
	}

	/**
	 * Method to check if http transport stream available for use
	 *
	 * @return  boolean  True if available else false
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return \function_exists('fopen') && \is_callable('fopen') && ini_get('allow_url_fopen');
	}
}


<?php
/**
 * Part of the Joomla Framework Http Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Http;

use Joomla\Uri\Uri;
use Joomla\Uri\UriInterface;

/**
 * HTTP client class.
 *
 * @since  1.0
 */
class Http
{
	/**
	 * Options for the HTTP client.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options;

	/**
	 * The HTTP transport object to use in sending HTTP requests.
	 *
	 * @var    TransportInterface
	 * @since  1.0
	 */
	protected $transport;

	/**
	 * Constructor.
	 *
	 * @param   array|\ArrayAccess  $options    Client options array. If the registry contains any headers.* elements,
	 *                                          these will be added to the request headers.
	 * @param   TransportInterface  $transport  The HTTP transport object.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($options = array(), TransportInterface $transport = null)
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;

		if (!isset($transport))
		{
			$transport = HttpFactory::getAvailableDriver($this->options);
		}

		// Ensure the transport is a TransportInterface instance or bail out
		if (!($transport instanceof TransportInterface))
		{
			throw new \InvalidArgumentException('A valid TransportInterface object was not set.');
		}

		$this->transport = $transport;
	}

	/**
	 * Get an option from the HTTP client.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   1.0
	 */
	public function getOption($key)
	{
		return isset($this->options[$key]) ? $this->options[$key] : null;
	}

	/**
	 * Set an option for the HTTP client.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  Http  This object for method chaining.
	 *
	 * @since   1.0
	 */
	public function setOption($key, $value)
	{
		$this->options[$key] = $value;

		return $this;
	}

	/**
	 * Method to send the OPTIONS command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function options($url, array $headers = array(), $timeout = null)
	{
		return $this->makeTransportRequest('OPTIONS', $url, null, $headers, $timeout);
	}

	/**
	 * Method to send the HEAD command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function head($url, array $headers = array(), $timeout = null)
	{
		return $this->makeTransportRequest('HEAD', $url, null, $headers, $timeout);
	}

	/**
	 * Method to send the GET command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function get($url, array $headers = array(), $timeout = null)
	{
		return $this->makeTransportRequest('GET', $url, null, $headers, $timeout);
	}

	/**
	 * Method to send the POST command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   mixed                $data     Either an associative array or a string to be sent with the request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function post($url, $data, array $headers = array(), $timeout = null)
	{
		return $this->makeTransportRequest('POST', $url, $data, $headers, $timeout);
	}

	/**
	 * Method to send the PUT command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   mixed                $data     Either an associative array or a string to be sent with the request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function put($url, $data, array $headers = array(), $timeout = null)
	{
		return $this->makeTransportRequest('PUT', $url, $data, $headers, $timeout);
	}

	/**
	 * Method to send the DELETE command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 * @param   mixed                $data     Either an associative array or a string to be sent with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function delete($url, array $headers = array(), $timeout = null, $data = null)
	{
		return $this->makeTransportRequest('DELETE', $url, $data, $headers, $timeout);
	}

	/**
	 * Method to send the TRACE command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function trace($url, array $headers = array(), $timeout = null)
	{
		return $this->makeTransportRequest('TRACE', $url, null, $headers, $timeout);
	}

	/**
	 * Method to send the PATCH command to the server.
	 *
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   mixed                $data     Either an associative array or a string to be sent with the request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 */
	public function patch($url, $data, array $headers = array(), $timeout = null)
	{
		return $this->makeTransportRequest('PATCH', $url, $data, $headers, $timeout);
	}

	/**
	 * Send a request to the server and return a Response object with the response.
	 *
	 * @param   string               $method   The HTTP method for sending the request.
	 * @param   string|UriInterface  $url      The URI to the resource to request.
	 * @param   mixed                $data     Either an associative array or a string to be sent with the request.
	 * @param   array                $headers  An array of request headers to send with the request.
	 * @param   integer              $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	protected function makeTransportRequest($method, $url, $data = null, array $headers = array(), $timeout = null)
	{
		// Look for headers set in the options.
		if (isset($this->options['headers']))
		{
			$temp = (array) $this->options['headers'];

			foreach ($temp as $key => $val)
			{
				if (!isset($headers[$key]))
				{
					$headers[$key] = $val;
				}
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && isset($this->options['timeout']))
		{
			$timeout = $this->options['timeout'];
		}

		$userAgent = isset($this->options['userAgent']) ? $this->options['userAgent'] : null;

		// Convert to a Uri object if we were given a string
		if (\is_string($url))
		{
			$url = new Uri($url);
		}
		elseif (!($url instanceof UriInterface))
		{
			throw new \InvalidArgumentException(sprintf('A string or UriInterface object must be provided, a "%s" was provided.', \gettype($url)));
		}

		return $this->transport->request($method, $url, $data, $headers, $timeout, $userAgent);
	}
}


<?php declare(strict_types=1);
/**
 * This file is part of OXID eShop Composer plugin.
 *
 * OXID eShop Composer plugin is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * OXID eShop Composer plugin is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OXID eShop Composer plugin.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @link      http://www.oxid-esales.com
 * @copyright (C) OXID eSales AG 2003-2017
 * @version   OXID eShop Composer plugin
 */

namespace OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher\Iteration;

use OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher\GlobMatcher;
use Webmozart\PathUtil\Path;

/**
 * Class BlacklistFilterIterator.
 *
 * An iterator which iterates through given iterator of files/directories and filters out the items described in list of
 * glob filter definitions (black list filtering).
 */
class BlacklistFilterIterator extends \FilterIterator
{
    /** @var array List of glob expressions, e.g. ["*.txt", "*.pdf"]. */
    private $globExpressionList;

    /** @var string Absolute root path from the start of iteration. */
    private $rootPath;

    /**
     * BlacklistFilterIterator constructor.
     *
     * @param \Iterator $iterator           An iterator which iterates through files/directories.
     * @param string    $rootPath           Absolute root path from the start of iteration.
     * @param array     $globExpressionList List of glob expressions, e.g. ["*.txt", "*.pdf"].
     */
    public function __construct(\Iterator $iterator, $rootPath, $globExpressionList)
    {
        parent::__construct($iterator);

        $this->globExpressionList = $globExpressionList;
        $this->rootPath = $rootPath;
    }

    /**
     * {@inheritdoc}
     *
     * @return bool
     */
    public function accept()
    {
        $path = $this->convertFromSplFileInfoToString(parent::current());

        return !GlobMatcher::matchAny($this->getRelativePath($path), $this->globExpressionList);
    }

    /**
     * Get relative path from given item of iteration compared to provided root path.
     *
     * @param string $absolutePath Absolute path from iteration.
     *
     * @return string
     */
    private function getRelativePath($absolutePath)
    {
        return Path::makeRelative($absolutePath, $this->rootPath);
    }

    /**
     * Returns string to absolute path from an entry of SplFileInfo.
     *
     * @param \SplFileInfo $item Item from iteration.
     *
     * @return string
     */
    private function convertFromSplFileInfoToString(\SplFileInfo $item)
    {
        return (string)$item;
    }
}


<?php declare(strict_types=1);
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher;

use OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher\Integration\WebmozartGlobMatcher;
use OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher\GlobListMatcher\GlobListMatcher;

/**
 * Class GlobMatcher.
 *
 * Expose multiple glob matching interface for given relative path.
 */
class GlobMatcher
{
    /**
     * @param string $relativePath       Relative path to match against.
     * @param array  $globExpressionList List of glob expressions, e.g. ["*.txt", "*.pdf"].
     *
     * @return bool True if given path matches any of given glob expression.
     */
    public static function matchAny($relativePath, $globExpressionList)
    {
        $globMatcher = new WebmozartGlobMatcher();
        $globListMatcher = new GlobListMatcher($globMatcher);

        return $globListMatcher->matchAny($relativePath, $globExpressionList);
    }
}


<?php declare(strict_types=1);
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher\Integration;

use InvalidArgumentException;
use Webmozart\PathUtil\Path;

/**
 * Class AbstractGlobMatcher.
 *
 * Abstract which defines API for matching a path against a glob expression.
 */
abstract class AbstractGlobMatcher
{
    /**
     * Returns true if given path matches a glob expression.
     *
     * @param string $relativePath
     * @param string $globExpression Glob filter expressions, e.g. "*.txt" or "*.pdf".
     *
     * @throws \InvalidArgumentException If given $globExpression is not a valid string.
     * @throws \InvalidArgumentException If given $globExpression is an absolute path.
     *
     * @return bool
     */
    public function match($relativePath, $globExpression)
    {
        if (!is_string($globExpression) && !is_null($globExpression)) {
            $message = "Given value \"$globExpression\" is not a valid glob expression. ".
                "Valid expression must be a string e.g. \"*.txt\".";

            throw new InvalidArgumentException($message);
        }

        if (Path::isAbsolute((string)$globExpression)) {
            $message = "Given value \"$globExpression\" is an absolute path. ".
                "Glob expression can only be accepted if it's a relative path.";

            throw new InvalidArgumentException($message);
        }

        if (is_null($globExpression)) {
            return true;
        }

        return static::isGlobMatch($relativePath, $globExpression);
    }

    /**
     * Implementation details for matching a given path against glob expression.
     *
     * @param string $relativePath
     * @param string $globExpression Glob filter expressions, e.g. "*.txt" or "*.pdf".
     */
    abstract protected function isGlobMatch($relativePath, $globExpression);
}


<?php declare(strict_types=1);
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\ComposerPlugin\Installer\Package;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;

/**
 * Class is responsible for preparing project structure.
 * It copies necessary files to specific directories.
 */
abstract class AbstractPackageInstaller
{
    const EXTRA_PARAMETER_KEY_ROOT = 'oxideshop';

    /** Used to determine third party package internal source path. */
    const EXTRA_PARAMETER_KEY_SOURCE = 'source-directory';

    /** Used to install third party integrations. */
    const EXTRA_PARAMETER_KEY_TARGET = 'target-directory';

    /** Used to install third party integration assets. */
    const EXTRA_PARAMETER_KEY_ASSETS = 'assets-directory';

    /** Used to decide what the shop source directory is. */
    const EXTRA_PARAMETER_SOURCE_PATH = 'source-path';

    /** List of glob expressions used to blacklist files being copied. */
    const EXTRA_PARAMETER_FILTER_BLACKLIST = 'blacklist-filter';

    /** Glob expression to filter all files, might be used to filter whole directory. */
    const BLACKLIST_ALL_FILES = '**/*';

    /** Name of directory to be excluded for VCS */
    const BLACKLIST_VCS_DIRECTORY = '.git';

    /** Name of ignore files to be excluded for VCS */
    const BLACKLIST_VCS_IGNORE_FILE = '.gitignore';

    /** Glob filter expression to exclude VCS files */
    const BLACKLIST_VCS_DIRECTORY_FILTER = self::BLACKLIST_VCS_DIRECTORY . DIRECTORY_SEPARATOR . self::BLACKLIST_ALL_FILES;

    /** @var IOInterface */
    private $io;

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

    /** @var PackageInterface */
    private $package;

    /**
     * AbstractInstaller constructor.
     *
     * @param IOInterface      $io
     * @param string           $rootDirectory
     * @param PackageInterface $package
     */
    public function __construct(IOInterface $io, $rootDirectory, PackageInterface $package)
    {
        $this->io = $io;
        $this->rootDirectory = $rootDirectory;
        $this->package = $package;
    }

    /**
     * Run package installation procedure. After installation files should be moved to correct location.
     *
     * @param string $packagePath Path to downloaded package in vendors directory.
     */
    abstract public function install($packagePath);

    /**
     * Run update procedure to keep package files up to date.
     *
     * @param string $packagePath Path to downloaded package in vendors directory.
     */
    abstract public function update($packagePath);

    /**
     * @return string
     */
    protected function getRootDirectory()
    {
        return $this->rootDirectory;
    }

    /**
     * @return string
     */
    protected function getPackageName()
    {
        return $this->getPackage()->getName();
    }

    /**
     * Return the value defined in composer extra parameters for blacklist filtering.
     *
     * @return array
     */
    protected function getBlacklistFilterValue()
    {
        return $this->getExtraParameterValueByKey(static::EXTRA_PARAMETER_FILTER_BLACKLIST, []);
    }

    /**
     * Search for parameter with specific key in "extra" composer configuration block
     *
     * @param string $extraParameterKey
     * @param string $defaultValue
     *
     * @return array|string|null
     */
    protected function getExtraParameterValueByKey($extraParameterKey, $defaultValue = null)
    {
        $extraParameters = $this->getPackage()->getExtra();

        $extraParameterValue = $extraParameters[static::EXTRA_PARAMETER_KEY_ROOT][$extraParameterKey] ?? null;

        return (!empty($extraParameterValue)) ? $extraParameterValue : $defaultValue;
    }

    /**
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Get VCS glob filter expression
     *
     * @return array
     */
    protected function getVCSFilter()
    {
        return [self::BLACKLIST_VCS_DIRECTORY_FILTER, self::BLACKLIST_VCS_IGNORE_FILE];
    }

    /**
     * Combine multiple glob expression lists into one list
     *
     * @param array $listOfGlobExpressionLists E.g. [["*.txt", "*.pdf"], ["*.md"]]
     *
     * @return array
     */
    protected function getCombinedFilters($listOfGlobExpressionLists)
    {
        $filters = [];
        foreach ($listOfGlobExpressionLists as $filter) {
            $filters = array_merge($filters, $filter);
        }

        return $filters;
    }

    /**
     * @param string $messageToAsk
     *
     * @return bool
     */
    protected function askQuestionIfNotInstalled($messageToAsk)
    {
        return $this->isInstalled() ? $this->askQuestion($messageToAsk) : true;
    }

    /**
     * Check whether given package is already installed.
     *
     * @return bool
     */
    public function isInstalled()
    {
        return false;
    }

    /**
     * Returns true if the human answer to the given question was answered with a positive value (Yes/yes/Y/y).
     *
     * @param string $messageToAsk
     *
     * @return bool
     */
    protected function askQuestion($messageToAsk)
    {
        $userInput = $this->getIO()->ask($messageToAsk, 'N');

        return $this->isPositiveUserInput($userInput);
    }

    /**
     * @return IOInterface
     */
    protected function getIO()
    {
        return $this->io;
    }

    /**
     * Return true if the input from user is a positive answer (Yes/yes/Y/y)
     *
     * @param string $userInput Raw user input
     *
     * @return bool
     */
    private function isPositiveUserInput($userInput)
    {
        $positiveAnswers = ['yes', 'y'];

        return in_array(strtolower(trim($userInput)), $positiveAnswers, true);
    }

    /**
     * @param string $packageType
     */
    protected function writeInstallingMessage(string $packageType)
    {
        $this->getIO()->write($this->getInstallingMessage($packageType));
    }

    /**
     * @param string $packageType
     *
     * @return string
     */
    protected function getInstallingMessage(string $packageType): string
    {
        return $this->getMessagePrefix() . "Installing {$packageType} {$this->getPackage()->getName()}";
    }

    /**
     * @return string
     */
    protected function getMessagePrefix(): string
    {
        return '<info>oxid-esales/oxideshop-composer-plugin:</info> ';
    }

    /**
     * @param string $packageType
     */
    protected function writeUpdatingMessage(string $packageType)
    {
        $this->getIO()->write($this->getUpdatingMessage($packageType));
    }

    /**
     * @param string $packageType
     *
     * @return string
     */
    protected function getUpdatingMessage(string $packageType): string
    {
        return $this->getMessagePrefix() . "Updating {$packageType} {$this->getPackage()->getName()}";
    }

    /**
     * @return mixed
     */
    protected function writeCopyingMessage()
    {
        return $this->getIO()->write($this->getCopyingMessage());
    }

    /**
     * @return string
     */
    protected function getCopyingMessage(): string
    {
        return 'Copying files ...';
    }

    /**
     * @return mixed
     */
    protected function writeDoneMessage()
    {
        return $this->getIO()->write($this->getDoneMessage());
    }

    /**
     * @return string
     */
    protected function getDoneMessage(): string
    {
        return 'Done';
    }

    /**
     * @return mixed
     */
    protected function writeSkippedMessage()
    {
        return $this->getIO()->write($this->getSkippedMessage());
    }

    /**
     * @return string
     */
    protected function getSkippedMessage(): string
    {
        return 'Skipped';
    }
}


<?php declare(strict_types=1);
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\ComposerPlugin;

use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use OxidEsales\ComposerPlugin\Installer\Package\AbstractPackageInstaller;
use OxidEsales\ComposerPlugin\Installer\PackageInstallerTrigger;

/**
 * Class Plugin.
 */
class Plugin implements PluginInterface, EventSubscriberInterface
{
    const ACTION_INSTALL = 'install';

    const ACTION_UPDATE = 'update';

    /** @var Composer */
    private $composer;

    /** @var IOInterface */
    private $io;

    /** @var PackageInstallerTrigger */
    private $packageInstallerTrigger;

    /**
     * Register events.
     *
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return array(
            'post-install-cmd' => 'installPackages',
            'post-update-cmd' => 'updatePackages'
        );
    }

    /**
     * Register shop packages installer.
     *
     * @param Composer    $composer
     * @param IOInterface $io
     */
    public function activate(Composer $composer, IOInterface $io)
    {
        $packageInstallerTrigger = new PackageInstallerTrigger($io, $composer);
        $composer->getInstallationManager()->addInstaller($packageInstallerTrigger);

        $this->composer = $composer;
        $this->io = $io;
        $this->packageInstallerTrigger = $packageInstallerTrigger;

        $extraSettings = $this->composer->getPackage()->getExtra();
        if (isset($extraSettings[AbstractPackageInstaller::EXTRA_PARAMETER_KEY_ROOT])) {
            $this->packageInstallerTrigger->setSettings($extraSettings[AbstractPackageInstaller::EXTRA_PARAMETER_KEY_ROOT]);
        }
    }

    /**
     * Run installation for oxid packages.
     */
    public function installPackages()
    {
        $this->executeAction(static::ACTION_INSTALL);
    }

    /**
     * Run update for oxid packages.
     */
    public function updatePackages()
    {
        $this->executeAction(static::ACTION_UPDATE);
    }

    /**
     * @param string $actionName
     */
    protected function executeAction($actionName)
    {
        $repo = $this->composer->getRepositoryManager()->getLocalRepository();

        foreach ($repo->getPackages() as $package) {
            if ($this->packageInstallerTrigger->supports($package->getType())) {
                if ($actionName === static::ACTION_INSTALL) {
                    $this->packageInstallerTrigger->installPackage($package);
                }
                if ($actionName === static::ACTION_UPDATE) {
                    $this->packageInstallerTrigger->updatePackage($package);
                }
            }
        }
    }
}


<?php declare(strict_types=1);
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher\GlobListMatcher;

use OxidEsales\ComposerPlugin\Utilities\CopyFileManager\GlobMatcher\Integration\AbstractGlobMatcher;

/**
 * Class GlobListMatcher.
 *
 * Enables glob matching for a relative path against a list of glob expressions.
 */
class GlobListMatcher
{
    /** @var AbstractGlobMatcher */
    protected $globMatcher;

    /**
     * GlobListMatcher constructor.
     *
     * @param AbstractGlobMatcher $globMatcher Instance of a variant from AbstractGlobMatcher.
     */
    public function __construct($globMatcher)
    {
        $this->globMatcher = $globMatcher;
    }

    /**
     * Returns true if given relative path matches against at least one glob expression from provided list.
     *
     * @param string $relativePath
     * @param array  $globExpressionList List of glob expressions, e.g. ["*.txt", "*.pdf"].
     *
     * @throws \InvalidArgumentException If $globExpressionList is not a \Traversable instance.
     *
     * @return bool
     */
    public function matchAny($relativePath, $globExpressionList)
    {
        if (!is_array($globExpressionList) && (!$globExpressionList instanceof \Traversable)
            && (!is_null($globExpressionList))) {
            $message = "Given value \"$globExpressionList\" is not a valid glob expression list. ".
                "Valid entry must be a list of glob expressions e.g. [\"*.txt\", \"*.pdf\"].";

            throw new \InvalidArgumentException($message);
        }

        if (count($globExpressionList) > 0) {
            return $this->isMatchInList($relativePath, $globExpressionList);
        }

        return false;
    }

    /**
     * Returns true if the supplied globMatcher indicates a match for at least one item in given glob expression list.
     *
     * @param string $relativePath
     * @param array  $globExpressionList List of glob expressions, e.g. ["*.txt", "*.pdf"].
     *
     * @return bool
     */
    private function isMatchInList($relativePath, $globExpressionList)
    {
        foreach ($globExpressionList as $globExpression) {
            if ($this->globMatcher->match($relativePath, $globExpression)) {
                return true;
            }
        }

        return false;
    }
}


<?php declare(strict_types=1);
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\ComposerPlugin\Installer;

use Composer\Installer\LibraryInstaller;
use Composer\Package\PackageInterface;
use OxidEsales\ComposerPlugin\Installer\Package\AbstractPackageInstaller;
use OxidEsales\ComposerPlugin\Installer\Package\ComponentInstaller;
use OxidEsales\ComposerPlugin\Installer\Package\ShopPackageInstaller;
use OxidEsales\ComposerPlugin\Installer\Package\ModulePackageInstaller;
use OxidEsales\ComposerPlugin\Installer\Package\ThemePackageInstaller;
use Webmozart\PathUtil\Path;

/**
 * Class responsible for triggering installation process.
 */
class PackageInstallerTrigger extends LibraryInstaller
{
    const TYPE_ESHOP = 'oxideshop';
    const TYPE_MODULE = 'oxideshop-module';
    const TYPE_THEME = 'oxideshop-theme';
    const TYPE_DEMODATA = 'oxideshop-demodata';
    const TYPE_COMPONENT = 'oxideshop-component';

    /** @var array Available installers for packages. */
    private $installers = [
        self::TYPE_ESHOP => ShopPackageInstaller::class,
        self::TYPE_MODULE => ModulePackageInstaller::class,
        self::TYPE_THEME => ThemePackageInstaller::class,
        self::TYPE_COMPONENT => ComponentInstaller::class,
    ];

    /**
     * @var array configurations
     */
    protected $settings = [];

    /**
     * Decides if the installer supports the given type
     *
     * @param  string $packageType
     * @return bool
     */
    public function supports($packageType)
    {
        return array_key_exists($packageType, $this->installers);
    }

    /**
     * @param array $settings Set additional settings.
     */
    public function setSettings($settings)
    {
        $this->settings = $settings;
    }

    /**
     * @param PackageInterface $package
     */
    public function installPackage(PackageInterface $package)
    {
        $installer = $this->createInstaller($package);
        if (!$installer->isInstalled()) {
            $installer->install($this->getInstallPath($package));
        }
    }

    /**
     * @param PackageInterface $package
     */
    public function updatePackage(PackageInterface $package)
    {
        $installer = $this->createInstaller($package);
        $installer->update($this->getInstallPath($package));
    }

    /**
     * Get the path to shop's source directory.
     *
     * @return string
     */
    public function getShopSourcePath()
    {
        $shopSource = Path::join(getcwd(), ShopPackageInstaller::SHOP_SOURCE_DIRECTORY);

        if (isset($this->settings[AbstractPackageInstaller::EXTRA_PARAMETER_SOURCE_PATH])) {
            $shopSource = $this->settings[AbstractPackageInstaller::EXTRA_PARAMETER_SOURCE_PATH];
        }

        return $shopSource;
    }

    /**
     * Creates package installer.
     *
     * @param PackageInterface $package
     * @return AbstractPackageInstaller
     */
    protected function createInstaller(PackageInterface $package)
    {
        return new $this->installers[$package->getType()]($this->io, $this->getShopSourcePath(), $package);
    }
}