mirror of
https://github.com/retailcrm/prestashop-module.git
synced 2025-03-01 19:03:14 +03:00
879 lines
26 KiB
PHP
879 lines
26 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 RetailcrmJobManager
|
|
*
|
|
* @author DIGITAL RETAIL TECHNOLOGIES SL <mail@simlachat.com>
|
|
* @license GPL
|
|
*
|
|
* @see https://retailcrm.ru
|
|
*/
|
|
class RetailcrmJobManager
|
|
{
|
|
const LAST_RUN_NAME = 'RETAILCRM_LAST_RUN';
|
|
const LAST_RUN_DETAIL_NAME = 'RETAILCRM_LAST_RUN_DETAIL';
|
|
const IN_PROGRESS_NAME = 'RETAILCRM_JOBS_IN_PROGRESS';
|
|
const CURRENT_TASK = 'RETAILCRM_JOB_CURRENT';
|
|
|
|
/** @var callable|null */
|
|
private static $customShutdownHandler;
|
|
|
|
/** @var bool */
|
|
private static $shutdownHandlerRegistered;
|
|
|
|
/**
|
|
* Entry point for all jobs.
|
|
* Jobs must be passed in this format:
|
|
* RetailcrmJobManager::startJobs(
|
|
* array(
|
|
* 'jobName' => DateInterval::createFromDateString('1 hour')
|
|
* )
|
|
* );
|
|
*
|
|
* File `jobName.php` must exist in retailcrm/job and must contain everything to run job.
|
|
* Throwed errors will be logged in <prestashop directory>/retailcrm.log
|
|
* DateInterval must be positive. Pass `null` instead of DateInterval to remove
|
|
* any delay - in other words, jobs without interval will be executed every time.
|
|
*
|
|
* @param array $jobs Jobs list
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public static function startJobs(
|
|
$jobs = []
|
|
) {
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'starting JobManager');
|
|
static::execJobs($jobs);
|
|
}
|
|
|
|
/**
|
|
* Run scheduled jobs with request
|
|
*
|
|
* @param array $jobs
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public static function execJobs($jobs = [])
|
|
{
|
|
$current = date_create_immutable('now');
|
|
$lastRuns = [];
|
|
$lastRunsDetails = [];
|
|
|
|
try {
|
|
$lastRuns = static::getLastRuns();
|
|
$lastRunsDetails = static::getLastRunDetails();
|
|
} catch (Exception $exception) {
|
|
static::handleError(
|
|
$exception->getFile(),
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString(),
|
|
'',
|
|
$jobs
|
|
);
|
|
|
|
return;
|
|
} catch (Error $exception) {
|
|
static::handleError(
|
|
$exception->getFile(),
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString(),
|
|
'',
|
|
$jobs
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'Trying to acquire lock...');
|
|
|
|
if (!static::lock()) {
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'Cannot acquire lock');
|
|
die;
|
|
}
|
|
|
|
RetailcrmLogger::writeDebug(
|
|
__METHOD__,
|
|
sprintf('Current time: %s', $current->format(DATE_RFC3339))
|
|
);
|
|
|
|
foreach ($lastRuns as $name => $diff) {
|
|
if (!array_key_exists($name, $jobs)) {
|
|
unset($lastRuns[$name]);
|
|
}
|
|
}
|
|
|
|
uasort($jobs, function ($diff1, $diff2) {
|
|
$date1 = new \DateTimeImmutable();
|
|
$date2 = new \DateTimeImmutable();
|
|
|
|
if (null !== $diff1) {
|
|
$date1 = $date1->add($diff1);
|
|
}
|
|
if (null !== $diff2) {
|
|
$date2 = $date2->add($diff2);
|
|
}
|
|
|
|
if ($date1 == $date2) {
|
|
return 0;
|
|
}
|
|
|
|
return ($date1 > $date2) ? -1 : 1;
|
|
});
|
|
|
|
foreach ($jobs as $job => $diff) {
|
|
$exception = null;
|
|
|
|
try {
|
|
if (isset($lastRuns[$job]) && $lastRuns[$job] instanceof DateTimeImmutable) {
|
|
$shouldRunAt = clone $lastRuns[$job];
|
|
|
|
if ($diff instanceof DateInterval) {
|
|
$shouldRunAt = $shouldRunAt->add($diff);
|
|
}
|
|
} else {
|
|
$shouldRunAt = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00');
|
|
}
|
|
|
|
RetailcrmLogger::writeDebug(__METHOD__, sprintf(
|
|
'Checking %s, interval %s, shouldRunAt: %s: %s',
|
|
$job,
|
|
null === $diff ? 'NULL' : $diff->format('%R%Y-%m-%d %H:%i:%s:%F'),
|
|
isset($shouldRunAt) && $shouldRunAt instanceof \DateTimeImmutable
|
|
? $shouldRunAt->format(DATE_RFC3339)
|
|
: 'undefined',
|
|
(isset($shouldRunAt) && $shouldRunAt <= $current) ? 'true' : 'false'
|
|
));
|
|
|
|
if (isset($shouldRunAt) && $shouldRunAt <= $current) {
|
|
RetailcrmLogger::writeDebug(__METHOD__, sprintf('Executing job %s', $job));
|
|
$result = RetailcrmJobManager::runJob($job);
|
|
RetailcrmLogger::writeDebug(
|
|
__METHOD__,
|
|
sprintf('Executed job %s, result: %s', $job, $result ? 'true' : 'false')
|
|
);
|
|
$lastRuns[$job] = new \DateTimeImmutable('now');
|
|
|
|
if ($result) {
|
|
$lastRunsDetails[$job] = [
|
|
'success' => true,
|
|
'lastRun' => new \DateTimeImmutable('now'),
|
|
'error' => null,
|
|
];
|
|
|
|
self::clearCurrentJob($job);
|
|
}
|
|
|
|
break;
|
|
}
|
|
} catch (Exception $e) {
|
|
$exception = $e;
|
|
} catch (Error $e) {
|
|
$exception = $e;
|
|
}
|
|
|
|
if (null !== $exception) {
|
|
if ($exception instanceof RetailcrmJobManagerException
|
|
&& $exception->getPrevious() instanceof \Exception
|
|
) {
|
|
$exception = $exception->getPrevious();
|
|
}
|
|
|
|
$lastRunsDetails[$job] = [
|
|
'success' => false,
|
|
'lastRun' => new \DateTimeImmutable('now'),
|
|
'error' => [
|
|
'message' => $exception->getMessage(),
|
|
'trace' => $exception->getTraceAsString(),
|
|
],
|
|
];
|
|
|
|
static::handleError(
|
|
$exception->getFile(),
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString(),
|
|
$job
|
|
);
|
|
|
|
self::clearCurrentJob($job);
|
|
}
|
|
}
|
|
|
|
try {
|
|
static::setLastRuns($lastRuns);
|
|
static::setLastRunDetails($lastRunsDetails);
|
|
} catch (Exception $exception) {
|
|
static::handleError(
|
|
$exception->getFile(),
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString(),
|
|
'',
|
|
$jobs
|
|
);
|
|
} catch (Error $exception) {
|
|
static::handleError(
|
|
$exception->getFile(),
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString(),
|
|
'',
|
|
$jobs
|
|
);
|
|
}
|
|
|
|
static::unlock();
|
|
}
|
|
|
|
/**
|
|
* Run job in the force mode so it will run even if there's another job running
|
|
*
|
|
* @param $jobName
|
|
*
|
|
* @return bool
|
|
*
|
|
* @throws Exception
|
|
* @throws Error
|
|
*/
|
|
public static function execManualJob($jobName)
|
|
{
|
|
try {
|
|
$result = static::runJob($jobName, false, true, Shop::getContextShopID());
|
|
|
|
if ($result) {
|
|
static::updateLastRunDetail($jobName, [
|
|
'success' => true,
|
|
'lastRun' => new \DateTimeImmutable('now'),
|
|
'error' => null,
|
|
]);
|
|
}
|
|
|
|
return $result;
|
|
} catch (Exception $exception) {
|
|
self::handleManualRunError($jobName, $exception);
|
|
} catch (Error $exception) {
|
|
self::handleManualRunError($jobName, $exception);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Extracts jobs last runs from db
|
|
*
|
|
* @return array<string, \DateTime>
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
private static function getLastRuns()
|
|
{
|
|
$lastRuns = json_decode((string) Configuration::getGlobalValue(self::LAST_RUN_NAME), true);
|
|
|
|
if (JSON_ERROR_NONE != json_last_error() || !is_array($lastRuns)) {
|
|
$lastRuns = [];
|
|
} else {
|
|
foreach ($lastRuns as $job => $ran) {
|
|
$lastRan = DateTimeImmutable::createFromFormat(DATE_RFC3339, $ran);
|
|
|
|
if ($lastRan instanceof DateTimeImmutable) {
|
|
$lastRuns[$job] = $lastRan;
|
|
} else {
|
|
$lastRuns[$job] = new DateTimeImmutable();
|
|
}
|
|
}
|
|
}
|
|
|
|
return (array) $lastRuns;
|
|
}
|
|
|
|
/**
|
|
* Updates jobs last runs in db
|
|
*
|
|
* @param array $lastRuns
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
private static function setLastRuns($lastRuns = [])
|
|
{
|
|
$now = new DateTimeImmutable();
|
|
|
|
if (!is_array($lastRuns)) {
|
|
$lastRuns = [];
|
|
}
|
|
|
|
foreach ($lastRuns as $job => $ran) {
|
|
if ($ran instanceof DateTimeImmutable) {
|
|
$lastRuns[$job] = $ran->format(DATE_RFC3339);
|
|
} else {
|
|
$lastRuns[$job] = $now->format(DATE_RFC3339);
|
|
}
|
|
|
|
RetailcrmLogger::writeDebug(
|
|
__METHOD__,
|
|
sprintf('Saving last run for %s as %s', $job, $lastRuns[$job])
|
|
);
|
|
}
|
|
|
|
Configuration::updateGlobalValue(self::LAST_RUN_NAME, (string) json_encode($lastRuns));
|
|
}
|
|
|
|
/**
|
|
* @param string $jobName
|
|
* @param Datetime|null $data
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public static function updateLastRun($jobName, $data)
|
|
{
|
|
$lastRuns = static::getLastRuns();
|
|
$lastRuns[$jobName] = $data;
|
|
static::setLastRuns($lastRuns);
|
|
}
|
|
|
|
/**
|
|
* Extracts jobs last runs from db
|
|
*
|
|
* @param bool $withFormattedDate
|
|
*
|
|
* @return array<string, array>
|
|
*/
|
|
public static function getLastRunDetails($withFormattedDate = false)
|
|
{
|
|
$lastRuns = json_decode((string) Configuration::getGlobalValue(self::LAST_RUN_DETAIL_NAME), true);
|
|
|
|
if (JSON_ERROR_NONE != json_last_error() || !is_array($lastRuns)) {
|
|
$lastRuns = [];
|
|
} else {
|
|
foreach ($lastRuns as $job => $details) {
|
|
$lastRan = DateTimeImmutable::createFromFormat(DATE_RFC3339, $details['lastRun']);
|
|
|
|
if ($lastRan instanceof DateTimeImmutable) {
|
|
$lastRuns[$job]['lastRun'] = $withFormattedDate ? $lastRan->format('Y-m-d H:i:s') : $lastRan;
|
|
} else {
|
|
$lastRuns[$job]['lastRun'] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (array) $lastRuns;
|
|
}
|
|
|
|
/**
|
|
* Updates jobs last runs in db
|
|
*
|
|
* @param array $lastRuns
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
private static function setLastRunDetails($lastRuns = [])
|
|
{
|
|
if (!is_array($lastRuns)) {
|
|
$lastRuns = [];
|
|
}
|
|
|
|
foreach ($lastRuns as $job => $details) {
|
|
if (isset($details['lastRun']) && $details['lastRun'] instanceof DateTimeImmutable) {
|
|
$lastRuns[$job]['lastRun'] = $details['lastRun']->format(DATE_RFC3339);
|
|
} else {
|
|
$lastRuns[$job]['lastRun'] = null;
|
|
}
|
|
}
|
|
|
|
RetailcrmLogger::writeDebug(__METHOD__, json_encode($lastRuns));
|
|
Configuration::updateGlobalValue(self::LAST_RUN_DETAIL_NAME, (string) json_encode($lastRuns));
|
|
}
|
|
|
|
/**
|
|
* @param string $jobName
|
|
* @param array $data
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public static function updateLastRunDetail($jobName, $data)
|
|
{
|
|
$lastRunsDetails = static::getLastRunDetails();
|
|
$lastRunsDetails[$jobName] = $data;
|
|
static::setLastRunDetails($lastRunsDetails);
|
|
}
|
|
|
|
/**
|
|
* Runs job
|
|
*
|
|
* @param string $job
|
|
* @param bool $once
|
|
* @param bool $cliMode
|
|
* @param bool $force
|
|
* @param int $shopId
|
|
*
|
|
* @return bool
|
|
*
|
|
* @throws \RetailcrmJobManagerException
|
|
*/
|
|
public static function runJob($job, $cliMode = false, $force = false, $shopId = null)
|
|
{
|
|
$jobName = self::escapeJobName($job);
|
|
|
|
try {
|
|
return static::execHere($jobName, $cliMode, $force, $shopId);
|
|
} catch (\RetailcrmJobManagerException $exception) {
|
|
throw $exception;
|
|
} catch (Exception $exception) {
|
|
throw new RetailcrmJobManagerException($exception->getMessage(), $job, [], 0, $exception);
|
|
} catch (Error $exception) {
|
|
throw new RetailcrmJobManagerException($exception->getMessage(), $job, [], 0, $exception);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Serializes jobs to JSON
|
|
*
|
|
* @param $jobs
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function serializeJobs($jobs)
|
|
{
|
|
foreach ($jobs as $name => $interval) {
|
|
$jobs[$name] = serialize($interval);
|
|
}
|
|
|
|
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::updateGlobalValue(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::getGlobalValue(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 (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 = Configuration::deleteByName(self::CURRENT_TASK);
|
|
$result = $result && Configuration::deleteByName(self::LAST_RUN_NAME);
|
|
|
|
self::unlock();
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Sets custom shutdown handler, it will be called before calling default shutdown handler.
|
|
*
|
|
* @param callable $shutdownHandler
|
|
*/
|
|
public static function setCustomShutdownHandler($shutdownHandler)
|
|
{
|
|
if (is_callable($shutdownHandler)) {
|
|
self::$customShutdownHandler = $shutdownHandler;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper for shutdown handler. Moved here in order to keep compatibility with older PHP versions.
|
|
*/
|
|
public static function shutdownHandlerWrapper()
|
|
{
|
|
$error = error_get_last();
|
|
|
|
if (null !== $error && E_ERROR === $error['type']) {
|
|
self::defaultShutdownHandler($error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register default shutdown handler (should be be called before any job execution)
|
|
*/
|
|
private static function registerShutdownHandler()
|
|
{
|
|
if (!self::$shutdownHandlerRegistered) {
|
|
register_shutdown_function(['RetailcrmJobManager', 'shutdownHandlerWrapper']);
|
|
self::$shutdownHandlerRegistered = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default handler for shutdown function
|
|
*
|
|
* @param array $error
|
|
*/
|
|
private static function defaultShutdownHandler($error)
|
|
{
|
|
if (is_callable(self::$customShutdownHandler)) {
|
|
call_user_func_array(self::$customShutdownHandler, [$error]);
|
|
} else {
|
|
if (null !== $error) {
|
|
$job = self::getCurrentJob();
|
|
if (!empty($job)) {
|
|
$lastRunsDetails = self::getLastRunDetails();
|
|
|
|
$lastRunsDetails[$job] = [
|
|
'success' => false,
|
|
'lastRun' => new \DateTimeImmutable('now'),
|
|
'error' => [
|
|
'message' => (isset($error['message']) ? $error['message'] : print_r($error, true)),
|
|
'trace' => print_r($error, true),
|
|
],
|
|
];
|
|
try {
|
|
self::setLastRunDetails($lastRunsDetails);
|
|
} catch (Exception $exception) {
|
|
static::handleError(
|
|
$exception->getFile(),
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString(),
|
|
$job
|
|
);
|
|
} catch (Error $exception) {
|
|
static::handleError(
|
|
$exception->getFile(),
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString(),
|
|
$job
|
|
);
|
|
}
|
|
}
|
|
|
|
self::clearCurrentJob(null);
|
|
}
|
|
}
|
|
|
|
RetailcrmLogger::writeCaller(
|
|
__METHOD__,
|
|
'Warning: something disrupted correct process execution. All information will be provided here.'
|
|
);
|
|
RetailcrmLogger::writeCaller(__METHOD__, print_r($error, true));
|
|
self::unlock();
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* Writes error to log and returns 500
|
|
*
|
|
* @param string $file
|
|
* @param string $msg
|
|
* @param string $trace
|
|
* @param string $currentJob
|
|
* @param array $jobs
|
|
*/
|
|
private static function handleError($file, $msg, $trace, $currentJob = '', $jobs = [])
|
|
{
|
|
$data = [];
|
|
|
|
if (!empty($currentJob)) {
|
|
$data[] = 'current job: ' . $currentJob;
|
|
}
|
|
|
|
if (0 < count($jobs)) {
|
|
$data[] = 'jobs list: ' . self::serializeJobs($jobs);
|
|
}
|
|
|
|
RetailcrmLogger::writeNoCaller(sprintf('%s: %s (%s)', $file, $msg, implode(', ', $data)));
|
|
RetailcrmLogger::writeNoCaller($trace);
|
|
|
|
if (PHP_SAPI != 'cli' && !headers_sent()) {
|
|
RetailcrmTools::http_response_code(500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @param bool $force
|
|
* @param int $shopId
|
|
*
|
|
* @return bool
|
|
*
|
|
* @throws \RetailcrmJobManagerException
|
|
*/
|
|
private static function execHere($jobName, $cliMode = false, $force = false, $shopId = null)
|
|
{
|
|
set_time_limit(static::getTimeLimit());
|
|
|
|
if (!$cliMode && !$force) {
|
|
ignore_user_abort(true);
|
|
|
|
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 (!class_exists($jobName)) {
|
|
throw new \RetailcrmJobManagerException(sprintf(
|
|
'The job class "%s" was not found.',
|
|
$jobName
|
|
));
|
|
}
|
|
|
|
$job = new $jobName();
|
|
|
|
if (!($job instanceof RetailcrmEventInterface)) {
|
|
throw new \RetailcrmJobManagerException(sprintf(
|
|
'Class "%s" must implement RetailcrmEventInterface',
|
|
$jobName
|
|
));
|
|
}
|
|
|
|
$job->setCliMode($cliMode);
|
|
$job->setForce($force);
|
|
$job->setShopId($shopId);
|
|
|
|
self::registerShutdownHandler();
|
|
|
|
return $job->execute();
|
|
}
|
|
|
|
/**
|
|
* Returns script execution time limit
|
|
*
|
|
* @return int
|
|
*/
|
|
private static function getTimeLimit()
|
|
{
|
|
return 14400;
|
|
}
|
|
|
|
/**
|
|
* Removes disallowed symbols from job name. Only latin characters, numbers and underscore allowed.
|
|
*
|
|
* @param string $job
|
|
*
|
|
* @return string
|
|
*/
|
|
private static function escapeJobName($job)
|
|
{
|
|
return (string) preg_replace('/[^[a-zA-Z0-9_]]*/m', '', $job);
|
|
}
|
|
|
|
/**
|
|
* Returns when JobManager was executed
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
private static function getLastRun()
|
|
{
|
|
$lastRuns = array_values(static::getLastRuns());
|
|
|
|
if (empty($lastRuns)) {
|
|
return \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00');
|
|
}
|
|
|
|
usort(
|
|
$lastRuns,
|
|
function ($first, $second) {
|
|
if ($first < $second) {
|
|
return 1;
|
|
} elseif ($first > $second) {
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
);
|
|
|
|
return $lastRuns[count($lastRuns) - 1];
|
|
}
|
|
|
|
/**
|
|
* Returns true if lock is present and it's not expired
|
|
*
|
|
* @return bool
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
private static function isLocked()
|
|
{
|
|
$inProcess = (bool) Configuration::getGlobalValue(self::IN_PROGRESS_NAME);
|
|
$lastRan = static::getLastRun();
|
|
$lastRanSeconds = $lastRan->format('U');
|
|
|
|
if ($inProcess && ($lastRanSeconds + self::getTimeLimit()) < time()) {
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'Removing lock because time limit exceeded.');
|
|
static::unlock();
|
|
|
|
return false;
|
|
}
|
|
|
|
return $inProcess;
|
|
}
|
|
|
|
/**
|
|
* Installs lock
|
|
*
|
|
* @return bool
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
private static function lock()
|
|
{
|
|
if (!static::isLocked()) {
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'Acquiring lock...');
|
|
Configuration::updateGlobalValue(self::IN_PROGRESS_NAME, true);
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'Lock acquired.');
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Removes lock
|
|
*
|
|
* @return bool
|
|
*/
|
|
private static function unlock()
|
|
{
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'Removing lock...');
|
|
Configuration::updateGlobalValue(self::IN_PROGRESS_NAME, false);
|
|
RetailcrmLogger::writeDebug(__METHOD__, 'Lock removed.');
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $jobName
|
|
* @param Exception|Error $exception
|
|
*
|
|
* @throws Exception|Error
|
|
*/
|
|
private static function handleManualRunError($jobName, $exception)
|
|
{
|
|
if ($exception instanceof RetailcrmJobManagerException
|
|
&& (
|
|
$exception->getPrevious() instanceof Exception
|
|
|| $exception->getPrevious() instanceof Error
|
|
)
|
|
) {
|
|
$exception = $exception->getPrevious();
|
|
}
|
|
|
|
RetailcrmLogger::printException($exception, '', false);
|
|
self::updateLastRunDetail($jobName, [
|
|
'success' => false,
|
|
'lastRun' => new \DateTimeImmutable('now'),
|
|
'error' => [
|
|
'message' => $exception->getMessage(),
|
|
'trace' => $exception->getTraceAsString(),
|
|
],
|
|
]);
|
|
|
|
throw $exception;
|
|
}
|
|
|
|
/**
|
|
* Returns list of jobs which are allowed to be executed via admin panel
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public static function getAllowedJobs()
|
|
{
|
|
return [
|
|
'RetailcrmAbandonedCartsEvent',
|
|
'RetailcrmIcmlEvent',
|
|
'RetailcrmIcmlUpdateUrlEvent',
|
|
'RetailcrmSyncEvent',
|
|
'RetailcrmInventoriesEvent',
|
|
'RetailcrmUpdateSinceIdEvent',
|
|
'RetailcrmClearLogsEvent',
|
|
];
|
|
}
|
|
}
|