HEX
Server: Apache
System: Linux srv13.cpanelhost.cl 3.10.0-962.3.2.lve1.5.38.el7.x86_64 #1 SMP Thu Jun 18 05:28:41 EDT 2020 x86_64
User: cca63905 (4205)
PHP: 7.3.20
Disabled: NONE
Upload Files
File: //proc/self/cwd/nueva/src/Core/Addon/Module/ModuleManager.php
<?php
/**
 * Copyright since 2007 PrestaShop SA and Contributors
 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.md.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/OSL-3.0
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to https://devdocs.prestashop.com/ for more information.
 *
 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
 * @copyright Since 2007 PrestaShop SA and Contributors
 * @license   https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
 */

namespace PrestaShop\PrestaShop\Core\Addon\Module;

use Exception;
use PrestaShop\PrestaShop\Adapter\Module\AdminModuleDataProvider;
use PrestaShop\PrestaShop\Adapter\Module\Module;
use PrestaShop\PrestaShop\Adapter\Module\ModuleDataProvider;
use PrestaShop\PrestaShop\Adapter\Module\ModuleDataUpdater;
use PrestaShop\PrestaShop\Adapter\Module\ModuleZipManager;
use PrestaShop\PrestaShop\Core\Addon\AddonManagerInterface;
use PrestaShop\PrestaShop\Core\Addon\AddonsCollection;
use PrestaShop\PrestaShop\Core\Addon\Module\Exception\UnconfirmedModuleActionException;
use PrestaShop\PrestaShop\Core\Cache\Clearer\CacheClearerInterface;
use PrestaShop\PrestaShop\Core\Domain\Theme\Exception\FailedToEnableThemeModuleException;
use PrestaShopBundle\Event\ModuleManagementEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\Translation\TranslatorInterface;

class ModuleManager implements AddonManagerInterface
{
    /**
     * Admin Module Data Provider.
     *
     * @var \PrestaShop\PrestaShop\Adapter\Module\AdminModuleDataProvider
     */
    private $adminModuleProvider;
    /**
     * Module Data Provider.
     *
     * @var \PrestaShop\PrestaShop\Adapter\Module\ModuleDataProvider
     */
    private $moduleProvider;
    /**
     * Module Data Provider.
     *
     * @var \PrestaShop\PrestaShop\Adapter\Module\ModuleDataUpdater
     */
    private $moduleUpdater;

    /**
     * Module Repository.
     *
     * @var \PrestaShop\PrestaShop\Core\Addon\Module\ModuleRepository
     */
    private $moduleRepository;

    /**
     * Module Zip Manager.
     *
     * @var \PrestaShop\PrestaShop\Adapter\Module\ModuleZipManager
     */
    private $moduleZipManager;

    /**
     * Translator.
     *
     * @var \Symfony\Component\Translation\TranslatorInterface
     */
    private $translator;

    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * Additionnal data used for module actions.
     *
     * @var ParameterBag
     */
    private $actionParams;

    /**
     * @var CacheClearerInterface
     */
    private $symfonyCacheClearer;

    /**
     * Used to check if the cache has already been cleaned.
     *
     * @var bool
     */
    private $cacheCleared = false;

    /**
     * @param AdminModuleDataProvider $adminModuleProvider
     * @param ModuleDataProvider $modulesProvider
     * @param ModuleDataUpdater $modulesUpdater
     * @param ModuleRepository $moduleRepository
     * @param ModuleZipManager $moduleZipManager
     * @param TranslatorInterface $translator
     * @param EventDispatcherInterface $eventDispatcher
     * @param CacheClearerInterface $symfonyCacheClearer
     */
    public function __construct(
        AdminModuleDataProvider $adminModuleProvider,
        ModuleDataProvider $modulesProvider,
        ModuleDataUpdater $modulesUpdater,
        ModuleRepository $moduleRepository,
        ModuleZipManager $moduleZipManager,
        TranslatorInterface $translator,
        EventDispatcherInterface $eventDispatcher,
        CacheClearerInterface $symfonyCacheClearer
    ) {
        $this->adminModuleProvider = $adminModuleProvider;
        $this->moduleProvider = $modulesProvider;
        $this->moduleUpdater = $modulesUpdater;
        $this->moduleRepository = $moduleRepository;
        $this->moduleZipManager = $moduleZipManager;
        $this->translator = $translator;
        $this->eventDispatcher = $eventDispatcher;
        $this->symfonyCacheClearer = $symfonyCacheClearer;
        $this->actionParams = new ParameterBag();
    }

    /**
     * For some actions, you may need to add params like confirmation details.
     * This setter is the way to register them in the manager.
     *
     * @param array $actionParams
     *
     * @return $this
     */
    public function setActionParams(array $actionParams)
    {
        $this->actionParams->replace($actionParams);

        return $this;
    }

    /**
     * @param callable $modulesPresenter
     *
     * @return object
     */
    public function getModulesWithNotifications(callable $modulesPresenter)
    {
        $modules = $this->groupModulesByInstallationProgress();

        $modulesProvider = $this->adminModuleProvider;
        foreach ($modules as $moduleLabel => $modulesPart) {
            $collection = AddonsCollection::createFrom($modulesPart);
            $modulesProvider->generateAddonsUrls($collection, str_replace('to_', '', $moduleLabel));
            $modules->{$moduleLabel} = $modulesPresenter($collection);
        }

        return $modules;
    }

    /**
     * Returns the total of module notifications
     * Not used anymore, but kept for backward compatibility.
     *
     * @return int
     *
     * @deprecated since 1.7.5.0
     */
    public function countModulesWithNotifications()
    {
        $modules = (array) $this->groupModulesByInstallationProgress();

        return array_reduce($modules, function ($carry, $item) {
            return $carry + count($item);
        }, 0);
    }

    /**
     * Detailed array of number of modules per notification type.
     *
     * @return array
     */
    public function countModulesWithNotificationsDetailed()
    {
        $notificationCounts = [
            'count' => 0,
        ];

        foreach ((array) $this->groupModulesByInstallationProgress() as $key => $modules) {
            $count = count($modules);
            $notificationCounts[$key] = $count;
            $notificationCounts['count'] += $count;
        }

        return $notificationCounts;
    }

    /**
     * @return object
     */
    protected function groupModulesByInstallationProgress()
    {
        $installedProducts = $this->moduleRepository->getInstalledModules();

        $modules = (object) [
            'to_configure' => [],
            'to_update' => [],
        ];

        /*
         * @var \PrestaShop\PrestaShop\Adapter\Module\Module
         */
        foreach ($installedProducts as $installedProduct) {
            if ($this->shouldRecommendConfigurationForModule($installedProduct)) {
                $modules->to_configure[] = (object) $installedProduct;
            }

            if ($installedProduct->canBeUpgraded()) {
                $modules->to_update[] = (object) $installedProduct;
            }
        }

        return $modules;
    }

    /**
     * @param Module $installedProduct
     *
     * @return bool
     */
    protected function shouldRecommendConfigurationForModule(Module $installedProduct)
    {
        $warnings = $this->getModuleInstallationWarnings($installedProduct);

        return !empty($warnings);
    }

    /**
     * @param Module $installedProduct
     *
     * @return string|array
     */
    protected function getModuleInstallationWarnings(Module $installedProduct)
    {
        if ($installedProduct->hasValidInstance()) {
            return $installedProduct->getInstance()->warning;
        }

        return [];
    }

    /**
     * Add new module from zipball. This will unzip the file and move the content
     * to the right locations.
     * A theme can bundle modules, resources, documentation, email templates and so on.
     *
     * @param string $source The source can be a module name (installed from either local disk or addons.prestashop.com).
     *                       or a location (url or path to the zip file)
     *
     * @return bool true for success
     */
    public function install($source)
    {
        // in CLI mode, there is no employee set up
        if (!$this->adminModuleProvider->isAllowedAccess(__FUNCTION__)) {
            throw new Exception($this->translator->trans('You are not allowed to install modules.', [], 'Admin.Modules.Notification'));
        }

        if (is_file($source)) {
            $name = $this->moduleZipManager->getName($source);
        } else {
            $name = $source;
            $source = null;
        }

        if ($this->moduleProvider->isInstalled($name)) {
            return $this->upgrade($name, 'latest', $source);
        }

        if (!empty($source)) {
            $this->moduleZipManager->storeInModulesFolder($source);
        } elseif (!$this->moduleProvider->isOnDisk($name)) {
            if (!$this->moduleUpdater->setModuleOnDiskFromAddons($name)) {
                throw new FailedToEnableThemeModuleException(
                    $name,
                    $this->translator->trans(
                        'The module %name% could not be found on Addons.',
                        ['%name%' => $name],
                        'Admin.Modules.Notification'
                    )
                );
            }
        }

        $module = $this->moduleRepository->getModule($name);
        $this->checkConfirmationGiven(__FUNCTION__, $module);
        $result = $module->onInstall();

        $this->checkAndClearCache($result);
        $this->dispatch(ModuleManagementEvent::INSTALL, $module);

        return $result;
    }

    /**
     * Remove all theme files, resources, documentation and specific modules.
     *
     * @param string $name The source can be a module name (installed from either local disk or addons.prestashop.com).
     *                     or a location (url or path to the zip file)
     *
     * @return bool true for success
     */
    public function uninstall($name)
    {
        // Check permissions:
        // * Employee can delete
        // * Employee can delete this specific module
        if (!$this->adminModuleProvider->isAllowedAccess(__FUNCTION__, $name)) {
            throw new Exception($this->translator->trans('You are not allowed to uninstall the module %module%.', ['%module%' => $name], 'Admin.Modules.Notification'));
        }

        $this->checkIsInstalled($name);

        // Get module instance and uninstall it
        $module = $this->moduleRepository->getModule($name);
        $result = $module->onUninstall();

        if ($result && $this->actionParams->get('deletion', false)) {
            $result = $this->removeModuleFromDisk($name);
        }

        $this->checkAndClearCache($result);
        $this->dispatch(ModuleManagementEvent::UNINSTALL, $module);

        return $result;
    }

    /**
     * Download new files from source, backup old files, replace files with new ones
     * and execute all necessary migration scripts form current version to the new one.
     *
     * @param string $name the theme you want to upgrade
     * @param string $version the version you want to up upgrade to
     * @param string $source if the upgrade is not coming from addons, you need to specify the path to the zipball
     *
     * @return bool true for success
     */
    public function upgrade($name, $version = 'latest', $source = null)
    {
        if (!$this->adminModuleProvider->isAllowedAccess(__FUNCTION__, $name)) {
            throw new Exception($this->translator->trans('You are not allowed to upgrade the module %module%.', ['%module%' => $name], 'Admin.Modules.Notification'));
        }

        $this->checkIsInstalled($name);
        $module = $this->moduleRepository->getModule($name);

        // Get new module
        // 1- From source
        if ($source != null) {
            $this->moduleZipManager->storeInModulesFolder($source);
        } elseif ($module->canBeUpgradedFromAddons()) {
            // 2- From Addons
            // This step is not mandatory (in case of local module),
            // we do not check the result
            $this->moduleUpdater->setModuleOnDiskFromAddons($name);
        }

        // Load and execute upgrade files
        $result = $this->moduleUpdater->upgrade($name) && $module->onUpgrade($version);

        $this->checkAndClearCache($result);
        $this->dispatch(ModuleManagementEvent::UPGRADE, $module);

        return $result;
    }

    /**
     * Disable a module without uninstalling it.
     * Allows the merchant to temporarly remove a module without uninstalling it.
     *
     * @param string $name The module name to disable
     *
     * @return bool True for success
     */
    public function disable($name)
    {
        if (!$this->adminModuleProvider->isAllowedAccess(__FUNCTION__, $name)) {
            throw new Exception($this->translator->trans('You are not allowed to disable the module %module%.', ['%module%' => $name], 'Admin.Modules.Notification'));
        }

        $this->checkIsInstalled($name);

        $module = $this->moduleRepository->getModule($name);

        try {
            $result = $module->onDisable();
        } catch (Exception $e) {
            throw new Exception($this->translator->trans('Error when disabling module %module%. %error_details%.', ['%module%' => $name, '%error_details%' => $e->getMessage()], 'Admin.Modules.Notification'), 0, $e);
        }

        $this->checkAndClearCache($result);
        $this->dispatch(ModuleManagementEvent::DISABLE, $module);

        return $result;
    }

    /**
     * Enable a module previously disabled.
     *
     * @param string $name The module name to enable
     *
     * @return bool True for success
     */
    public function enable($name)
    {
        if (!$this->adminModuleProvider->isAllowedAccess(__FUNCTION__, $name)) {
            throw new Exception($this->translator->trans('You are not allowed to enable the module %module%.', ['%module%' => $name], 'Admin.Modules.Notification'));
        }

        $this->checkIsInstalled($name);

        $module = $this->moduleRepository->getModule($name);

        try {
            $result = $module->onEnable();
        } catch (Exception $e) {
            throw new Exception($this->translator->trans('Error when enabling module %module%. %error_details%.', ['%module%' => $name, '%error_details%' => $e->getMessage()], 'Admin.Modules.Notification'), 0, $e);
        }

        $this->checkAndClearCache($result);
        $this->dispatch(ModuleManagementEvent::ENABLE, $module);

        return $result;
    }

    /**
     * Disable a module specifically on mobile.
     * Not written in camel case because the route and the displayed action in the template
     * are related to this function name.
     *
     * @deprecated use disableMobile()
     *
     * @param string $name The module name to disable
     *
     * @return bool True for success
     */
    public function disable_mobile($name)
    {
        return $this->disableMobile($name);
    }

    /**
     * Disable a module specifically on mobile.
     *
     * @param string $name The module name to disable
     *
     * @return bool True for success
     */
    public function disableMobile($name)
    {
        if (!$this->adminModuleProvider->isAllowedAccess(__FUNCTION__, $name)) {
            throw new Exception($this->translator->trans('You are not allowed to disable the module %module% on mobile.', ['%module%' => $name], 'Admin.Modules.Notification'));
        }

        $this->checkIsInstalled($name);

        $module = $this->moduleRepository->getModule($name);

        try {
            $result = $module->onMobileDisable();
        } catch (Exception $e) {
            throw new Exception($this->translator->trans('Error when disabling module %module% on mobile. %error_details%', ['%module%' => $name, '%error_details%' => $e->getMessage()], 'Admin.Modules.Notification'), 0, $e);
        }

        $this->checkAndClearCache($result);

        return $result;
    }

    /**
     * Enable a module previously disabled on mobile
     * Not written in camel case because the route and the displayed action in the template
     * are related to this function name.
     *
     * @deprecated use enableMobile
     *
     * @param string $name The module name to enable
     *
     * @return bool True for success
     */
    public function enable_mobile($name)
    {
        return $this->enableMobile($name);
    }

    /**
     * Enable a module previously disabled on mobile.
     *
     * @param string $name The module name to enable
     *
     * @return bool True for success
     */
    public function enableMobile($name)
    {
        if (!$this->adminModuleProvider->isAllowedAccess(__FUNCTION__, $name)) {
            throw new Exception($this->translator->trans('You are not allowed to enable the module %module% on mobile.', ['%module%' => $name], 'Admin.Modules.Notification'));
        }

        $this->checkIsInstalled($name);

        $module = $this->moduleRepository->getModule($name);

        try {
            $result = $module->onMobileEnable();
        } catch (Exception $e) {
            throw new Exception($this->translator->trans('Error when enabling module %module% on mobile. %error_details%', ['%module%' => $name, '%error_details%' => $e->getMessage()], 'Admin.Modules.Notification'), 0, $e);
        }

        $this->checkAndClearCache($result);

        return $result;
    }

    /**
     * Actions to perform to restaure default settings.
     *
     * @param string $name The theme name to reset
     *
     * @return bool True for success
     */
    public function reset($name, $keep_data = false)
    {
        if (!$this->adminModuleProvider->isAllowedAccess('install') || !$this->adminModuleProvider->isAllowedAccess('uninstall', $name)) {
            throw new Exception($this->translator->trans('You are not allowed to reset the module %module%.', ['%module%' => $name], 'Admin.Modules.Notification'));
        }

        $this->checkIsInstalled($name);

        $module = $this->moduleRepository->getModule($name);

        try {
            if ((bool) $keep_data && method_exists($module->getInstance(), 'reset')) {
                $this->dispatch(ModuleManagementEvent::UNINSTALL, $module);
                $status = $module->onReset();
                $this->dispatch(ModuleManagementEvent::INSTALL, $module);
            } else {
                $status = ($this->uninstall($name) && $this->install($name));
            }
        } catch (Exception $e) {
            throw new Exception($this->translator->trans('Error when resetting module %module%. %error_details%', ['%module%' => $name, '%error_details%' => $e->getMessage()], 'Admin.Modules.Notification'), 0, $e);
        }

        return $status;
    }

    /**
     * Shortcut to the module data provider in order to know if a module is enabled.
     *
     * @param string $name The technical module name
     *
     * @return bool
     */
    public function isEnabled($name)
    {
        return $this->moduleProvider->isEnabled($name);
    }

    /**
     * Shortcut to the module data provider in order to know if a module is installed.
     *
     * @param string $name The technical module name
     *
     * @return bool True is installed
     */
    public function isInstalled($name)
    {
        return $this->moduleProvider->isInstalled($name);
    }

    /**
     * Shortcut to the module data provider in order to know the module id depends
     * on its name.
     *
     * @param string $name The technical module name
     *
     * @return int the Module Id, or 0 if not found
     */
    public function getModuleIdByName($name)
    {
        return $this->moduleProvider->getModuleIdByName($name);
    }

    /**
     * Shortcut to the module data updater to remove the module from the disk.
     *
     * @param string $name The technical module name
     *
     * @return bool True if files were properly removed
     */
    public function removeModuleFromDisk($name)
    {
        return $this->moduleUpdater->removeModuleFromDisk($name);
    }

    /**
     * Returns the last error, if found.
     *
     * @param string $name The technical module name
     *
     * @return string|null The last error added to the module if found
     */
    public function getError($name)
    {
        $message = null;
        $module = $this->moduleRepository->getModule($name);
        if ($module->hasValidInstance()) {
            $errors = $module->getInstance()->getErrors();
            $message = array_pop($errors);
        } else {
            // Invalid instance: Missing or with syntax error
            $message = $this->translator->trans(
                'The module is invalid and cannot be loaded.',
                [],
                'Admin.Modules.Notification'
            );
        }

        if (empty($message)) {
            $message = $this->translator->trans(
                'Unfortunately, the module did not return additional details.',
                [],
                'Admin.Modules.Notification'
            );
        }

        return $message;
    }

    /**
     * This function is a refacto of the event dispatching.
     *
     * @param string $event
     * @param Module $module
     */
    private function dispatch($event, $module)
    {
        $this->eventDispatcher->dispatch($event, new ModuleManagementEvent($module));
    }

    private function checkIsInstalled($name)
    {
        if (!$this->moduleProvider->isInstalled($name)) {
            throw new Exception($this->translator->trans('The module %module% must be installed first', ['%module%' => $name], 'Admin.Modules.Notification'));
        }
    }

    /**
     * We check the module does not ask for pre-requisites to be respected prior the action being executed.
     *
     * @param string $action
     * @param Module $module
     *
     * @throws UnconfirmedModuleActionException
     */
    private function checkConfirmationGiven($action, Module $module)
    {
        if ($action === 'install') {
            if ($module->attributes->has('prestatrust') && !$this->actionParams->has('confirmPrestaTrust')) {
                throw (new UnconfirmedModuleActionException())->setModule($module)->setAction($action)->setSubject('PrestaTrust');
            }
        }
    }

    /**
     * @param bool $result
     */
    private function checkAndClearCache($result)
    {
        if ($result && $this->actionParams->get('cacheClearEnabled', true)) {
            $this->clearCache();
        }
    }

    /**
     * Clear smarty and Symfony cache (the sf2 cache is remove on the process shutdown).
     */
    private function clearCache()
    {
        if ($this->cacheCleared) {
            return;
        }

        $this->symfonyCacheClearer->clear();
        $this->cacheCleared = true;
    }
}