File: //proc/self/cwd/nueva/src/Adapter/Module/AdminModuleDataProvider.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\Adapter\Module;
use Context;
use Doctrine\Common\Cache\CacheProvider;
use Employee;
use Module as LegacyModule;
use PrestaShop\PrestaShop\Core\Addon\AddonListFilterOrigin;
use PrestaShop\PrestaShop\Core\Addon\AddonsCollection;
use PrestaShopBundle\Service\DataProvider\Admin\AddonsInterface;
use PrestaShopBundle\Service\DataProvider\Admin\CategoriesProvider;
use PrestaShopBundle\Service\DataProvider\Admin\ModuleInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Router;
use Symfony\Component\Translation\TranslatorInterface;
use Tools;
/**
* Data provider for new Architecture, about Module object model.
*
* This class will provide data from DB / ORM about Modules for the Admin interface.
* This is an Adapter that works with the Legacy code and persistence behaviors.
*/
class AdminModuleDataProvider implements ModuleInterface
{
public const _CACHEKEY_MODULES_ = '_addons_modules';
public const _DAY_IN_SECONDS_ = 86400; /* Cache for One Day */
/**
* @const array giving a translation domain key for each module action
*/
public const _ACTIONS_TRANSLATION_DOMAINS_ = [
'install' => 'Admin.Actions',
'uninstall' => 'Admin.Actions',
'enable' => 'Admin.Actions',
'disable' => 'Admin.Actions',
'enable_mobile' => 'Admin.Modules.Feature',
'disable_mobile' => 'Admin.Modules.Feature',
'reset' => 'Admin.Actions',
'upgrade' => 'Admin.Actions',
'configure' => 'Admin.Actions',
];
/**
* @var array of defined and callable module actions
*/
protected $moduleActions = ['install', 'uninstall', 'enable', 'disable', 'enable_mobile', 'disable_mobile', 'reset', 'upgrade'];
/**
* @var int
*/
private $languageISO;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var Router|null
*/
private $router = null;
/**
* @var AddonsInterface
*/
private $addonsDataProvider;
/**
* @var CategoriesProvider
*/
private $categoriesProvider;
/**
* @var ModuleDataProvider
*/
private $moduleProvider;
/**
* @var CacheProvider|null
*/
private $cacheProvider;
/**
* @var Employee|null
*/
private $employee;
/**
* @var array
*/
protected $catalog_modules = [];
/**
* @var array
*/
protected $catalog_modules_names;
/**
* @var bool
*/
public $failed = false;
public function __construct(
TranslatorInterface $translator,
LoggerInterface $logger,
AddonsInterface $addonsDataProvider,
CategoriesProvider $categoriesProvider,
ModuleDataProvider $modulesProvider,
CacheProvider $cacheProvider = null,
Employee $employee = null
) {
list($this->languageISO) = explode('-', $translator->getLocale());
$this->logger = $logger;
$this->addonsDataProvider = $addonsDataProvider;
$this->categoriesProvider = $categoriesProvider;
$this->moduleProvider = $modulesProvider;
$this->cacheProvider = $cacheProvider;
$this->employee = $employee;
}
/**
* @param Router $router
*/
public function setRouter(Router $router)
{
$this->router = $router;
}
/**
* Clear the modules information from Addons cache.
*/
public function clearCatalogCache()
{
if ($this->cacheProvider) {
$this->cacheProvider->delete($this->languageISO . self::_CACHEKEY_MODULES_);
}
$this->catalog_modules = [];
}
/**
* Clears module list cache.
*/
public function clearModuleListCache()
{
if (file_exists(LegacyModule::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST)) {
@unlink(LegacyModule::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST);
}
}
/**
* @deprecated since version 1.7.3.0
*
* @return array
*/
public function getAllModules()
{
return LegacyModule::getModulesOnDisk(
true,
$this->addonsDataProvider->isAddonsAuthenticated(),
(int) Context::getContext()->employee->id
);
}
/**
* @param array $filters
*
* @return array
*/
public function getCatalogModules(array $filters = [])
{
if (count($this->catalog_modules) === 0 && !$this->failed) {
$this->loadCatalogData();
}
return $this->applyModuleFilters(
$this->catalog_modules,
$filters
);
}
/**
* @param array $filter
*
* @return array
*/
public function getCatalogModulesNames(array $filter = [])
{
return array_keys($this->getCatalogModules($filter));
}
/**
* Check the permissions of the current context (CLI or employee) for a module.
*
* @param array $actions Actions to check
* @param string $name The module name
*
* @return array of allowed actions
*/
protected function filterAllowedActions(array $actions, $name = '')
{
$allowedActions = [];
foreach (array_keys($actions) as $actionName) {
if ($this->isAllowedAccess($actionName, $name)) {
$allowedActions[$actionName] = $actions[$actionName];
}
}
return $allowedActions;
}
/**
* Check the permissions of the current context (CLI or employee) for a specified action.
*
* @param string $action The action called in the module
* @param string $name (Optionnal for 'install') The module name to check
*
* @return bool
*/
public function isAllowedAccess($action, $name = '')
{
if (Tools::isPHPCLI()) {
return true;
}
if (in_array($action, ['install', 'upgrade'])) {
return $this->employee->can('add', 'AdminModulessf');
}
if ('uninstall' === $action) {
return $this->employee->can('delete', 'AdminModulessf') && $this->moduleProvider->can('uninstall', $name);
}
return $this->employee->can('edit', 'AdminModulessf') && $this->moduleProvider->can('configure', $name);
}
/**
* @param AddonsCollection $addons
* @param string|null $specific_action
*
* @return AddonsCollection
*/
public function generateAddonsUrls(AddonsCollection $addons, $specific_action = null)
{
foreach ($addons as $addon) {
$urls = [];
foreach ($this->moduleActions as $action) {
$urls[$action] = $this->router->generate('admin_module_manage_action', [
'action' => $action,
'module_name' => $addon->attributes->get('name'),
]);
}
$urls['configure'] = $this->router->generate('admin_module_configure_action', [
'module_name' => $addon->attributes->get('name'),
]);
if ($addon->database->has('installed') && $addon->database->getBoolean('installed')) {
if (!$addon->database->getBoolean('active')) {
$url_active = 'enable';
unset(
$urls['install'],
$urls['disable']
);
} elseif ($addon->attributes->getBoolean('is_configurable')) {
$url_active = 'configure';
unset(
$urls['enable'],
$urls['install']
);
} else {
$url_active = 'disable';
unset(
$urls['install'],
$urls['enable'],
$urls['configure']
);
}
if (!$addon->attributes->getBoolean('is_configurable')) {
unset($urls['configure']);
}
if ($addon->canBeUpgraded()) {
$url_active = 'upgrade';
} else {
unset(
$urls['upgrade']
);
}
if (!$addon->database->getBoolean('active_on_mobile')) {
unset($urls['disable_mobile']);
} else {
unset($urls['enable_mobile']);
}
if (!$addon->canBeUpgraded()) {
unset(
$urls['upgrade']
);
}
} elseif (
!$addon->attributes->has('origin') ||
$addon->disk->getBoolean('is_present') ||
in_array($addon->attributes->get('origin'), ['native', 'native_all', 'partner', 'customer'], true)
) {
$url_active = 'install';
unset(
$urls['uninstall'],
$urls['enable'],
$urls['disable'],
$urls['enable_mobile'],
$urls['disable_mobile'],
$urls['reset'],
$urls['upgrade'],
$urls['configure']
);
} else {
$url_active = 'buy';
}
$urls = $this->filterAllowedActions($urls, $addon->attributes->get('name'));
$addon->attributes->set('urls', $urls);
$addon->attributes->set('actionTranslationDomains', self::_ACTIONS_TRANSLATION_DOMAINS_);
if ($specific_action && array_key_exists($specific_action, $urls)) {
$addon->attributes->set('url_active', $specific_action);
} elseif ($url_active === 'buy' || array_key_exists($url_active, $urls)) {
$addon->attributes->set('url_active', $url_active);
} else {
$addon->attributes->set('url_active', key($urls));
}
$categoryParent = $this->categoriesProvider->getParentCategory($addon->attributes->get('categoryName'));
$addon->attributes->set('categoryParent', $categoryParent);
}
return $addons;
}
/**
* @param int $moduleId
*
* @return array
*/
public function getModuleAttributesById($moduleId)
{
return (array) $this->addonsDataProvider->request('module', ['id_module' => $moduleId]);
}
/**
* @param array $modules
* @param array $filters
*
* @return array
*/
protected function applyModuleFilters(array $modules, array $filters)
{
if (!count($filters)) {
return $modules;
}
// We get our module IDs to keep
foreach ($filters as $filter_name => $value) {
$search_result = [];
switch ($filter_name) {
case 'search':
// We build our results array.
// We could remove directly the non-matching modules, but we will give that for the final loop of this function
foreach (explode(' ', $value) as $keyword) {
if (empty($keyword)) {
continue;
}
// Instead of looping on the whole module list, we use $module_ids which can already be reduced
// thanks to the previous array_intersect(...)
foreach ($modules as $key => $module) {
if (strpos($module->displayName, $keyword) !== false
|| strpos($module->name, $keyword) !== false
|| strpos($module->description, $keyword) !== false) {
$search_result[] = $key;
}
}
}
break;
case 'name':
// exact given name (should return 0 or 1 result)
$search_result[] = $value;
break;
default:
// "the switch statement is considered a looping structure for the purposes of continue."
continue 2;
}
$modules = array_intersect_key($modules, array_flip($search_result));
}
return $modules;
}
/**
* Load module catalogue. If not in cache, query Addons API.
*/
protected function loadCatalogData()
{
if ($this->cacheProvider && $this->cacheProvider->contains($this->languageISO . self::_CACHEKEY_MODULES_)) {
$this->catalog_modules = $this->cacheProvider->fetch($this->languageISO . self::_CACHEKEY_MODULES_);
}
if (!$this->catalog_modules) {
$params = ['format' => 'json'];
$requests = [
AddonListFilterOrigin::ADDONS_MUST_HAVE => 'must-have',
AddonListFilterOrigin::ADDONS_SERVICE => 'service',
AddonListFilterOrigin::ADDONS_NATIVE => 'native',
AddonListFilterOrigin::ADDONS_NATIVE_ALL => 'native_all',
];
if ($this->addonsDataProvider->isAddonsAuthenticated()) {
$requests[AddonListFilterOrigin::ADDONS_CUSTOMER] = 'customer';
}
try {
$listAddons = [];
// We execute each addons request
foreach ($requests as $action_filter_value => $action) {
if (!$this->addonsDataProvider->isAddonsUp()) {
continue;
}
// We add the request name in each product returned by Addons,
// so we know whether is bought
$addons = $this->addonsDataProvider->request($action, $params);
/** @var \stdClass $addon */
foreach ($addons as $addonsType => $addon) {
if (empty($addon->name)) {
$this->logger->error(sprintf('The addon with id %s does not have name.', $addon->id));
continue;
}
$addon->origin = $action;
$addon->origin_filter_value = $action_filter_value;
$addon->categoryParent = $this->categoriesProvider
->getParentCategory($addon->categoryName);
if (isset($addon->version)) {
$addon->version_available = $addon->version;
}
if (!isset($addon->product_type)) {
$addon->productType = isset($addonsType) ? rtrim($addonsType, 's') : 'module';
} else {
$addon->productType = $addon->product_type;
}
$listAddons[$addon->name] = $addon;
}
}
if (!empty($listAddons)) {
$this->catalog_modules = $listAddons;
if ($this->cacheProvider) {
$this->cacheProvider->save($this->languageISO . self::_CACHEKEY_MODULES_, $this->catalog_modules, self::_DAY_IN_SECONDS_);
}
} else {
$this->fallbackOnCatalogCache();
}
} catch (\Exception $e) {
if (!$this->fallbackOnCatalogCache()) {
$this->logger->error('Data from PrestaShop Addons is invalid, and cannot fallback on cache.');
}
}
}
}
/**
* If cache exists, get the Catalogue from the cache.
*
* @return array Module loaded from the cache
*/
protected function fallbackOnCatalogCache()
{
// Fallback on data from cache if exists
if ($this->cacheProvider) {
$this->catalog_modules = $this->cacheProvider->fetch($this->languageISO . self::_CACHEKEY_MODULES_);
}
if (!$this->catalog_modules) {
$this->catalog_modules = [];
}
$this->failed = true;
return $this->catalog_modules;
}
}