mirror of
https://github.com/retailcrm/prestashop-module.git
synced 2025-03-01 19:03:14 +03:00
470 lines
15 KiB
PHP
470 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2021 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 2021 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
|
|
*
|
|
* @see 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 = 'hj:s:';
|
|
$longopts = [
|
|
'help',
|
|
'job:',
|
|
'shop:',
|
|
'query-shops',
|
|
'set-web-jobs:',
|
|
'query-web-jobs',
|
|
'run-jobs',
|
|
'reset-job-manager',
|
|
'reset-all',
|
|
];
|
|
|
|
$options = getopt($shortopts, $longopts);
|
|
$jobName = isset($options['j']) ? $options['j'] : (isset($options['job']) ? $options['job'] : null);
|
|
$shopId = isset($options['s']) ? $options['s'] : (isset($options['shop']) ? $options['shop'] : null);
|
|
|
|
if (isset($options['reset-job-manager'])) {
|
|
$this->resetJobManager();
|
|
} elseif (isset($options['reset-all'])) {
|
|
$this->resetAll();
|
|
} elseif (isset($options['query-web-jobs'])) {
|
|
$this->queryWebJobs($shopId);
|
|
} elseif (isset($options['run-jobs'])) {
|
|
RetailcrmTools::startJobManager();
|
|
} elseif (isset($options['set-web-jobs'])) {
|
|
$this->setWebJobs(self::getBool($options['set-web-jobs']), $shopId);
|
|
} elseif (isset($options['query-shops'])) {
|
|
$this->queryShops();
|
|
} elseif (!empty($jobName)) {
|
|
$this->setCleanupOnShutdown();
|
|
$this->runJob($jobName, $shopId);
|
|
} elseif (isset($options['help']) || isset($options['h'])) {
|
|
$this->printHelp();
|
|
} else {
|
|
$this->printArgumentError('Unknown argument!');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shutdown handler. Moved here in order to keep compatibility with older PHP versions.
|
|
*
|
|
* @param mixed $error
|
|
*/
|
|
public function cleanupOnShutdown($error)
|
|
{
|
|
if (null !== $error) {
|
|
self::clearCurrentJob(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This will register shutdown handler which will clean lock before shutdown
|
|
*/
|
|
private function setCleanupOnShutdown()
|
|
{
|
|
RetailcrmJobManager::setCustomShutdownHandler([$this, 'cleanupOnShutdown']);
|
|
}
|
|
|
|
/**
|
|
* Runs provided job
|
|
*
|
|
* @param string $jobName
|
|
*/
|
|
private function runJob($jobName, $shopId)
|
|
{
|
|
try {
|
|
$result = RetailcrmJobManager::runJob($jobName, true, false, $shopId);
|
|
RetailcrmLogger::output(sprintf(
|
|
'Job %s was executed, result: %s',
|
|
$jobName,
|
|
$result ? 'true' : 'false'
|
|
));
|
|
} catch (Exception $exception) {
|
|
$this->handleException($jobName, $exception);
|
|
} catch (Error $exception) {
|
|
$this->handleException($jobName, $exception);
|
|
}
|
|
|
|
if (isset($result) && $result) {
|
|
self::clearCurrentJob($jobName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints error details
|
|
*
|
|
* @param Exception|Error $exception
|
|
* @param string $header
|
|
*/
|
|
private function printStack($exception, $header = 'Error while executing a job: ')
|
|
{
|
|
RetailcrmLogger::output(sprintf('%s%s', $header, $exception->getMessage()));
|
|
RetailcrmLogger::output(sprintf('%s:%d', $exception->getFile(), $exception->getLine()));
|
|
RetailcrmLogger::output();
|
|
RetailcrmLogger::output($exception->getTraceAsString());
|
|
}
|
|
|
|
/**
|
|
* Prints CLI help
|
|
*/
|
|
private function printHelp()
|
|
{
|
|
RetailcrmLogger::output('Available jobs:');
|
|
RetailcrmLogger::output();
|
|
|
|
foreach ($this->getAllowedJobs() 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 --run-jobs - Run default jobs routine', $this->cliPath));
|
|
RetailcrmLogger::output(sprintf('> php %s --set-web-jobs true / false - Enable or disable web jobs', $this->cliPath));
|
|
RetailcrmLogger::output(sprintf('> php %s --query-web-jobs - Check web jobs status', $this->cliPath));
|
|
RetailcrmLogger::output(sprintf('> php %s --query-shops - Get list of shops with ids (for MultiShop)', $this->cliPath));
|
|
RetailcrmLogger::output(sprintf('> php %s -h - Shows this page', $this->cliPath));
|
|
RetailcrmLogger::output(sprintf('> php %s --help - Shows this page', $this->cliPath));
|
|
RetailcrmLogger::output();
|
|
RetailcrmLogger::output(
|
|
'NOTICE: If you have MultiShop feature enabled, you can additionally ' .
|
|
'specify shop id when manually running job: '
|
|
);
|
|
RetailcrmLogger::output('At default jobs are running for all active shops alternately.');
|
|
RetailcrmLogger::output();
|
|
RetailcrmLogger::output(sprintf('> php %s -j <job name> -s <shop id> - Runs provided job for specified shop', $this->cliPath));
|
|
RetailcrmLogger::output(sprintf('> php %s --job <job name> --shop <shop id> - Runs provided job for specified shop', $this->cliPath));
|
|
RetailcrmLogger::output();
|
|
RetailcrmLogger::output(
|
|
'WARNING: Commands below are dangerous and should be used only when ' .
|
|
"job manager or cli doesn't work properly."
|
|
);
|
|
RetailcrmLogger::output('Use them at your own risk.');
|
|
RetailcrmLogger::output();
|
|
RetailcrmLogger::output(sprintf(
|
|
'> php %s --reset-job-manager - Will reset job manager internal timers & current job name',
|
|
$this->cliPath
|
|
));
|
|
RetailcrmLogger::output(sprintf(
|
|
'> php %s --reset-all - Will reset the entire job subsystem state, can resolve most problems',
|
|
$this->cliPath
|
|
));
|
|
RetailcrmLogger::output();
|
|
}
|
|
|
|
private function printArgumentError($message = '')
|
|
{
|
|
if (!empty($message)) {
|
|
RetailcrmLogger::output($message);
|
|
}
|
|
|
|
RetailcrmLogger::output('Use -h or --help to get more info about CLI');
|
|
}
|
|
|
|
/**
|
|
* Sets new web jobs state
|
|
*
|
|
* @param bool $state
|
|
* @param $shopId
|
|
*/
|
|
private function setWebJobs($state, $shopId = null)
|
|
{
|
|
$shopId = $this->setShopId($shopId);
|
|
|
|
if (null === $shopId) {
|
|
return;
|
|
}
|
|
|
|
$this->loadConfiguration();
|
|
|
|
Configuration::updateValue(RetailCRM::ENABLE_WEB_JOBS, $state ? '1' : '0');
|
|
RetailcrmLogger::output('Updated web jobs state.');
|
|
$this->queryWebJobs($shopId);
|
|
}
|
|
|
|
/**
|
|
* Prints web jobs status
|
|
*
|
|
* @param $shopId
|
|
*/
|
|
private function queryWebJobs($shopId = null)
|
|
{
|
|
$shopId = $this->setShopId($shopId);
|
|
|
|
if (null === $shopId) {
|
|
return;
|
|
}
|
|
|
|
$this->loadConfiguration();
|
|
|
|
RetailcrmLogger::output(sprintf(
|
|
'Web jobs status: %s',
|
|
RetailcrmTools::isWebJobsEnabled() ? 'true (enabled)' : 'false (disabled)'
|
|
));
|
|
}
|
|
|
|
private function queryShops()
|
|
{
|
|
$isFeatureActive = Shop::isFeatureActive();
|
|
|
|
RetailcrmLogger::output(sprintf(
|
|
'Multistore status: %s',
|
|
$isFeatureActive ? 'true (enabled)' : 'false (disabled)'
|
|
));
|
|
|
|
$shops = RetailcrmContextSwitcher::getShops();
|
|
RetailcrmLogger::output(
|
|
"\nShop ID\t| Shop Name"
|
|
);
|
|
|
|
foreach ($shops as $shop) {
|
|
RetailcrmLogger::output(sprintf(
|
|
"%s\t- %s",
|
|
$shop['id_shop'],
|
|
$shop['name']
|
|
));
|
|
}
|
|
}
|
|
|
|
private function setShopId($shopId)
|
|
{
|
|
if (null === $shopId) {
|
|
$shopId = Shop::getContextShopID();
|
|
}
|
|
|
|
if (null === $shopId) {
|
|
$this->printArgumentError('You must specify shop id');
|
|
} else {
|
|
RetailcrmContextSwitcher::setShopContext($shopId);
|
|
}
|
|
|
|
return $shopId;
|
|
}
|
|
|
|
/**
|
|
* Load PrestaShop configuration if it's not loaded yet
|
|
*/
|
|
private function loadConfiguration()
|
|
{
|
|
if (!Configuration::configurationIsLoaded()) {
|
|
Configuration::loadConfiguration();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
} catch (Error $exception) {
|
|
$this->printStack($exception);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets JobManager and cli internal lock
|
|
*/
|
|
private function resetAll()
|
|
{
|
|
$this->resetJobManager();
|
|
self::clearCurrentJob(null);
|
|
RetailcrmLogger::output('CLI command lock was cleared.');
|
|
}
|
|
|
|
/**
|
|
* 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::updateGlobalValue(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::getGlobalValue(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 (null === $job || self::getCurrentJob() == $job) {
|
|
return Configuration::deleteByName(self::CURRENT_TASK_CLI);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Converts string param from CLI into boolean
|
|
*
|
|
* @param string $param
|
|
*/
|
|
private static function getBool($param)
|
|
{
|
|
if ('true' == $param) {
|
|
return true;
|
|
}
|
|
|
|
if ('false' == $param) {
|
|
return false;
|
|
}
|
|
|
|
return (bool) $param;
|
|
}
|
|
|
|
/**
|
|
* Returns list of jobs which are allowed to be executed via cli
|
|
*
|
|
* @return string[]
|
|
*/
|
|
private function getAllowedJobs()
|
|
{
|
|
return [
|
|
'RetailcrmAbandonedCartsEvent',
|
|
'RetailcrmIcmlEvent',
|
|
'RetailcrmIcmlUpdateUrlEvent',
|
|
'RetailcrmSyncEvent',
|
|
'RetailcrmInventoriesEvent',
|
|
'RetailcrmExportEvent',
|
|
'RetailcrmUpdateSinceIdEvent',
|
|
'RetailcrmClearLogsEvent',
|
|
];
|
|
}
|
|
|
|
private function handleException($jobName, $exception)
|
|
{
|
|
if ($exception instanceof RetailcrmJobManagerException
|
|
&& (
|
|
$exception->getPrevious() instanceof Exception
|
|
|| $exception->getPrevious() instanceof Error
|
|
)
|
|
) {
|
|
$this->printStack($exception->getPrevious());
|
|
} else {
|
|
$this->printStack($exception);
|
|
}
|
|
|
|
self::clearCurrentJob($jobName);
|
|
}
|
|
}
|