diff --git a/changelog.md b/changelog.md index 7db6d6de7..8d4c43aeb 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ # Changelog # ## Changes in release 6.2.3 ## + Multi shop improvements with order states ++ Logging improvements ## Changes in release 6.2.2 ## + Error handling improvements diff --git a/composer.json b/composer.json index 6f4806454..499663da4 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "http-interop/http-factory-guzzle": "^1.1", "php-http/message-factory": "^1.1", "prestashop/prestashop-accounts-installer": "^1.0.4", - "prestashop/module-lib-mbo-installer": "^2.0" + "prestashop/module-lib-mbo-installer": "^2.0", + "dusank/knapsack": "^10.0" }, "require-dev": { "roave/security-advisories": "dev-latest", diff --git a/controllers/admin/AdminMollieLogsController.php b/controllers/admin/AdminMollieLogsController.php new file mode 100644 index 000000000..f0e331ff7 --- /dev/null +++ b/controllers/admin/AdminMollieLogsController.php @@ -0,0 +1,336 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +use Mollie\Config\Config; +use Mollie\Logger\LogFormatter; +use Mollie\Repository\MolLogRepositoryInterface; +use Mollie\Utility\ExceptionUtility; +use Mollie\Logger\Logger; +use Mollie\Logger\LoggerInterface; +use Mollie\Utility\VersionUtility; +use Mollie\Adapter\Shop; + +if (!defined('_PS_VERSION_')) { + exit; +} +class AdminMollieLogsController extends ModuleAdminController +{ + const FILE_NAME = 'AdminMollieLogsController'; + + const LOG_INFORMATION_TYPE_REQUEST = 'request'; + const LOG_INFORMATION_TYPE_RESPONSE = 'response'; + const LOG_INFORMATION_TYPE_CONTEXT = 'context'; + + public function __construct() + { + $this->bootstrap = true; + $this->table = 'log'; + $this->className = 'PrestaShopLogger'; + $this->lang = false; + $this->noLink = true; + + parent::__construct(); + + $this->toolbar_btn = []; + $this->fields_list = [ + 'id_log' => [ + 'title' => $this->module->l('ID', self::FILE_NAME), + 'align' => 'text-center', + 'class' => 'fixed-width-xs', + ], + 'severity' => [ + 'title' => $this->module->l('Severity (1-4)', self::FILE_NAME), + 'align' => 'text-center', + 'class' => 'fixed-width-xs', + 'callback' => 'printSeverityLevel', + ], + 'message' => [ + 'title' => $this->module->l('Message', self::FILE_NAME), + ], + 'request' => [ + 'title' => $this->module->l('Request', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printRequestButton', + 'orderby' => false, + 'search' => false, + 'remove_onclick' => true, + ], + 'response' => [ + 'title' => $this->module->l('Response', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printResponseButton', + 'orderby' => false, + 'search' => false, + 'remove_onclick' => true, + ], + 'context' => [ + 'title' => $this->module->l('Context', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printContextButton', + 'orderby' => false, + 'search' => false, + 'remove_onclick' => true, + ], + 'date_add' => [ + 'title' => $this->module->l('Date', self::FILE_NAME), + 'align' => 'right', + 'type' => 'datetime', + 'filter_key' => 'a!date_add', + ], + ]; + + $this->_orderBy = 'id_log'; + $this->_orderWay = 'desc'; + + $this->_select .= ' + REPLACE(a.`message`, "' . LogFormatter::MOLLIE_LOG_PREFIX . '", "") as message, + kpl.request, kpl.response, kpl.context + '; + + $shopIdCheck = ''; + + if (VersionUtility::isPsVersionGreaterOrEqualTo('1.7.8.0')) { + $shopIdCheck = ' AND kpl.id_shop = a.id_shop'; + } + + $this->_join .= ' JOIN ' . _DB_PREFIX_ . 'mol_logs kpl ON (kpl.id_log = a.id_log' . $shopIdCheck . ' AND a.object_type = "' . pSQL(Logger::LOG_OBJECT_TYPE) . '")'; + $this->_use_found_rows = false; + $this->list_no_link = true; + } + + /** + * @return false|string + * + * @throws SmartyException + */ + public function displaySeverityInformation() + { + return $this->context->smarty->fetch( + "{$this->module->getLocalPath()}views/templates/admin/logs/severity_levels.tpl" + ); + } + + /** + * @throws SmartyException + */ + public function initContent(): void + { + // NOTE: we cannot add new logs here. + if (isset($this->toolbar_btn['new'])) { + unset($this->toolbar_btn['new']); + } + + $this->content .= $this->displaySeverityInformation(); + + parent::initContent(); + } + + public function setMedia($isNewTheme = false): void + { + parent::setMedia($isNewTheme); + + Media::addJsDef([ + 'mollie' => [ + 'logsUrl' => Context::getContext()->link->getAdminLink(Config::LOGS_MODULE_TAB_CONTROLLER_NAME), + ], + ]); + + $this->addJS($this->module->getPathUri() . 'views/js/admin/logs/log.js', false); + $this->addCss($this->module->getPathUri() . 'views/css/admin/logs/log.css'); + } + + /** + * @param string $request + * @param array $data + * + * @return false|string + * + * @throws SmartyException + */ + public function printRequestButton(string $request, array $data) + { + return $this->getDisplayButton($data['id_log'], $request, self::LOG_INFORMATION_TYPE_REQUEST); + } + + /** + * @param string $response + * @param array $data + * + * @return false|string + * + * @throws SmartyException + */ + public function printResponseButton(string $response, array $data) + { + return $this->getDisplayButton($data['id_log'], $response, self::LOG_INFORMATION_TYPE_RESPONSE); + } + + /** + * @param string $context + * @param array $data + * + * @return false|string + * + * @throws SmartyException + */ + public function printContextButton(string $context, array $data) + { + return $this->getDisplayButton($data['id_log'], $context, self::LOG_INFORMATION_TYPE_CONTEXT); + } + + /** + * @param int $level + * + * @return false|string + * + * @throws SmartyException + */ + public function printSeverityLevel(int $level) + { + $this->context->smarty->assign([ + 'log_severity_level' => $level, + 'log_severity_level_informative' => defined('\PrestaShopLogger::LOG_SEVERITY_LEVEL_INFORMATIVE') ? + PrestaShopLogger::LOG_SEVERITY_LEVEL_INFORMATIVE : + Config::LOG_SEVERITY_LEVEL_INFORMATIVE, + 'log_severity_level_warning' => defined('\PrestaShopLogger::LOG_SEVERITY_LEVEL_WARNING') ? + PrestaShopLogger::LOG_SEVERITY_LEVEL_WARNING : + Config::LOG_SEVERITY_LEVEL_WARNING, + 'log_severity_level_error' => defined('\PrestaShopLogger::LOG_SEVERITY_LEVEL_ERROR') ? + PrestaShopLogger::LOG_SEVERITY_LEVEL_ERROR : + Config::LOG_SEVERITY_LEVEL_ERROR, + 'log_severity_level_major' => defined('\PrestaShopLogger::LOG_SEVERITY_LEVEL_MAJOR') ? + PrestaShopLogger::LOG_SEVERITY_LEVEL_MAJOR : + Config::LOG_SEVERITY_LEVEL_MAJOR, + ]); + + return $this->context->smarty->fetch( + "{$this->module->getLocalPath()}views/templates/admin/logs/severity_level_column.tpl" + ); + } + + /** + * @param int $logId + * @param string $data + * @param string $logInformationType + * + * @return false|string + * + * @throws SmartyException + */ + public function getDisplayButton(int $logId, string $data, string $logInformationType) + { + $unserializedData = json_decode($data); + + if (empty($unserializedData)) { + return '--'; + } + + $this->context->smarty->assign([ + 'log_id' => $logId, + 'log_information_type' => $logInformationType, + ]); + + return $this->context->smarty->fetch( + "{$this->module->getLocalPath()}views/templates/admin/logs/log_modal.tpl" + ); + } + + public function displayAjaxGetLog() + { + /** @var \Mollie\Adapter\ToolsAdapter $tools */ + $tools = $this->module->getService(\Mollie\Adapter\ToolsAdapter::class); + + /** @var MolLogRepositoryInterface $logRepository */ + $logRepository = $this->module->getService(MolLogRepositoryInterface::class); + + /** @var Shop $shopContext */ + $shopContext = $this->module->getService(Shop::class); + + $logId = $tools->getValueAsInt('log_id'); + + /** @var LoggerInterface $logger */ + $logger = $this->module->getService(LoggerInterface::class); + + try { + /** @var \MolLog|null $log */ + $log = $logRepository->findOneBy([ + 'id_log' => $logId, + 'id_shop' => $shopContext->getShop()->id, + ]); + } catch (\Exception $exception) { + $logger->error('Failed to find log', [ + 'context' => [ + 'id_log' => $logId, + 'id_shop' => $shopContext->getShop()->id, + ], + 'exceptions' => ExceptionUtility::getExceptions($exception), + ]); + + $this->ajaxResponse(json_encode([ + 'error' => true, + 'message' => $this->module->l('Failed to find log.', self::FILE_NAME), + ])); + } + + if (!$log) { + $logger->error('No log information found.', [ + 'context' => [ + 'id_log' => $logId, + 'id_shop' => $shopContext->getShop()->id, + ], + 'exceptions' => [], + ]); + + $this->ajaxResponse(json_encode([ + 'error' => true, + 'message' => $this->module->l('No log information found.', self::FILE_NAME), + ])); + } + + $this->ajaxResponse(json_encode([ + 'error' => false, + 'log' => [ + self::LOG_INFORMATION_TYPE_REQUEST => $log->request, + self::LOG_INFORMATION_TYPE_RESPONSE => $log->response, + self::LOG_INFORMATION_TYPE_CONTEXT => $log->context, + ], + ])); + } + + /** + * @param null $value + * @param null $controller + * @param null $method + * + * @return never + * + * @throws \PrestaShopException + */ + protected function ajaxResponse($value = null, $controller = null, $method = null) + { + /** @var LoggerInterface $logger */ + $logger = $this->module->getService(LoggerInterface::class); + + try { + $this->ajaxDie($value, $controller, $method); + } catch (\Exception $exception) { + $logger->error('Could not return ajax response', [ + 'context' => [ + 'response' => json_encode($value ?: []), + 'exceptions' => ExceptionUtility::getExceptions($exception), + ], + ]); + } + + exit; + } +} \ No newline at end of file diff --git a/controllers/admin/AdminMollieLogsParentController.php b/controllers/admin/AdminMollieLogsParentController.php new file mode 100644 index 000000000..e4ffe4218 --- /dev/null +++ b/controllers/admin/AdminMollieLogsParentController.php @@ -0,0 +1,22 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +if (!defined('_PS_VERSION_')) { + exit; +} +class AdminMollieLogsParentController extends ModuleAdminController +{ + public function init() + { + Tools::redirectAdmin($this->context->link->getAdminLink('AdminMollieLogs')); + } +} \ No newline at end of file diff --git a/controllers/admin/AdminMollieSettingsController.php b/controllers/admin/AdminMollieSettingsController.php index da395c199..da5871c28 100644 --- a/controllers/admin/AdminMollieSettingsController.php +++ b/controllers/admin/AdminMollieSettingsController.php @@ -12,6 +12,9 @@ declare(strict_types=1); +use Mollie\Logger\LoggerInterface; +use Mollie\Utility\ExceptionUtility; + if (!defined('_PS_VERSION_')) { exit; } @@ -91,6 +94,17 @@ private function initCloudSyncAndPsAccounts(): void public function postProcess() { + try { + /** @var \Mollie\Logger\LoggerInterface $logger */ + $logger = $this->module->getService(\Mollie\Logger\LoggerInterface::class); + + $logger->error('Failed to present payment option assets.', [ + 'context' => [], + 'exceptions' => ExceptionUtility::getExceptions(new Exception('Failed to present payment option assets.')), + ]); + } catch (PrestaShopException $e) { + + } /** @var \Mollie\Service\Content\TemplateParserInterface $templateParser */ $templateParser = $this->module->getService(\Mollie\Service\Content\TemplateParserInterface::class); @@ -100,7 +114,7 @@ public function postProcess() $this->module->getLocalPath() . 'views/templates/admin/logo.tpl' ); -// $this->initCloudSyncAndPsAccounts(); + $this->initCloudSyncAndPsAccounts(); /** @var \Mollie\Repository\ModuleRepository $moduleRepository */ $moduleRepository = $this->module->getService(\Mollie\Repository\ModuleRepository::class); diff --git a/mollie.php b/mollie.php index 21621619f..188e733e1 100755 --- a/mollie.php +++ b/mollie.php @@ -20,6 +20,7 @@ use Mollie\Exception\ShipmentCannotBeSentException; use Mollie\Handler\ErrorHandler\ErrorHandler; use Mollie\Handler\Shipment\ShipmentSenderHandlerInterface; +use Mollie\Logger\LoggerInterface; use Mollie\Logger\PrestaLoggerInterface; use Mollie\Provider\ProfileIdProviderInterface; use Mollie\Repository\MolOrderPaymentFeeRepositoryInterface; @@ -36,6 +37,7 @@ use Mollie\Subscription\Repository\LanguageRepository as LanguageAdapter; use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; use Mollie\Subscription\Validator\CanProductBeAddedToCartValidator; +use Mollie\Utility\ExceptionUtility; use Mollie\Utility\PsVersionUtility; use Mollie\Verification\IsPaymentInformationAvailable; use PrestaShop\PrestaShop\Core\Localization\Locale\Repository; @@ -74,6 +76,11 @@ class Mollie extends PaymentModule const ADMIN_MOLLIE_SUBSCRIPTION_ORDERS_CONTROLLER = 'AdminMollieSubscriptionOrders'; const ADMIN_MOLLIE_SUBSCRIPTION_FAQ_PARENT_CONTROLLER = 'AdminMollieSubscriptionFAQParent'; const ADMIN_MOLLIE_SUBSCRIPTION_FAQ_CONTROLLER = 'AdminMollieSubscriptionFAQ'; + + const ADMIN_MOLLIE_LOGS_CONTROLLER = 'AdminMollieLogs'; + + const ADMIN_MOLLIE_LOGS_PARENT_CONTROLLER = 'AdminMollieLogsParent'; + /** @var LeagueServiceContainerProvider */ private $containerProvider; @@ -84,7 +91,7 @@ public function __construct() { $this->name = 'mollie'; $this->tab = 'payments_gateways'; - $this->version = '6.2.2'; + $this->version = '6.2.3'; $this->author = 'Mollie B.V.'; $this->need_instance = 1; $this->bootstrap = true; @@ -865,6 +872,18 @@ public function getTabs() 'parent_class_name' => self::ADMIN_MOLLIE_TAB_CONTROLLER, 'module_tab' => true, ], + [ + 'name' => $this->l('Logs'), + 'class_name' => self::ADMIN_MOLLIE_LOGS_PARENT_CONTROLLER, + 'parent_class_name' => self::ADMIN_MOLLIE_CONTROLLER, + 'module_tab' => true, + ], + [ + 'name' => $this->l('Logs'), + 'class_name' => self::ADMIN_MOLLIE_LOGS_CONTROLLER, + 'parent_class_name' => self::ADMIN_MOLLIE_TAB_CONTROLLER, + 'module_tab' => true, + ], ]; } diff --git a/src/Adapter/ConfigurationAdapter.php b/src/Adapter/ConfigurationAdapter.php index 73cd185a7..bac043b55 100644 --- a/src/Adapter/ConfigurationAdapter.php +++ b/src/Adapter/ConfigurationAdapter.php @@ -74,6 +74,17 @@ public function delete($key): void \Configuration::deleteByName($this->parseKeyByEnvironment($key)); } + public function getAsInteger($id, ?int $shopId = null) + { + $result = $this->get($id, $shopId); + + if (in_array($result, ['null', 'false', '0', null, false, 0], true)) { + return 0; + } + + return (int) $result; + } + /** * @param string|array{production: string, sandbox: string} $key */ diff --git a/src/Adapter/ToolsAdapter.php b/src/Adapter/ToolsAdapter.php index 436f5a315..91adbfc11 100644 --- a/src/Adapter/ToolsAdapter.php +++ b/src/Adapter/ToolsAdapter.php @@ -47,4 +47,9 @@ public function isSubmit(string $string): bool { return (bool) Tools::isSubmit($string); } + + public function getValueAsInt($value, $defaultValue = 0) + { + return (int) Tools::getValue($value, $defaultValue); + } } diff --git a/src/Config/Config.php b/src/Config/Config.php index 946946d57..aed49aeb8 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -30,6 +30,7 @@ class Config const SENTRY_KEY = 'https://c2c43f02599847d682e0a1fb7843600f@o497594.ingest.sentry.io/5573860'; const SENTRY_ENV = 'MISSING_ENV'; + const LOGS_MODULE_TAB_CONTROLLER_NAME = 'AdminMollieLogs'; /** * Default payment method availability. @@ -330,6 +331,11 @@ class Config 'trustly' => 'Trustly', ]; + public const LOG_SEVERITY_LEVEL_INFORMATIVE = 1; + public const LOG_SEVERITY_LEVEL_WARNING = 2; + public const LOG_SEVERITY_LEVEL_ERROR = 3; + public const LOG_SEVERITY_LEVEL_MAJOR = 4; + const MOLLIE_BUTTON_ORDER_TOTAL_REFRESH = 'MOLLIE_BUTTON_ORDER_TOTAL_REFRESH'; // TODO migrate functions below to separate service diff --git a/src/Entity/MolLog.php b/src/Entity/MolLog.php new file mode 100644 index 000000000..dbda19c17 --- /dev/null +++ b/src/Entity/MolLog.php @@ -0,0 +1,45 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +class MolLog extends ObjectModel +{ + public $id_mollie_log; + + public $id_log; + + public $id_shop; + + public $request; + + public $response; + + public $context; + + public $date_add; + + public static $definition = [ + 'table' => 'mol_logs', + 'primary' => 'id_mollie_log', + 'fields' => [ + 'id_log' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], + 'id_shop' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], + 'request' => ['type' => self::TYPE_STRING, 'validate' => 'isString'], + 'response' => ['type' => self::TYPE_STRING, 'validate' => 'isString'], + 'context' => ['type' => self::TYPE_STRING, 'validate' => 'isString'], + 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], + ], + ]; +} diff --git a/src/Entity/ObjectModelEntityManager.php b/src/Entity/ObjectModelEntityManager.php new file mode 100644 index 000000000..bbb26b76c --- /dev/null +++ b/src/Entity/ObjectModelEntityManager.php @@ -0,0 +1,75 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +use Mollie\Service\EntityManager\EntityManagerInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class ObjectModelEntityManager implements EntityManagerInterface +{ + private $unitOfWork; + + public function __construct(ObjectModelUnitOfWork $unitOfWork) + { + $this->unitOfWork = $unitOfWork; + } + + /** + * @param \ObjectModel $model + * @param string $unitOfWorkType + * @param string|null $specificKey + * for example external_id key to make it easier to keep + * track of which object model is related to which external_id + */ + public function persist( + \ObjectModel $model, + string $unitOfWorkType, + ?string $specificKey = null + ): EntityManagerInterface { + $this->unitOfWork->setWork($model, $unitOfWorkType, $specificKey); + + return $this; + } + + /** + * @return array<\ObjectModel> + * + * @throws \PrestaShopDatabaseException + * @throws \PrestaShopException + */ + public function flush(): array + { + $persistenceModels = $this->unitOfWork->getWork(); + $persistedModels = []; + + foreach ($persistenceModels as $externalId => $persistenceModel) { + if ($persistenceModel['unit_of_work_type'] === ObjectModelUnitOfWork::UNIT_OF_WORK_SAVE) { + $persistenceModel['object']->save(); + } + + if ($persistenceModel['unit_of_work_type'] === ObjectModelUnitOfWork::UNIT_OF_WORK_DELETE) { + $persistenceModel['object']->delete(); + } + + if (!empty($externalId)) { + $persistedModels[$externalId] = $persistenceModel['object']; + } else { + $persistedModels[] = $persistenceModel['object']; + } + } + $this->unitOfWork->clearWork(); + + return $persistedModels; + } +} diff --git a/src/Entity/ObjectModelUnitOfWork.php b/src/Entity/ObjectModelUnitOfWork.php new file mode 100644 index 000000000..ed55cc82d --- /dev/null +++ b/src/Entity/ObjectModelUnitOfWork.php @@ -0,0 +1,53 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +use Mollie\Service\EntityManager\EntityManagerInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** In memory entity manager object model unit of work */ +class ObjectModelUnitOfWork +{ + public const UNIT_OF_WORK_SAVE = 'UNIT_OF_WORK_SAVE'; + public const UNIT_OF_WORK_DELETE = 'UNIT_OF_WORK_DELETE'; + + private $work = []; + + public function setWork(\ObjectModel $objectModel, string $unitOfWorkType, ?string $specificKey = null): void + { + $work = [ + 'unit_of_work_type' => $unitOfWorkType, + 'object' => $objectModel, + ]; + + if (!is_null($specificKey)) { + $this->work[$specificKey] = $work; + } else { + $this->work[] = $work; + } + } + + /** + * @return array + */ + public function getWork(): array + { + return $this->work; + } + + public function clearWork(): void + { + $this->work = []; + } +} diff --git a/src/Install/DatabaseTableInstaller.php b/src/Install/DatabaseTableInstaller.php index efeff8aae..6489402c9 100644 --- a/src/Install/DatabaseTableInstaller.php +++ b/src/Install/DatabaseTableInstaller.php @@ -138,6 +138,19 @@ private function getCommands() ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8; '; + $sql[] = ' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'mol_logs` ( + `id_mollie_log` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `id_log` INT(11), + `id_shop` INT(11), + `request` TEXT, + `response` TEXT, + `context` TEXT, + `date_add` DATETIME NOT NULL, + INDEX (`id_log`), + INDEX (`id_shop`) + ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;'; + return $sql; } diff --git a/src/Logger/LogFormatter.php b/src/Logger/LogFormatter.php new file mode 100644 index 000000000..dd671b4e1 --- /dev/null +++ b/src/Logger/LogFormatter.php @@ -0,0 +1,27 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Logger; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class LogFormatter implements LogFormatterInterface +{ + const MOLLIE_LOG_PREFIX = 'MOLLIE_MODULE_LOG:'; + + public function getMessage(string $message): string + { + return self::MOLLIE_LOG_PREFIX . ' ' . $message; + } +} \ No newline at end of file diff --git a/src/Logger/LogFormatterInterface.php b/src/Logger/LogFormatterInterface.php new file mode 100644 index 000000000..a0603cc45 --- /dev/null +++ b/src/Logger/LogFormatterInterface.php @@ -0,0 +1,23 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Logger; + +interface LogFormatterInterface +{ + /** + * @param string $message - an actual error message + * + * @return string + */ + public function getMessage(string $message): string; +} \ No newline at end of file diff --git a/src/Logger/Logger.php b/src/Logger/Logger.php new file mode 100644 index 000000000..9abb47357 --- /dev/null +++ b/src/Logger/Logger.php @@ -0,0 +1,172 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Logger; + +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Adapter\Context; +use Mollie\Config\Config; +use Mollie\Service\EntityManager\EntityManagerInterface; +use Mollie\Service\EntityManager\ObjectModelUnitOfWork; +use Mollie\Utility\NumberIdempotencyProvider; + +class Logger implements LoggerInterface +{ + public const FILE_NAME = 'Logger'; + + public const LOG_OBJECT_TYPE = 'mollieLog'; + + public const SEVERITY_INFO = 1; + public const SEVERITY_WARNING = 2; + public const SEVERITY_ERROR = 3; + + private $logFormatter; + private $configuration; + private $context; + private $entityManager; + private $idempotencyProvider; + private $prestashopLoggerRepository; + + public function __construct( + LogFormatterInterface $logFormatter, + ConfigurationAdapter $configuration, + Context $context, + EntityManagerInterface $entityManager, + NumberIdempotencyProvider $idempotencyProvider, + PrestashopLoggerRepositoryInterface $prestashopLoggerRepository + ) { + $this->logFormatter = $logFormatter; + $this->configuration = $configuration; + $this->context = $context; + $this->entityManager = $entityManager; + $this->idempotencyProvider = $idempotencyProvider; + $this->prestashopLoggerRepository = $prestashopLoggerRepository; + } + + public function emergency($message, array $context = []) + { + $this->log( + $this->configuration->getAsInteger( + 'PS_LOGS_BY_EMAIL', + $this->context->getShopId() + ), + $message, + $context + ); + } + + public function alert($message, array $context = []) + { + $this->log(self::SEVERITY_WARNING, $message, $context); + } + + public function critical($message, array $context = []) + { + $this->log( + $this->configuration->getAsInteger( // todo + 'PS_LOGS_BY_EMAIL', + $this->context->getShopId() + ), + $message, + $context + ); + } + + public function error($message, array $context = []) + { + $this->log(self::SEVERITY_ERROR, $message, $context); + } + + public function warning($message, array $context = []) + { + $this->log(self::SEVERITY_WARNING, $message, $context); + } + + public function notice($message, array $context = []) + { + $this->log(self::SEVERITY_INFO, $message, $context); + } + + public function info($message, array $context = []) + { + $this->log(self::SEVERITY_INFO, $message, $context); + } + + public function debug($message, array $context = []) + { + if (!$this->configuration->get(Config::MOLLIE_DEBUG_LOG)) { // todo check for value + return; + } + + $this->log(self::SEVERITY_INFO, $message, $context); + } + + public function log($level, $message, array $context = []) + { + $idempotencyKey = $this->idempotencyProvider->getIdempotencyKey(); + + \PrestaShopLogger::addLog( + $this->logFormatter->getMessage($message), + $level, + null, + self::LOG_OBJECT_TYPE, + $idempotencyKey + ); + + $logId = $this->prestashopLoggerRepository->getLogIdByObjectId( + $idempotencyKey, + $this->context->getShopId() + ); + + if (!$logId) { + return; + } + + $this->logContext($logId, $context); + } + + private function logContext($logId, array $context) + { + $request = ''; + $response = ''; + + if (isset($context['request'])) { + $request = $context['request']; + unset($context['request']); + } + + if (isset($context['response'])) { + $response = $context['response']; + unset($context['response']); + } + + $log = new \MolLog(); + $log->id_log = $logId; + $log->id_shop = $this->context->getShopId(); + $log->context = json_encode($this->getFilledContextWithShopData($context)); + $log->request = json_encode($request); + $log->response = json_encode($response); + + $this->entityManager->persist($log, ObjectModelUnitOfWork::UNIT_OF_WORK_SAVE); + $this->entityManager->flush(); + } + + private function getFilledContextWithShopData(array $context = []) + { + $context['context_id_customer'] = $this->context->getCustomerId(); + $context['id_shop'] = $this->context->getShopId(); + $context['currency'] = $this->context->getCurrencyIso(); + $context['id_language'] = $this->context->getLanguageId(); + + return $context; + } +} diff --git a/src/Logger/LoggerInterface.php b/src/Logger/LoggerInterface.php new file mode 100644 index 000000000..d6230e177 --- /dev/null +++ b/src/Logger/LoggerInterface.php @@ -0,0 +1,17 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Logger; + +interface LoggerInterface extends \Psr\Log\LoggerInterface +{ +} diff --git a/src/Logger/PrestashopLoggerRepositoryInterface.php b/src/Logger/PrestashopLoggerRepositoryInterface.php new file mode 100644 index 000000000..d7c19fbef --- /dev/null +++ b/src/Logger/PrestashopLoggerRepositoryInterface.php @@ -0,0 +1,32 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Logger; + +use Mollie\Repository\ReadOnlyCollectionRepositoryInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +interface PrestashopLoggerRepositoryInterface extends ReadOnlyCollectionRepositoryInterface +{ + /** + * @param string $objectId + * @param int $shopId + * + * @return int|null + */ + public function getLogIdByObjectId(string $objectId, ?int $shopId): ?int; + + public function prune(int $daysToKeep): void; +} diff --git a/src/Repository/CollectionRepository.php b/src/Repository/CollectionRepository.php new file mode 100644 index 000000000..467f6071f --- /dev/null +++ b/src/Repository/CollectionRepository.php @@ -0,0 +1,56 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Repository; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CollectionRepository implements ReadOnlyCollectionRepositoryInterface +{ + /** + * @var string + */ + private $fullyClassifiedClassName; + + public function __construct(string $fullyClassifiedClassName) + { + $this->fullyClassifiedClassName = $fullyClassifiedClassName; + } + + public function findAllInCollection($langId = null): \PrestaShopCollection + { + return new \PrestaShopCollection($this->fullyClassifiedClassName, $langId); + } + + /** + * @param array $keyValueCriteria + * @param $langId + * + * @return bool|\ObjectModel|null + * + * @throws \PrestaShopException + */ + public function findOneBy(array $keyValueCriteria, $langId = null): ?\ObjectModel + { + $psCollection = new \PrestaShopCollection($this->fullyClassifiedClassName, $langId); + + foreach ($keyValueCriteria as $field => $value) { + $psCollection = $psCollection->where($field, '=', $value); + } + + $first = $psCollection->getFirst(); + + return false === $first ? null : $first; + } +} diff --git a/src/Repository/MolLogRepository.php b/src/Repository/MolLogRepository.php new file mode 100644 index 000000000..70b874602 --- /dev/null +++ b/src/Repository/MolLogRepository.php @@ -0,0 +1,54 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Repository; + +use DusanKasan\Knapsack\Collection; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class MolLogRepository extends CollectionRepository implements MolLogRepositoryInterface +{ + public function __construct() + { + parent::__construct(\MolLog::class); + } + + public function prune(int $daysToKeep): void + { + Collection::from( + $this->findAllInCollection() + ->sqlWhere('DATEDIFF(NOW(),date_add) >= ' . $daysToKeep) + ) + ->each(function (\MolLog $log) { + $log->delete(); + }) + ->realize(); + } + + public function findAll(int $langId = null): \PrestaShopCollection + { + // TODO: Implement findAll() method. + } + + public function findAllBy(array $keyValueCriteria, int $langId = null): ?\PrestaShopCollection + { + // TODO: Implement findAllBy() method. + } + + public function findOrFail(array $keyValueCriteria, int $langId = null): \ObjectModel + { + // TODO: Implement findOrFail() method. + } +} diff --git a/src/Repository/MolLogRepositoryInterface.php b/src/Repository/MolLogRepositoryInterface.php new file mode 100644 index 000000000..e1f06ddb8 --- /dev/null +++ b/src/Repository/MolLogRepositoryInterface.php @@ -0,0 +1,24 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Repository; + +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +interface MolLogRepositoryInterface extends ReadOnlyRepositoryInterface +{ + public function prune(int $daysToKeep): void; +} diff --git a/src/Repository/PrestashopLoggerRepository.php b/src/Repository/PrestashopLoggerRepository.php new file mode 100644 index 000000000..bb22178f5 --- /dev/null +++ b/src/Repository/PrestashopLoggerRepository.php @@ -0,0 +1,63 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Repository; + +use DusanKasan\Knapsack\Collection; +use Mollie\Logger\Logger; +use Mollie\Logger\PrestashopLoggerRepositoryInterface; +use Mollie\Utility\VersionUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class PrestashopLoggerRepository extends CollectionRepository implements PrestashopLoggerRepositoryInterface +{ + public function __construct() + { + parent::__construct(\PrestaShopLogger::class); + } + + /** {@inheritDoc} */ + public function getLogIdByObjectId(string $objectId, ?int $shopId): ?int + { + $query = new \DbQuery(); + + $query + ->select('l.id_log') + ->from('log', 'l') + ->where('l.object_id = "' . pSQL($objectId) . '"') + ->orderBy('l.id_log DESC'); + + if (VersionUtility::isPsVersionGreaterOrEqualTo('1.7.8.0')) { + $query->where('l.id_shop = ' . (int) $shopId); + } + + $logId = \Db::getInstance()->getValue($query); + + return (int) $logId ?: null; + } + + public function prune(int $daysToKeep): void + { + Collection::from( + $this->findAllInCollection() + ->sqlWhere('DATEDIFF(NOW(),date_add) >= ' . $daysToKeep) + ->where('object_type', '=', Logger::LOG_OBJECT_TYPE) + ) + ->each(function (\PrestaShopLogger $log) { + $log->delete(); + }) + ->realize(); + } +} diff --git a/src/Repository/ReadOnlyCollectionRepositoryInterface.php b/src/Repository/ReadOnlyCollectionRepositoryInterface.php new file mode 100644 index 000000000..6af7aed4b --- /dev/null +++ b/src/Repository/ReadOnlyCollectionRepositoryInterface.php @@ -0,0 +1,39 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Repository; + +if (!defined('_PS_VERSION_')) { + exit; +} + +interface ReadOnlyCollectionRepositoryInterface +{ + /** + * @param int|null $langId - objects which ussualy are type of array will become strings. E.g + * $product->name is string instead of multidimensional array where key is id_language. + * Always pass language id + * unless there is a special need not to. Synchronization or smth. + * It saves quite a lot performance wise. + * + * @return \PrestaShopCollection + */ + public function findAllInCollection($langId = null): \PrestaShopCollection; + + /** + * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] + * @param int|null $langId + * + * @return \ObjectModel|null + */ + public function findOneBy(array $keyValueCriteria, $langId = null): ?\ObjectModel; +} diff --git a/src/Service/EntityManager/EntityManagerInterface.php b/src/Service/EntityManager/EntityManagerInterface.php index cb4877180..53d0e7813 100644 --- a/src/Service/EntityManager/EntityManagerInterface.php +++ b/src/Service/EntityManager/EntityManagerInterface.php @@ -12,17 +12,17 @@ namespace Mollie\Service\EntityManager; -use ObjectModel; -use PrestaShopException; - if (!defined('_PS_VERSION_')) { exit; } + interface EntityManagerInterface { /** - * @throws PrestaShopException + * @return array<\ObjectModel> + * + * @throws \PrestaShopException */ - public function flush(ObjectModel $model); + public function flush(): array; } diff --git a/src/Service/EntityManager/ObjectModelEntityManager.php b/src/Service/EntityManager/ObjectModelEntityManager.php new file mode 100644 index 000000000..ad6c7c0e6 --- /dev/null +++ b/src/Service/EntityManager/ObjectModelEntityManager.php @@ -0,0 +1,75 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Service\EntityManager; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class ObjectModelEntityManager implements EntityManagerInterface +{ + private $unitOfWork; + + public function __construct(ObjectModelUnitOfWork $unitOfWork) + { + $this->unitOfWork = $unitOfWork; + } + + /** + * @param \ObjectModel $model + * @param string $unitOfWorkType + * @param string|null $specificKey + * for example external_id key to make it easier to keep + * track of which object model is related to which external_id + */ + public function persist( + \ObjectModel $model, + string $unitOfWorkType, + ?string $specificKey = null + ): EntityManagerInterface { + $this->unitOfWork->setWork($model, $unitOfWorkType, $specificKey); + + return $this; + } + + /** + * @return array<\ObjectModel> + * + * @throws \PrestaShopDatabaseException + * @throws \PrestaShopException + */ + public function flush(): array + { + $persistenceModels = $this->unitOfWork->getWork(); + $persistedModels = []; + + foreach ($persistenceModels as $externalId => $persistenceModel) { + if ($persistenceModel['unit_of_work_type'] === ObjectModelUnitOfWork::UNIT_OF_WORK_SAVE) { + $persistenceModel['object']->save(); + } + + if ($persistenceModel['unit_of_work_type'] === ObjectModelUnitOfWork::UNIT_OF_WORK_DELETE) { + $persistenceModel['object']->delete(); + } + + if (!empty($externalId)) { + $persistedModels[$externalId] = $persistenceModel['object']; + } else { + $persistedModels[] = $persistenceModel['object']; + } + } + $this->unitOfWork->clearWork(); + + return $persistedModels; + } +} diff --git a/src/Service/EntityManager/ObjectModelUnitOfWork.php b/src/Service/EntityManager/ObjectModelUnitOfWork.php new file mode 100644 index 000000000..50e6385c6 --- /dev/null +++ b/src/Service/EntityManager/ObjectModelUnitOfWork.php @@ -0,0 +1,53 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Service\EntityManager; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** In memory entity manager object model unit of work */ +class ObjectModelUnitOfWork +{ + public const UNIT_OF_WORK_SAVE = 'UNIT_OF_WORK_SAVE'; + public const UNIT_OF_WORK_DELETE = 'UNIT_OF_WORK_DELETE'; + + private $work = []; + + public function setWork(\ObjectModel $objectModel, string $unitOfWorkType, ?string $specificKey = null): void + { + $work = [ + 'unit_of_work_type' => $unitOfWorkType, + 'object' => $objectModel, + ]; + + if (!is_null($specificKey)) { + $this->work[$specificKey] = $work; + } else { + $this->work[] = $work; + } + } + + /** + * @return array + */ + public function getWork(): array + { + return $this->work; + } + + public function clearWork(): void + { + $this->work = []; + } +} diff --git a/src/ServiceProvider/BaseServiceProvider.php b/src/ServiceProvider/BaseServiceProvider.php index 876fbf367..933fe69e5 100644 --- a/src/ServiceProvider/BaseServiceProvider.php +++ b/src/ServiceProvider/BaseServiceProvider.php @@ -16,6 +16,8 @@ use League\Container\Container; use Mollie; +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Adapter\Context; use Mollie\Builder\ApiTestFeedbackBuilder; use Mollie\Factory\ModuleFactory; use Mollie\Handler\Api\OrderEndpointPaymentTypeHandler; @@ -33,8 +35,13 @@ use Mollie\Handler\Shipment\ShipmentSenderHandler; use Mollie\Handler\Shipment\ShipmentSenderHandlerInterface; use Mollie\Install\UninstallerInterface; +use Mollie\Logger\LogFormatter; +use Mollie\Logger\LogFormatterInterface; +use Mollie\Logger\Logger; +use Mollie\Logger\LoggerInterface; use Mollie\Logger\PrestaLogger; use Mollie\Logger\PrestaLoggerInterface; +use Mollie\Logger\PrestashopLoggerRepositoryInterface; use Mollie\Provider\CreditCardLogoProvider; use Mollie\Provider\CustomLogoProviderInterface; use Mollie\Provider\EnvironmentVersionProvider; @@ -71,6 +78,8 @@ use Mollie\Repository\GenderRepositoryInterface; use Mollie\Repository\MolCustomerRepository; use Mollie\Repository\MolCustomerRepositoryInterface; +use Mollie\Repository\MolLogRepository; +use Mollie\Repository\MolLogRepositoryInterface; use Mollie\Repository\MolOrderPaymentFeeRepository; use Mollie\Repository\MolOrderPaymentFeeRepositoryInterface; use Mollie\Repository\OrderRepository; @@ -79,8 +88,10 @@ use Mollie\Repository\PaymentMethodRepositoryInterface; use Mollie\Repository\PendingOrderCartRuleRepository; use Mollie\Repository\PendingOrderCartRuleRepositoryInterface; +use Mollie\Repository\PrestashopLoggerRepository; use Mollie\Repository\ProductRepository; use Mollie\Repository\ProductRepositoryInterface; +use Mollie\Repository\ReadOnlyCollectionRepositoryInterface; use Mollie\Repository\TaxRepository; use Mollie\Repository\TaxRepositoryInterface; use Mollie\Repository\TaxRuleRepository; @@ -90,6 +101,9 @@ use Mollie\Service\ApiKeyService; use Mollie\Service\Content\SmartyTemplateParser; use Mollie\Service\Content\TemplateParserInterface; +use Mollie\Service\EntityManager\EntityManagerInterface; +use Mollie\Service\EntityManager\ObjectModelEntityManager; +use Mollie\Service\EntityManager\ObjectModelUnitOfWork; use Mollie\Service\PaymentMethod\PaymentMethodRestrictionValidation; use Mollie\Service\PaymentMethod\PaymentMethodRestrictionValidation\AmountPaymentMethodRestrictionValidator; use Mollie\Service\PaymentMethod\PaymentMethodRestrictionValidation\ApplePayPaymentMethodRestrictionValidator; @@ -123,6 +137,7 @@ use Mollie\Subscription\Utility\ClockInterface; use Mollie\Utility\Decoder\DecoderInterface; use Mollie\Utility\Decoder\JsonDecoder; +use Mollie\Utility\NumberIdempotencyProvider; use Mollie\Verification\PaymentType\CanBeRegularPaymentType; use Mollie\Verification\PaymentType\PaymentTypeVerificationInterface; use Mollie\Verification\Shipment\CanSendShipment; @@ -252,6 +267,22 @@ public function register(Container $container) $service = $this->addService($container, ApiTestFeedbackBuilder::class, ApiTestFeedbackBuilder::class); $this->addServiceArgument($service, $container->get(ModuleFactory::class)->getModuleVersion() ?? ''); $this->addServiceArgument($service, ApiKeyService::class); + + $this->addService($container, PrestashopLoggerRepositoryInterface::class, PrestashopLoggerRepository::class); + $this->addService($container, MolLogRepositoryInterface::class, MolLogRepository::class); + + $service = $this->addService($container, LoggerInterface::class, Logger::class); + $this->addServiceArgument($service, LogFormatterInterface::class); + $this->addServiceArgument($service, ConfigurationAdapter::class); + $this->addServiceArgument($service, Context::class); + $this->addServiceArgument($service, EntityManagerInterface::class); + $this->addServiceArgument($service, NumberIdempotencyProvider::class); + $this->addServiceArgument($service, PrestashopLoggerRepositoryInterface::class); + + $this->addService($container, LogFormatterInterface::class, LogFormatter::class); + + $service = $this->addService($container, EntityManagerInterface::class, ObjectModelEntityManager::class); + $this->addServiceArgument($service, ObjectModelUnitOfWork::class); } private function addService(Container $container, $className, $service) diff --git a/src/Utility/ExceptionUtility.php b/src/Utility/ExceptionUtility.php new file mode 100644 index 000000000..bbddd687a --- /dev/null +++ b/src/Utility/ExceptionUtility.php @@ -0,0 +1,49 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Utility; + +use Configuration; +use Mollie\Config\Config; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class ExceptionUtility +{ + public static function getExceptions(\Throwable $exception) + { + if (method_exists($exception, 'getExceptions')) { + return $exception->getExceptions(); + } + + return [self::toArray($exception)]; + } + + public static function toArray(\Throwable $exception): array + { + if (method_exists($exception, 'getContext')) { + $context = $exception->getContext(); + } else { + $context = []; + } + + return [ + 'message' => (string) $exception->getMessage(), + 'code' => (int) $exception->getCode(), + 'file' => (string) $exception->getFile(), + 'line' => (int) $exception->getLine(), + 'context' => $context, + ]; + } +} diff --git a/src/Service/EntityManager/ObjectModelManager.php b/src/Utility/NumberIdempotencyProvider.php similarity index 59% rename from src/Service/EntityManager/ObjectModelManager.php rename to src/Utility/NumberIdempotencyProvider.php index 62b1c4e64..962863a90 100644 --- a/src/Service/EntityManager/ObjectModelManager.php +++ b/src/Utility/NumberIdempotencyProvider.php @@ -10,21 +10,18 @@ * @codingStandardsIgnoreStart */ -namespace Mollie\Service\EntityManager; +namespace Mollie\Utility; -use ObjectModel; +use Language; if (!defined('_PS_VERSION_')) { exit; } -class ObjectModelManager implements EntityManagerInterface +class NumberIdempotencyProvider { - /** - * @throws \PrestaShopException - */ - public function flush(ObjectModel $model) + public function getIdempotencyKey(): string { - $model->save(); + return (string) mt_rand(); } } diff --git a/src/Utility/VersionUtility.php b/src/Utility/VersionUtility.php new file mode 100644 index 000000000..679ae02d2 --- /dev/null +++ b/src/Utility/VersionUtility.php @@ -0,0 +1,46 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Utility; + +class VersionUtility +{ + public static function isPsVersionLessThan($version): ?int + { + return version_compare(_PS_VERSION_, $version, '<'); + } + + public static function isPsVersionGreaterThan($version): ?int + { + return version_compare(_PS_VERSION_, $version, '>'); + } + + public static function isPsVersionGreaterOrEqualTo($version): ?int + { + return version_compare(_PS_VERSION_, $version, '>='); + } + + public static function isPsVersionLessThanOrEqualTo($version): ?int + { + return version_compare(_PS_VERSION_, $version, '<='); + } + + public static function isPsVersionEqualTo($version): ?int + { + return version_compare(_PS_VERSION_, $version, '='); + } + + public static function current(): string + { + return _PS_VERSION_; + } +} diff --git a/upgrade/Upgrade-6.2.3.php b/upgrade/Upgrade-6.2.3.php new file mode 100644 index 000000000..9b9ba3105 --- /dev/null +++ b/upgrade/Upgrade-6.2.3.php @@ -0,0 +1,72 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Config\Config; + +if (!defined('_PS_VERSION_')) { + exit; +} + +function upgrade_module_6_2_3(Mollie $module): bool +{ + // Create the new mol_logs table + $sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'mol_logs` ( + `id_mollie_log` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `id_log` INT(11), + `id_shop` INT(11), + `request` TEXT, + `response` TEXT, + `context` TEXT, + `date_add` DATETIME NOT NULL, + INDEX (`id_log`), + INDEX (`id_shop`) + ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;'; + + $isTableCreated = Db::getInstance()->execute($sql); + if (!$isTableCreated) { + return false; // If the table creation fails, return false + } + + // Install the necessary tabs + $tabs = [ + [ + 'name' => $module->l('Logs'), + 'class_name' => $module::ADMIN_MOLLIE_LOGS_PARENT_CONTROLLER, + 'parent_class_name' => $module::ADMIN_MOLLIE_CONTROLLER, + 'module_tab' => true, + ], + [ + 'name' => $module->l('Logs'), + 'class_name' => $module::ADMIN_MOLLIE_LOGS_CONTROLLER, + 'parent_class_name' => $module::ADMIN_MOLLIE_TAB_CONTROLLER, + 'module_tab' => true, + ], + ]; + + foreach ($tabs as $tabData) { + $tab = new Tab(); + $tab->name = []; + foreach (Language::getLanguages(true) as $lang) { + $tab->name[$lang['id_lang']] = $tabData['name']; + } + $tab->class_name = $tabData['class_name']; + $tab->id_parent = Tab::getIdFromClassName($tabData['parent_class_name']); + $tab->module = $module->name; + $tab->active = 1; + + if (!$tab->add()) { + return false; // Return false if any tab creation fails + } + } + + return $isTableCreated; +} diff --git a/views/css/admin/logs/index.php b/views/css/admin/logs/index.php new file mode 100755 index 000000000..c005d9315 --- /dev/null +++ b/views/css/admin/logs/index.php @@ -0,0 +1,21 @@ + jQuery.parseJSON(response)) + .then(data => { + $('.log-modal-content-spinner').addClass('hidden'); + + $('#log-modal-' + logId + '-request .log-modal-content-data').removeClass('hidden').html(prettyJson(data.log.request)); + $('#log-modal-' + logId + '-response .log-modal-content-data').removeClass('hidden').html(prettyJson(data.log.response)); + $('#log-modal-' + logId + '-context .log-modal-content-data').removeClass('hidden').html(prettyJson(data.log.context)); + }) + }); +}); + +function prettyJson(json) { + return JSON.stringify(jQuery.parseJSON(json), null, 2); +} diff --git a/views/templates/admin/logs/index.php b/views/templates/admin/logs/index.php new file mode 100755 index 000000000..c005d9315 --- /dev/null +++ b/views/templates/admin/logs/index.php @@ -0,0 +1,21 @@ + + {l s='View' mod='mollie'} + + + + + diff --git a/views/templates/admin/logs/severity_level_column.tpl b/views/templates/admin/logs/severity_level_column.tpl new file mode 100755 index 000000000..682ca4032 --- /dev/null +++ b/views/templates/admin/logs/severity_level_column.tpl @@ -0,0 +1,23 @@ +{** + * NOTICE OF LICENSE + * + * @author Mastercard Inc. www.mastercard.com + * @copyright Copyright (c) permanent, Mastercard Inc. + * @license Apache-2.0 + * + * @see /LICENSE + * + * International Registered Trademark & Property of Mastercard Inc. + *} + +{if $log_severity_level == $log_severity_level_informative} + {l s='Informative only' mod='mollie'} ({$log_severity_level|intval}) +{elseif $log_severity_level == $log_severity_level_warning} + {l s='Warning' mod='mollie'} ({$log_severity_level|intval}) +{elseif $log_severity_level == $log_severity_level_error} + {l s='Error' mod='mollie'} ({$log_severity_level|intval}) +{elseif $log_severity_level == $log_severity_level_major} + {l s='Major issue (crash)!' mod='mollie'} ({$log_severity_level|intval}) +{else} + {$log_severity_level|escape:'htmlall':'UTF-8'} +{/if} diff --git a/views/templates/admin/logs/severity_levels.tpl b/views/templates/admin/logs/severity_levels.tpl new file mode 100755 index 000000000..7b0a78a6b --- /dev/null +++ b/views/templates/admin/logs/severity_levels.tpl @@ -0,0 +1,25 @@ +{** + * NOTICE OF LICENSE + * + * @author Mastercard Inc. www.mastercard.com + * @copyright Copyright (c) permanent, Mastercard Inc. + * @license Apache-2.0 + * + * @see /LICENSE + * + * International Registered Trademark & Property of Mastercard Inc. + *} + +
+

+ + {l s='Severity levels:' mod='mollie'} +

+

{l s='Meaning of severity levels:' mod='mollie'}

+
    +
  1. {l s='Info' mod='mollie'}
  2. +
  3. {l s='Warning' mod='mollie'}
  4. +
  5. {l s='Error' mod='mollie'}
  6. +
  7. {l s='Fatal' mod='mollie'}
  8. +
+