From 1be638a970fa0cb41b0c42ee9627407d7e77823a Mon Sep 17 00:00:00 2001 From: Neur0toxine Date: Thu, 23 Apr 2020 15:49:29 +0300 Subject: [PATCH] proper cron & cli support, separated logs for different execution contexts (#65) * proper cron & cli support, separated logs for separate execution contexts * different job-scope locks for cli and manager * proper signal handling pass * add proper cli path * trick prestashop into thinking that we're calling getProducts from back office * create order on edit if it's not present in retailCRM * prevent discount to be higher than total * fix for striped logging --- CHANGELOG.md | 6 + VERSION | 2 +- retailcrm/cli.php | 54 ++++ retailcrm/controllers/front/Jobs.php | 9 +- retailcrm/lib/RetailcrmCli.php | 233 ++++++++++++++++++ retailcrm/lib/RetailcrmJobManager.php | 157 +++++++++--- retailcrm/lib/RetailcrmLogger.php | 95 +++++-- retailcrm/lib/RetailcrmTools.php | 2 +- .../events/RetailcrmAbandonedCartsEvent.php | 31 ++- .../lib/events/RetailcrmAbstractEvent.php | 92 +++++++ .../lib/events/RetailcrmEventInterface.php | 17 +- retailcrm/lib/events/RetailcrmExportEvent.php | 33 ++- retailcrm/lib/events/RetailcrmIcmlEvent.php | 21 +- .../lib/events/RetailcrmInventoriesEvent.php | 42 +++- .../lib/events/RetailcrmMissingEvent.php | 32 ++- retailcrm/lib/events/RetailcrmSyncEvent.php | 45 ++-- retailcrm/retailcrm.php | 35 +-- 17 files changed, 767 insertions(+), 139 deletions(-) create mode 100644 retailcrm/cli.php create mode 100644 retailcrm/lib/RetailcrmCli.php create mode 100644 retailcrm/lib/events/RetailcrmAbstractEvent.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea3252..f0ee6aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v3.0.5 +* Возможность установки регулярных задач в `cron`, CLI-интерфейс для запуска задач вручную +* Теперь при отсутствии редактируемого заказа в retailCRM он будет создаваться (ранее попытка редактирования приводила к ошибке) +* Доработана механика выгрузки брошенных корзин для большего соответствия ожиданиям API PrestaShop +* Исправление ошибки округления скидки в заказе + ## v3.0.4 * Проверка корректности генерации категорий в ICML diff --git a/VERSION b/VERSION index b0f2dcb..eca690e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.4 +3.0.5 diff --git a/retailcrm/cli.php b/retailcrm/cli.php new file mode 100644 index 0000000..2204437 --- /dev/null +++ b/retailcrm/cli.php @@ -0,0 +1,54 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +declare(ticks = 1); + +require_once __DIR__ . '/lib/RetailcrmCli.php'; + +function retailcrmCliInterruptHandler($signo) { + RetailcrmLogger::output('WARNING: Interrupt received, stopping...'); + RetailcrmCli::clearCurrentJob(null); + exit(1); +} + +if (php_sapi_name() == 'cli') { + $cli = new RetailcrmCli(__FILE__); + $cli->execute('retailcrmCliInterruptHandler'); +} else { + include_once __DIR__ . DIRECTORY_SEPARATOR . 'index.php'; +} diff --git a/retailcrm/controllers/front/Jobs.php b/retailcrm/controllers/front/Jobs.php index bd9f022..347b222 100644 --- a/retailcrm/controllers/front/Jobs.php +++ b/retailcrm/controllers/front/Jobs.php @@ -86,14 +86,7 @@ class RetailcrmJobsModuleFrontController extends ModuleFrontController */ protected function getData() { - RetailcrmJobManager::startJobs( - array( - 'RetailcrmAbandonedCartsEvent' => null, - 'RetailcrmIcmlEvent' => new \DateInterval('PT4H'), - 'RetailcrmSyncEvent' => new \DateInterval('PT7M'), - 'RetailcrmInventoriesEvent' => new \DateInterval('PT15M') - ) - ); + RetailcrmJobManager::startJobs(RetailCRM::getJobs()); return array('success' => true); } diff --git a/retailcrm/lib/RetailcrmCli.php b/retailcrm/lib/RetailcrmCli.php new file mode 100644 index 0000000..32c267d --- /dev/null +++ b/retailcrm/lib/RetailcrmCli.php @@ -0,0 +1,233 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { + date_default_timezone_set(@date_default_timezone_get()); +} + +require_once(dirname(__FILE__) . '/../../../config/config.inc.php'); +require_once(dirname(__FILE__) . '/../../../init.php'); +require_once(dirname(__FILE__) . '/../bootstrap.php'); + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Class RetailcrmCli + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @license GPL + * @link https://retailcrm.ru + */ +class RetailcrmCli +{ + const CURRENT_TASK_CLI = 'RETAILCRM_JOB_CURRENT_CLI'; + + /** @var string CLI path */ + private $cliPath; + + /** + * RetailcrmCli constructor. + * + * @param string $cliPath + */ + public function __construct($cliPath) + { + RetailcrmLogger::setCloneToStdout(true); + $this->cliPath = $cliPath; + } + + /** + * Run cli routine. Callable can be passed which will be used to handle terminate signals. + * + * @param callable|int|null $signalsHandler + */ + public function execute($signalsHandler = null) + { + if (function_exists('pcntl_signal')) { + if (function_exists('pcntl_async_signals')) { + pcntl_async_signals(true); + } + + if (function_exists('pcntl_signal_dispatch')) { + pcntl_signal_dispatch(); + } + + if (!empty($signalsHandler) && (is_callable($signalsHandler) || function_exists($signalsHandler))) { + pcntl_signal(SIGINT, $signalsHandler); + pcntl_signal(SIGTERM, $signalsHandler); + pcntl_signal(SIGHUP, $signalsHandler); + } + } else { + RetailcrmLogger::output('WARNING: cannot handle signals properly, force stop can cause problems!'); + } + + $shortopts = "j:"; + $longopts = array("job:", "reset-job-manager"); + $options = getopt($shortopts, $longopts); + $jobName = isset($options['j']) ? $options['j'] : (isset($options['job']) ? $options['job'] : null); + + if (isset($options['reset-job-manager'])) { + $this->resetJobManager(); + } elseif (empty($jobName)) { + $this->help(); + } else { + $this->runJob($jobName); + } + } + + /** + * Runs provided job + * + * @param string $jobName + */ + private function runJob($jobName) + { + try { + $result = RetailcrmJobManager::runJob($jobName, true, true); + RetailcrmLogger::output(sprintf( + 'Job %s was executed, result: %s', + $jobName, + $result ? 'true' : 'false' + )); + } catch (\Exception $exception) { + $this->printStack($exception); + self::clearCurrentJob($jobName); + } finally { + if (isset($result) && $result) { + self::clearCurrentJob($jobName); + } + } + } + + /** + * Prints error details + * + * @param \Exception $exception + */ + private function printStack($exception) + { + RetailcrmLogger::output(sprintf('Error while executing a job: %s', $exception->getMessage())); + RetailcrmLogger::output(sprintf('%s:%d', $exception->getFile(), $exception->getLine())); + RetailcrmLogger::output(); + RetailcrmLogger::output($exception->getTraceAsString()); + } + + /** + * Prints CLI help + */ + private function help() + { + RetailcrmLogger::output('Available jobs:'); + RetailcrmLogger::output(); + + foreach (array_keys(RetailCRM::getJobs()) as $job) { + RetailcrmLogger::output(sprintf(' - %s', $job)); + } + + RetailcrmLogger::output(); + RetailcrmLogger::output('Usage:'); + RetailcrmLogger::output(); + RetailcrmLogger::output(sprintf('> php %s -j - Runs provided job', $this->cliPath)); + RetailcrmLogger::output(sprintf('> php %s --job - Runs provided job', $this->cliPath)); + RetailcrmLogger::output(sprintf( + '> php %s --reset-job-manager - Will reset job manager internal timers & current job name', + $this->cliPath + )); + RetailcrmLogger::output(); + } + + /** + * Resets JobManager + */ + private function resetJobManager() + { + try { + if (RetailcrmJobManager::reset()) { + RetailcrmLogger::output('Job manager internal state was cleared.'); + } else { + RetailcrmLogger::output('Job manager internal state was NOT cleared.'); + } + } catch (\Exception $exception) { + $this->printStack($exception); + } + } + + /** + * Sets current running job. Every job must call this in CLI in order to work properly. + * Current running job will be cleared automatically after job was finished (or crashed). + * That way, JobManager will maintain it's data integrity and will coexist with manual runs and cron. + * + * @param string $job + * + * @return bool + */ + public static function setCurrentJob($job) + { + return (bool) Configuration::updateValue(self::CURRENT_TASK_CLI, $job); + } + + /** + * Returns current job or empty string if there's no jobs running at this moment + * + * @return string + */ + public static function getCurrentJob() + { + return (string) Configuration::get(self::CURRENT_TASK_CLI); + } + + /** + * Clears current job (job name must be provided to ensure we're removed correct job). + * + * @param string|null $job + * + * @return bool + */ + public static function clearCurrentJob($job) + { + if (Configuration::hasKey(self::CURRENT_TASK_CLI)) { + if (is_null($job) || self::getCurrentJob() == $job) { + return Configuration::deleteByName(self::CURRENT_TASK_CLI); + } + } + + return true; + } +} diff --git a/retailcrm/lib/RetailcrmJobManager.php b/retailcrm/lib/RetailcrmJobManager.php index d5aa2ad..3ce455b 100644 --- a/retailcrm/lib/RetailcrmJobManager.php +++ b/retailcrm/lib/RetailcrmJobManager.php @@ -59,6 +59,7 @@ class RetailcrmJobManager { const LAST_RUN_NAME = 'RETAILCRM_LAST_RUN'; const IN_PROGRESS_NAME = 'RETAILCRM_JOBS_IN_PROGRESS'; + const CURRENT_TASK = 'RETAILCRM_JOB_CURRENT'; /** * Entry point for all jobs. @@ -157,7 +158,11 @@ class RetailcrmJobManager if (isset($shouldRunAt) && $shouldRunAt <= $current) { RetailcrmLogger::writeDebug(__METHOD__, sprintf('Executing job %s', $job)); - RetailcrmJobManager::runJob($job, $runOnceInContext); + $result = RetailcrmJobManager::runJob($job, $runOnceInContext); + RetailcrmLogger::writeDebug( + __METHOD__, + sprintf('Executed job %s, result: %s', $job, $result ? 'true' : 'false') + ); $lastRuns[$job] = new \DateTime('now'); } } catch (\Exception $exception) { @@ -167,6 +172,11 @@ class RetailcrmJobManager $exception->getTraceAsString(), $job ); + self::clearCurrentJob($job); + } finally { + if (isset($result) && $result) { + self::clearCurrentJob($job); + } } } @@ -248,39 +258,29 @@ class RetailcrmJobManager * * @param string $job * @param bool $once + * @param bool $cliMode * + * @return bool * @throws \RetailcrmJobManagerException */ - public static function runJob($job, $once = false) + public static function runJob($job, $once = false, $cliMode = false) { + $jobName = self::escapeJobName($job); $jobFile = implode( DIRECTORY_SEPARATOR, - array(_PS_ROOT_DIR_, 'modules', 'retailcrm', 'lib', 'events', self::escapeJobName($job) . '.php') + array(_PS_ROOT_DIR_, 'modules', 'retailcrm', 'lib', 'events', $jobName . '.php') ); if (!file_exists($jobFile)) { throw new \RetailcrmJobManagerException('Cannot find job', $job); } - static::execPHP($jobFile, $once); - } - - /** - * Runs PHP file - * - * @param string $fileCommandLine - * @param bool $once - * - * @throws \RetailcrmJobManagerException - */ - private static function execPHP($fileCommandLine, $once = false) - { - $error = null; - try { - static::execHere($fileCommandLine, $once); + return static::execHere($jobName, $jobFile, $once, $cliMode); + } catch (\RetailcrmJobManagerException $exception) { + throw $exception; } catch (\Exception $exception) { - throw new RetailcrmJobManagerException($exception->getMessage(), $fileCommandLine); + throw new RetailcrmJobManagerException($exception->getMessage(), $jobFile); } } @@ -300,6 +300,71 @@ class RetailcrmJobManager return (string)base64_encode(json_encode($jobs)); } + /** + * Sets current running job. Every job must call this in order to work properly. + * Current running job will be cleared automatically after job was finished (or crashed). + * That way, JobManager will maintain it's data integrity and will coexist with manual runs and cron. + * + * @param string $job + * + * @return bool + */ + public static function setCurrentJob($job) + { + return (bool) Configuration::updateValue(self::CURRENT_TASK, $job); + } + + /** + * Returns current job or empty string if there's no jobs running at this moment + * + * @return string + */ + public static function getCurrentJob() + { + return (string) Configuration::get(self::CURRENT_TASK); + } + + /** + * Clears current job (job name must be provided to ensure we're removed correct job). + * + * @param string|null $job + * + * @return bool + */ + public static function clearCurrentJob($job) + { + if (Configuration::hasKey(self::CURRENT_TASK)) { + if (is_null($job) || self::getCurrentJob() == $job) { + return Configuration::deleteByName(self::CURRENT_TASK); + } + } + + return true; + } + + /** + * Resets JobManager internal state. Doesn't work if JobManager is active. + * + * @return bool + * @throws \Exception + */ + public static function reset() + { + $result = true; + + if (Configuration::hasKey(self::CURRENT_TASK)) { + $result = Configuration::deleteByName(self::CURRENT_TASK); + } + + if (Configuration::hasKey(self::LAST_RUN_NAME)) { + $result = $result && Configuration::deleteByName(self::LAST_RUN_NAME); + } + + self::unlock(); + + return $result; + } + /** * Writes error to log and returns 500 * @@ -327,25 +392,34 @@ class RetailcrmJobManager } /** - * Executes php script in this context, without hanging up request + * Executes job without hanging up request (if executed by a hit). + * Returns execution result from job. * + * @param string $jobName * @param string $phpScript * @param bool $once + * @param bool $cliMode + * + * @return bool + * @throws \RetailcrmJobManagerException */ - private static function execHere($phpScript, $once = false) + private static function execHere($jobName, $phpScript, $once = false, $cliMode = false) { - ignore_user_abort(true); set_time_limit(static::getTimeLimit()); - if (version_compare(phpversion(), '7.0.16', '>=') && - function_exists('fastcgi_finish_request') - ) { - if (!headers_sent()) { - header('Expires: Thu, 19 Nov 1981 08:52:00 GMT'); - header('Cache-Control: no-store, no-cache, must-revalidate'); - } + if (!$cliMode) { + ignore_user_abort(true); - fastcgi_finish_request(); + if (version_compare(phpversion(), '7.0.16', '>=') && + function_exists('fastcgi_finish_request') + ) { + if (!headers_sent()) { + header('Expires: Thu, 19 Nov 1981 08:52:00 GMT'); + header('Cache-Control: no-store, no-cache, must-revalidate'); + } + + fastcgi_finish_request(); + } } if ($once) { @@ -353,6 +427,27 @@ class RetailcrmJobManager } else { require($phpScript); } + + if (!class_exists($jobName)) { + throw new \RetailcrmJobManagerException(sprintf( + 'The job file "%s" has been found, but job class "%s" was not present in there.', + $phpScript, + $jobName + )); + } + + $job = new $jobName(); + + if (!($job instanceof RetailcrmEventInterface)) { + throw new \RetailcrmJobManagerException(sprintf( + 'Class "%s" must implement RetailcrmEventInterface', + $jobName + )); + } + + $job->setCliMode($cliMode); + + return $job->execute(); } /** diff --git a/retailcrm/lib/RetailcrmLogger.php b/retailcrm/lib/RetailcrmLogger.php index e2c4285..42188cd 100644 --- a/retailcrm/lib/RetailcrmLogger.php +++ b/retailcrm/lib/RetailcrmLogger.php @@ -47,6 +47,18 @@ if (!defined('_PS_VERSION_')) { */ class RetailcrmLogger { + static $cloneToStdout; + + /** + * Set to true if you want all output to be cloned into STDOUT + * + * @param bool $cloneToStdout + */ + public static function setCloneToStdout($cloneToStdout) + { + self::$cloneToStdout = $cloneToStdout; + } + /** * Write entry to log * @@ -55,16 +67,22 @@ class RetailcrmLogger */ public static function writeCaller($caller, $message) { - error_log( - sprintf( - '[%s] @ [%s] %s' . PHP_EOL, - date(DATE_RFC3339), - $caller, - $message - ), - 3, - static::getErrorLog() + $result = sprintf( + '[%s] @ [%s] %s' . PHP_EOL, + date(DATE_RFC3339), + $caller, + $message ); + + error_log( + $result, + 3, + static::getLogFile() + ); + + if (self::$cloneToStdout) { + self::output($result, ''); + } } /** @@ -74,15 +92,34 @@ class RetailcrmLogger */ public static function writeNoCaller($message) { - error_log( - sprintf( - '[%s] %s' . PHP_EOL, - date(DATE_RFC3339), - $message - ), - 3, - static::getErrorLog() + $result = sprintf( + '[%s] %s' . PHP_EOL, + date(DATE_RFC3339), + $message ); + + error_log( + $result, + 3, + static::getLogFile() + ); + + if (self::$cloneToStdout) { + self::output($result, ''); + } + } + + /** + * Output message to stdout + * + * @param string $message + * @param string $end + */ + public static function output($message = '', $end = PHP_EOL) + { + if (php_sapi_name() == 'cli') { + echo $message . $end; + } } /** @@ -103,16 +140,34 @@ class RetailcrmLogger } /** - * Returns error log path + * Returns log file path * * @return string */ - protected static function getErrorLog() + public static function getLogFile() { if (!defined('_PS_ROOT_DIR_')) { return ''; } - return _PS_ROOT_DIR_ . '/retailcrm.log'; + return _PS_ROOT_DIR_ . '/retailcrm_' . self::getLogFilePrefix() . '.log'; + } + + /** + * Returns log file prefix based on current environment + * + * @return string + */ + private static function getLogFilePrefix() + { + if (php_sapi_name() == 'cli') { + if (isset($_SERVER['TERM'])) { + return 'cli'; + } else { + return 'cron'; + } + } + + return 'web'; } } \ No newline at end of file diff --git a/retailcrm/lib/RetailcrmTools.php b/retailcrm/lib/RetailcrmTools.php index 250dd40..cf53530 100644 --- a/retailcrm/lib/RetailcrmTools.php +++ b/retailcrm/lib/RetailcrmTools.php @@ -359,7 +359,7 @@ class RetailcrmTools $apiKey = Configuration::get(RetailCRM::API_KEY); if (!empty($apiUrl) && !empty($apiKey)) { - return new RetailcrmProxy($apiUrl, $apiKey, RetailCRM::getErrorLog()); + return new RetailcrmProxy($apiUrl, $apiKey, RetailcrmLogger::getLogFile()); } return null; diff --git a/retailcrm/lib/events/RetailcrmAbandonedCartsEvent.php b/retailcrm/lib/events/RetailcrmAbandonedCartsEvent.php index 4cdfee8..e6fcc36 100644 --- a/retailcrm/lib/events/RetailcrmAbandonedCartsEvent.php +++ b/retailcrm/lib/events/RetailcrmAbandonedCartsEvent.php @@ -38,23 +38,33 @@ require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); -class RetailcrmAbandonedCartsEvent implements RetailcrmEventInterface +class RetailcrmAbandonedCartsEvent extends RetailcrmAbstractEvent implements RetailcrmEventInterface { /** * @inheritDoc */ public function execute() { + if ($this->isRunning()) { + return false; + } + + $this->setRunning(); + $syncCartsActive = Configuration::get(RetailCRM::SYNC_CARTS_ACTIVE); + if (empty($syncCartsActive)) { - return; + RetailcrmLogger::writeCaller(__METHOD__, 'Abandoned carts is disabled, skipping...'); + + return true; } $api = RetailcrmTools::getApiClient(); if (empty($api)) { - RetailcrmLogger::writeCaller('abandonedCarts', 'set api key & url first'); - return; + RetailcrmLogger::writeCaller(__METHOD__, 'Set API key & URL first'); + + return true; } RetailcrmCartUploader::init(); @@ -63,8 +73,15 @@ class RetailcrmAbandonedCartsEvent implements RetailcrmEventInterface RetailcrmCartUploader::$syncStatus = Configuration::get(RetailCRM::SYNC_CARTS_STATUS); RetailcrmCartUploader::setSyncDelay(Configuration::get(RetailCRM::SYNC_CARTS_DELAY)); RetailcrmCartUploader::run(); + + return true; + } + + /** + * @inheritDoc + */ + public function getName() + { + return 'RetailcrmAbandonedCartsEvent'; } } - -$event = new RetailcrmAbandonedCartsEvent(); -$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmAbstractEvent.php b/retailcrm/lib/events/RetailcrmAbstractEvent.php new file mode 100644 index 0000000..cde1bab --- /dev/null +++ b/retailcrm/lib/events/RetailcrmAbstractEvent.php @@ -0,0 +1,92 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); + +abstract class RetailcrmAbstractEvent implements RetailcrmEventInterface +{ + private $cliMode; + + /** + * @inheritDoc + */ + abstract public function execute(); + + /** + * @inheritDoc + */ + public function getName() + { + throw new InvalidArgumentException("Not implemented."); + } + + /** + * Sets cli mode to true. CLI mode here stands for any execution outside of JobManager context. + * + * @param bool $mode + */ + public function setCliMode($mode) + { + $this->cliMode = (bool) $mode; + } + + /** + * Returns true if current job is running now + * + * @return bool + */ + protected function isRunning() + { + return (strcmp(RetailcrmJobManager::getCurrentJob(), $this->getName() === 0)) + || (strcmp(RetailcrmCli::getCurrentJob(), $this->getName() === 0)); + } + + /** + * Sets current job as active based on execution context. + * + * @return bool + */ + protected function setRunning() + { + if ($this->cliMode) { + return RetailcrmCli::setCurrentJob($this->getName()); + } + + return RetailcrmJobManager::setCurrentJob($this->getName()); + } +} diff --git a/retailcrm/lib/events/RetailcrmEventInterface.php b/retailcrm/lib/events/RetailcrmEventInterface.php index 2cc82c0..07dc316 100644 --- a/retailcrm/lib/events/RetailcrmEventInterface.php +++ b/retailcrm/lib/events/RetailcrmEventInterface.php @@ -39,9 +39,24 @@ interface RetailcrmEventInterface { /** - * Execute event + * Executes event. Event MUST return true if it was executed. False should be returned only when event + * found out that it's already running. * * @return bool */ public function execute(); + + /** + * Returns event name + * + * @return string + */ + public function getName(); + + /** + * Sets cli mode to true. CLI mode here stands for any execution outside of JobManager context. + * + * @param bool $mode + */ + public function setCliMode($mode); } diff --git a/retailcrm/lib/events/RetailcrmExportEvent.php b/retailcrm/lib/events/RetailcrmExportEvent.php index afc47fd..1092de2 100644 --- a/retailcrm/lib/events/RetailcrmExportEvent.php +++ b/retailcrm/lib/events/RetailcrmExportEvent.php @@ -38,18 +38,25 @@ require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); -class RetailcrmExportEvent implements RetailcrmEventInterface +class RetailcrmExportEvent extends RetailcrmAbstractEvent implements RetailcrmEventInterface { /** * @inheritDoc */ public function execute() { + if ($this->isRunning()) { + return false; + } + + $this->setRunning(); + $api = RetailcrmTools::getApiClient(); if (empty($api)) { - RetailcrmLogger::writeCaller('orderHistory', 'set api key & url first'); - exit(); + RetailcrmLogger::writeCaller(__METHOD__, 'Set API key & URL first'); + + return true; } $orders = array(); @@ -84,11 +91,8 @@ class RetailcrmExportEvent implements RetailcrmEventInterface $orders[] = $orderBuilder->buildOrderWithPreparedCustomer(); } catch (\InvalidArgumentException $exception) { RetailcrmLogger::writeCaller('export', $exception->getMessage()); - RetailcrmLogger::writeNoCaller($e->getTraceAsString()); - - if (PHP_SAPI == 'cli') { - echo $exception->getMessage() . PHP_EOL; - } + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + RetailcrmLogger::output($exception->getMessage()); } time_nanosleep(0, 500000000); @@ -101,8 +105,15 @@ class RetailcrmExportEvent implements RetailcrmEventInterface foreach ($orders as $chunk) { $api->ordersUpload($chunk); } + + return true; + } + + /** + * @inheritDoc + */ + public function getName() + { + return 'RetailcrmExportEvent'; } } - -$event = new RetailcrmExportEvent(); -$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmIcmlEvent.php b/retailcrm/lib/events/RetailcrmIcmlEvent.php index 9e3ee87..0c50e46 100644 --- a/retailcrm/lib/events/RetailcrmIcmlEvent.php +++ b/retailcrm/lib/events/RetailcrmIcmlEvent.php @@ -38,20 +38,33 @@ require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); -class RetailcrmIcmlEvent implements RetailcrmEventInterface +class RetailcrmIcmlEvent extends RetailcrmAbstractEvent implements RetailcrmEventInterface { /** * @inheritDoc */ public function execute() { + if ($this->isRunning()) { + return false; + } + + $this->setRunning(); + $job = new RetailcrmCatalog(); $data = $job->getData(); $icml = new RetailcrmIcml(Configuration::get('PS_SHOP_NAME'), _PS_ROOT_DIR_ . '/retailcrm.xml'); $icml->generate($data[0], $data[1]); + + return true; + } + + /** + * @inheritDoc + */ + public function getName() + { + return 'RetailcrmIcmlEvent'; } } - -$event = new RetailcrmIcmlEvent(); -$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmInventoriesEvent.php b/retailcrm/lib/events/RetailcrmInventoriesEvent.php index df4d818..b8445d5 100644 --- a/retailcrm/lib/events/RetailcrmInventoriesEvent.php +++ b/retailcrm/lib/events/RetailcrmInventoriesEvent.php @@ -38,33 +38,49 @@ require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); -class RetailcrmInventoriesEvent implements RetailcrmEventInterface +class RetailcrmInventoriesEvent extends RetailcrmAbstractEvent implements RetailcrmEventInterface { /** * @inheritDoc */ public function execute() { + if ($this->isRunning()) { + return false; + } + + $this->setRunning(); + + if (!Configuration::get(RetailCRM::ENABLE_BALANCES_RECEIVING)) { + RetailcrmLogger::writeDebug( + 'RetailcrmInventoriesEvent', + 'Balances receiving is not enabled, skipping...' + ); + + return true; + } + $apiUrl = Configuration::get(RetailCRM::API_URL); $apiKey = Configuration::get(RetailCRM::API_KEY); if (!empty($apiUrl) && !empty($apiKey)) { - RetailcrmInventories::$api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log'); + RetailcrmInventories::$api = new RetailcrmProxy($apiUrl, $apiKey, RetailcrmLogger::getLogFile()); } else { RetailcrmLogger::writeCaller('inventories', 'set api key & url first'); - exit(); + + return true; } RetailcrmInventories::loadStocks(); + + return true; + } + + /** + * @inheritDoc + */ + public function getName() + { + return 'RetailcrmInventoriesEvent'; } } - -if (Configuration::get(RetailCRM::ENABLE_BALANCES_RECEIVING)) { - $event = new RetailcrmInventoriesEvent(); - $event->execute(); -} else { - RetailcrmLogger::writeDebug( - 'RetailcrmInventoriesEvent', - 'Balances receiving is not enabled, skipping...' - ); -} diff --git a/retailcrm/lib/events/RetailcrmMissingEvent.php b/retailcrm/lib/events/RetailcrmMissingEvent.php index 03b7305..d3612f0 100644 --- a/retailcrm/lib/events/RetailcrmMissingEvent.php +++ b/retailcrm/lib/events/RetailcrmMissingEvent.php @@ -38,29 +38,37 @@ require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); -class RetailcrmMissingEvent implements RetailcrmEventInterface +class RetailcrmMissingEvent extends RetailcrmAbstractEvent implements RetailcrmEventInterface { /** * @inheritDoc */ public function execute() { + if ($this->isRunning()) { + return false; + } + + $this->setRunning(); + $shortopts = 'o:'; $options = getopt($shortopts); if (!isset($options['o'])) { - echo ('Parameter -o is missing'); - exit(); + echo 'Parameter -o is missing'; + + return true; } $apiUrl = Configuration::get(RetailCRM::API_URL); $apiKey = Configuration::get(RetailCRM::API_KEY); if (!empty($apiUrl) && !empty($apiKey)) { - $api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log'); + $api = new RetailcrmProxy($apiUrl, $apiKey, RetailcrmLogger::getLogFile()); } else { - echo('Set api key & url first'); - exit(); + echo 'Set api key & url first'; + + return true; } $delivery = json_decode(Configuration::get(RetailCRM::DELIVERY), true); @@ -97,7 +105,7 @@ class RetailcrmMissingEvent implements RetailcrmEventInterface $order['lastName'] = $orderCustomer->lastname; $order['email'] = $orderCustomer->email; } else { - exit(); + return true; } } @@ -195,8 +203,12 @@ class RetailcrmMissingEvent implements RetailcrmEventInterface } $api->ordersEdit($order); + + return true; + } + + public function getName() + { + return 'RetailcrmMissingEvent'; } } - -$event = new RetailcrmMissingEvent(); -$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmSyncEvent.php b/retailcrm/lib/events/RetailcrmSyncEvent.php index d2ca66f..e038cca 100644 --- a/retailcrm/lib/events/RetailcrmSyncEvent.php +++ b/retailcrm/lib/events/RetailcrmSyncEvent.php @@ -38,36 +38,51 @@ require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); -class RetailcrmSyncEvent implements RetailcrmEventInterface +class RetailcrmSyncEvent extends RetailcrmAbstractEvent implements RetailcrmEventInterface { /** * @inheritDoc */ public function execute() { + if ($this->isRunning()) { + return false; + } + + $this->setRunning(); + + if (!Configuration::get(RetailCRM::ENABLE_HISTORY_UPLOADS)) { + RetailcrmLogger::writeDebug( + __METHOD__, + 'History uploads is not enabled, skipping...' + ); + + return true; + } + $apiUrl = Configuration::get(RetailCRM::API_URL); $apiKey = Configuration::get(RetailCRM::API_KEY); RetailcrmHistory::$default_lang = (int) Configuration::get('PS_LANG_DEFAULT'); if (!empty($apiUrl) && !empty($apiKey)) { - RetailcrmHistory::$api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log'); + RetailcrmHistory::$api = new RetailcrmProxy($apiUrl, $apiKey, RetailcrmLogger::getLogFile()); } else { - RetailcrmLogger::writeCaller('orderHistory', 'set api key & url first'); - exit(); + RetailcrmLogger::writeCaller(__METHOD__, 'Set api key & url first'); + + return true; } RetailcrmHistory::customersHistory(); RetailcrmHistory::ordersHistory(); + + return true; + } + + /** + * @inheritDoc + */ + public function getName() + { + return 'RetailcrmSyncEvent'; } } - -if (Configuration::get(RetailCRM::ENABLE_HISTORY_UPLOADS)) { - $event = new RetailcrmSyncEvent(); - $event->execute(); -} else { - RetailcrmLogger::writeDebug( - 'RetailcrmSyncEvent', - 'History uploads is not enabled, skipping...' - ); -} - diff --git a/retailcrm/retailcrm.php b/retailcrm/retailcrm.php index 8488704..0c1b2d3 100644 --- a/retailcrm/retailcrm.php +++ b/retailcrm/retailcrm.php @@ -116,7 +116,7 @@ class RetailCRM extends Module { $this->name = 'retailcrm'; $this->tab = 'export'; - $this->version = '3.0.4'; + $this->version = '3.0.5'; $this->author = 'DIGITAL RETAIL TECHNOLOGIES SL'; $this->displayName = $this->l('retailCRM'); $this->description = $this->l('Integration module for retailCRM'); @@ -128,7 +128,7 @@ class RetailCRM extends Module $this->apiKey = Configuration::get(static::API_KEY); $this->ps_versions_compliancy = array('min' => '1.6.1.0', 'max' => _PS_VERSION_); $this->psVersion = Tools::substr(_PS_VERSION_, 0, 3); - $this->log = static::getErrorLog(); + $this->log = RetailcrmLogger::getLogFile(); $this->module_key = 'dff3095326546f5fe8995d9e86288491'; $this->assetsBase = Tools::getShopDomainSsl(true, true) . @@ -186,7 +186,7 @@ class RetailCRM extends Module $api = new RetailcrmProxy( $apiUrl, $apiKey, - static::getErrorLog() + RetailcrmLogger::getLogFile() ); $clientId = Configuration::get(static::CLIENT_ID); @@ -946,20 +946,6 @@ class RetailCRM extends Module return $output; } - /** - * Returns error log path - * - * @return string - */ - public static function getErrorLog() - { - if (!defined('_PS_ROOT_DIR_')) { - return ''; - } - - return _PS_ROOT_DIR_ . '/retailcrm.log'; - } - /** * Loads data from modules list cache * @@ -1148,6 +1134,21 @@ class RetailCRM extends Module } } + /** + * Returns jobs list + * + * @return array + */ + public static function getJobs() + { + return array( + 'RetailcrmAbandonedCartsEvent' => null, + 'RetailcrmIcmlEvent' => new \DateInterval('PT4H'), + 'RetailcrmSyncEvent' => new \DateInterval('PT7M'), + 'RetailcrmInventoriesEvent' => new \DateInterval('PT15M') + ); + } + /** * Synchronized cartsIds time choice *