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
This commit is contained in:
Pavel 2020-04-23 15:49:29 +03:00 committed by GitHub
parent d4f9e0b762
commit 1be638a970
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 767 additions and 139 deletions

View File

@ -1,3 +1,9 @@
## v3.0.5
* Возможность установки регулярных задач в `cron`, CLI-интерфейс для запуска задач вручную
* Теперь при отсутствии редактируемого заказа в retailCRM он будет создаваться (ранее попытка редактирования приводила к ошибке)
* Доработана механика выгрузки брошенных корзин для большего соответствия ожиданиям API PrestaShop
* Исправление ошибки округления скидки в заказе
## v3.0.4
* Проверка корректности генерации категорий в ICML

View File

@ -1 +1 @@
3.0.4
3.0.5

54
retailcrm/cli.php Normal file
View File

@ -0,0 +1,54 @@
<?php
/**
* MIT License
*
* Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* 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 http://www.prestashop.com for more information.
*
* @author DIGITAL RETAIL TECHNOLOGIES SL <mail@simlachat.com>
* @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';
}

View File

@ -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);
}

View File

@ -0,0 +1,233 @@
<?php
/**
* MIT License
*
* Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* 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 http://www.prestashop.com for more information.
*
* @author DIGITAL RETAIL TECHNOLOGIES SL <mail@simlachat.com>
* @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 <mail@simlachat.com>
* @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 <job name> - Runs provided job', $this->cliPath));
RetailcrmLogger::output(sprintf('> php %s --job <job name> - 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;
}
}

View File

@ -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();
}
/**

View File

@ -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';
}
}

View File

@ -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;

View File

@ -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();

View File

@ -0,0 +1,92 @@
<?php
/**
* MIT License
*
* Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* 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 http://www.prestashop.com for more information.
*
* @author DIGITAL RETAIL TECHNOLOGIES SL <mail@simlachat.com>
* @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());
}
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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();

View File

@ -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...'
);
}

View File

@ -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();

View File

@ -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...'
);
}

View File

@ -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
*