Current File : /var/www/prestashop/modules/ps_mbo/src/Module/ModuleBuilder.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 Academic Free License version 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/AFL-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.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
declare(strict_types=1);
namespace PrestaShop\Module\Mbo\Module;
use Exception;
use Module as LegacyModule;
use PaymentModule;
use PhpParser;
use PrestaShop\Module\Mbo\Helpers\ErrorHelper;
use PrestaShop\Module\Mbo\Helpers\UrlHelper;
use PrestaShopBundle\Service\Routing\Router;
use Psr\Log\LoggerInterface;
use stdClass;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Validate;
/**
* Builds a Module object with the data provided by Addons, the database, the Core and some static links
*/
class ModuleBuilder implements ModuleBuilderInterface
{
/**
* @var array
*/
public const MAIN_CLASS_ATTRIBUTES = [
'warning',
'name',
'tab',
'displayName',
'description',
'author',
'author_address',
'limited_countries',
'need_instance',
];
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var Router
*/
protected $router;
/**
* Path to the module directory, coming from Configuration class.
*
* @var string
*/
protected $moduleDirectory;
public function __construct(
Router $router,
LoggerInterface $logger,
string $moduleDirectory
) {
$this->router = $router;
$this->logger = $logger;
$this->moduleDirectory = $moduleDirectory;
}
public function build(stdClass $module, ?array $database = null): Module
{
/* Convert module to array */
$attributes = json_decode(json_encode($module), true);
// Get filemtime of module main class (We do this directly with an error suppressor to go faster)
$filePath = $this->getModulePath($module->name);
$moduleIsPresentOnDisk = file_exists($filePath);
$disk = [
'filemtime' => $moduleIsPresentOnDisk ? (int) filemtime($filePath) : 0,
'is_present' => $moduleIsPresentOnDisk,
'is_valid' => 0,
'version' => null,
'path' => $this->moduleDirectory . $module->name,
];
if ($moduleIsPresentOnDisk && $this->isModuleMainClassValid($module->name)) {
$mainClassAttributes = [];
// We load the main class of the module, and get its properties
$tmpModule = LegacyModule::getInstanceByName($module->name);
foreach (static::MAIN_CLASS_ATTRIBUTES as $dataToRetrieve) {
if (isset($tmpModule->{$dataToRetrieve}) && empty($attributes[$dataToRetrieve])) {
$mainClassAttributes[$dataToRetrieve] = $tmpModule->{$dataToRetrieve};
}
}
$mainClassAttributes['parent_class'] = get_parent_class($module->name);
$mainClassAttributes['is_paymentModule'] = is_subclass_of($module->name, PaymentModule::class);
$mainClassAttributes['is_configurable'] = (int) method_exists($tmpModule, 'getContent');
$disk['is_valid'] = 1;
$disk['version'] = $tmpModule->version;
$attributes = array_merge($attributes, $mainClassAttributes);
}
$module = new Module($attributes, $disk, $database);
$this->generateAddonsUrls($module);
return $module;
}
/**
* @param Module $module
*
* @return void
*/
protected function generateAddonsUrls(Module $module): void
{
$moduleName = $module->attributes->get('name');
if (
$module->database->has('installed')
&& $module->database->getBoolean('installed')
&& $module->attributes->getBoolean('is_configurable')
) {
$module->attributes->set('urls', [
'configure' => UrlHelper::transformToAbsoluteUrl($this->router->generate(
'admin_module_configure_action',
[
'module_name' => $moduleName,
],
UrlGeneratorInterface::ABSOLUTE_URL
)),
]);
}
}
/**
* We won't load an invalid class. This function will check any potential parse error.
*
* @param string $name The technical module name to check
*
* @return bool true if valid
*/
protected function isModuleMainClassValid(string $name): bool
{
if (!Validate::isModuleName($name)) {
return false;
}
$filePath = $this->getModulePath($name);
// Check if file exists (slightly faster than file_exists)
if (!file_exists($filePath)) {
return false;
}
$parser = (new PhpParser\ParserFactory())->create(PhpParser\ParserFactory::ONLY_PHP7);
$logContextData = [
'object_type' => 'Module',
'object_id' => LegacyModule::getModuleIdByName($name),
];
try {
$parser->parse(file_get_contents($filePath));
} catch (PhpParser\Error $exception) {
ErrorHelper::reportError($exception);
$this->logger->critical(
sprintf(
'Parse error detected in main class of module %s: %s',
$name,
$exception->getMessage()
),
$logContextData
);
return false;
}
$logger = $this->logger;
// -> Even if we do not detect any parse error in the file, we may have issues
// when trying to load the file. (i.e with additional require_once).
// -> We use an anonymous function here because if a test is made twice
// on the same module, the test on require_once would immediately return true
// (as the file would have already been evaluated).
$requireCorrect = function ($name) use ($filePath, $logger, $logContextData) {
try {
require_once $filePath;
} catch (Exception $e) {
$logger->error(
sprintf(
'Error while loading file of module %s. %s',
$name,
$e->getMessage()
),
$logContextData
);
return false;
}
return true;
};
return $requireCorrect($name);
}
protected function getModulePath(string $name): string
{
return $this->moduleDirectory . $name . '/' . $name . '.php';
}
}