• Модуль: tasks
  • Путь к файлу: ~/bitrix/modules/tasks/lib/util/replicator/task/fromtemplate.php
  • Класс: BitrixTasksUtilReplicatorTaskFromTemplate
  • Вызов: FromTemplate::repeatTask
static function repeatTask($templateId, array $parameters = [])
{
	$forceExecute = false;
	if (array_key_exists('FORCE_EXECUTE', $parameters))
	{
		$forceExecute = $parameters['FORCE_EXECUTE'];
	}

	$templateId = (int)$templateId;
	if (!$templateId)
	{
		return ''; // delete agent
	}

	$userId = static::getEffectiveUser();

	static::liftLogAgent();

	// todo: replace this with itemorm call
	$templateDbRes = CTaskTemplates::getList(
		[],
		['ID' => $templateId],
		false,
		['USER_IS_ADMIN' => true],
		['*', 'UF_*']
	);
	$template = $templateDbRes->Fetch();
	if ($template && $template['REPLICATE'] === 'Y')
	{
		$agentName = str_replace('#ID#', $templateId, $parameters['AGENT_NAME_TEMPLATE']); // todo: when AGENT_NAME_TEMPLATE is not set?
		$replicateParams = $template['REPLICATE_PARAMS'] = unserialize($template['REPLICATE_PARAMS'], ['allowed_classes' => false]);

		$executionTime = static::getExecutionTime($agentName, $replicateParams);
		if (
			$executionTime
			&&
			(
				static::taskByTemplateAlreadyExist($templateId, $executionTime)
				|| time() < MakeTimeStamp($executionTime)
			)
			&& !$forceExecute
		)
		{
			return $agentName;
		}

		$result = new UtilReplicatorResult();
		if (is_array($parameters['RESULT']))
		{
			$parameters['RESULT']['RESULT'] = $result;
		}

		$createMessage = '';
		$taskId = 0;
		$resumeReplication = true;
		$replicationCancelReason = '';

		if (!$userId)
		{
			$createMessage = Loc::getMessage('TASKS_REPLICATOR_TASK_WAS_NOT_CREATED');
			$result->addError('REPLICATION_FAILED', Loc::getMessage('TASKS_REPLICATOR_CANT_IDENTIFY_USER'));
		}

		// check if CREATOR is alive
		if (!User::isActive($template['CREATED_BY']))
		{
			$resumeReplication = false; // no need to make another try
			$createMessage = Loc::getMessage('TASKS_REPLICATOR_TASK_WAS_NOT_CREATED');
			$result->addError('REPLICATION_FAILED', Loc::getMessage('TASKS_REPLICATOR_CREATOR_INACTIVE'));
		}

		// create task if no error occured
		if ($result->isSuccess())
		{
			// todo: remove this spike
			$userChanged = false;
			$originalUser = null;

			if (intval($template['CREATED_BY']))
			{
				if (array_key_exists('FORCE_USER', $parameters))
				{
					$userId = (int) $parameters['FORCE_USER'];
				}
				else
				{
					$userId = (int) $template['CREATED_BY'];
				}
				$userChanged = true;
				$originalUser = User::getOccurAsId();
				User::setOccurAsId($userId); // not admin in logs, but template creator
			}

			try
			{
				/** @var BitrixTasksUtilReplicatorTask $replicator */
				$replicator = new static();
				$replicator->setConfig('DISABLE_SOURCE_ACCESS_CONTROLLER', true); // do not query rights and do not check it
				$produceResult = $replicator->produce($templateId, $userId, array('OVERRIDE_DATA' => array('CREATED_DATE' => $executionTime)));

				if ($produceResult->isSuccess())
				{
					static::incrementReplicationCount($templateId, $template['TPARAM_REPLICATION_COUNT']);

					$task = $produceResult->getInstance();
					$subInstanceResult = $produceResult->getSubInstanceResult();

					$result->setInstance($task);
					if (Collection::isA($subInstanceResult))
					{
						$result->setSubInstanceResult($produceResult->getSubInstanceResult());
					}

					$taskId = $task->getId();

					if ($produceResult->getErrors()->isEmpty())
					{
						$createMessage = Loc::getMessage('TASKS_REPLICATOR_TASK_CREATED');
					}
					else
					{
						$createMessage = Loc::getMessage('TASKS_REPLICATOR_TASK_CREATED_WITH_ERRORS');
					}

					if ($taskId)
					{
						$createMessage .= ' (#'.$taskId.')';
					}
				}
				else
				{
					$createMessage = Loc::getMessage('TASKS_REPLICATOR_TASK_WAS_NOT_CREATED');
				}

				$result->adoptErrors($produceResult);
			}
			catch(Exception $e) // catch EACH exception, as we dont want the agent to repeat every 10 minutes in case of smth is wrong
			{
				$createMessage = Loc::getMessage('TASKS_REPLICATOR_TASK_POSSIBLY_WAS_NOT_CREATED');
				if ($taskId)
				{
					$createMessage = Loc::getMessage('TASKS_REPLICATOR_TASK_CREATED_WITH_ERRORS').' (#'.$taskId.')';
				}

				$result->addException($e, Loc::getMessage('TASKS_REPLICATOR_INTERNAL_ERROR'));
			}

			// switch an original hit user back, unless we want some strange things to happen
			if ($userChanged)
			{
				User::setOccurAsId($originalUser);
			}
		}

		if ($createMessage !== '')
		{
			static::sendToSysLog($templateId, intval($taskId), $createMessage, $result->getErrors());
		}

		// calculate next execution time

		if ($resumeReplication)
		{
			$currentUserTimezone = User::getTimeZoneOffsetCurrentUser();
			$lastTime = $executionTime;
			$iterationCount = 0;

			do
			{
				$nextResult = static::getNextTime($template, $lastTime);
				$nextData = $nextResult->getData();
				$nextTime = $nextData['TIME'];

				// next time is legal, but goes before or equals to the current time
				if (($nextTime && MakeTimeStamp($lastTime) >= MakeTimeStamp($nextTime)) || ($iterationCount > 10000))
				{
					if ($iterationCount > 10000)
					{
						$message = 'insane iteration count reached while calculating next execution time';
					}
					else
					{
						$creator = $template['CREATED_BY'];
						$creatorTimezone =  User::getTimeZoneOffset($creator);

						$eDebug = array(
							$creator,
							time(),
							$currentUserTimezone,
							$creatorTimezone,
							$replicateParams['TIME'],
							$replicateParams['TIMEZONE_OFFSET'],
							$iterationCount
						);
						$message = 'getNextTime() loop detected for replication by template '.$templateId.' ('.$nextTime.' => '.$lastTime.') ('.implode(', ', $eDebug).')';
					}

					Util::log($message); // write to b24 exception log
					static::sendToSysLog($templateId, 0, Loc::getMessage('TASKS_REPLICATOR_PROCESS_ERROR'), null, true);

					$nextTime = false; // possible endless loop, this agent must be stopped
					break;
				}

				// $nextTime in current user`s time (or server time, if no user)

				$lastTime = $nextTime;
				// we can compare one user`s time only with another user`s time, we canna just take time() value
				$cTime = time() + $currentUserTimezone;

				$iterationCount++;
			}
			while (($nextResult->isSuccess() && $nextTime) && MakeTimeStamp($nextTime) < $cTime);

			if ($nextTime)
			{
				// we can not use CAgent::Update() here, kz the agent will be updated again just after this function ends ...
				global $pPERIOD;

				// still have $nextTime in current user timezone, we need server time now, so:
				$nextTime = MakeTimeStamp($nextTime) - $currentUserTimezone;
				$nextTimeFormatted = UI::formatDateTime($nextTime);

				$replicateParams['NEXT_EXECUTION_TIME'] = $nextTimeFormatted;

				$template = new CTaskTemplates();
				$template->Update(
					$templateId,
					array('REPLICATE_PARAMS' => serialize($replicateParams)),
					array('SKIP_AGENT_PROCESSING' => true)
				);

				// ... but we may set some global var called $pPERIOD
				// "why ' - time()'?" you may ask. see CAgent::ExecuteAgents(), in the last sql we got:
				// NEXT_EXEC=DATE_ADD(".($arAgent["IS_PERIOD"]=="Y"? "NEXT_EXEC" : "now()").", INTERVAL ".$pPERIOD." SECOND),
				$pPERIOD = $nextTime - time();

				$timeZoneFromGmtInSeconds = date('Z', time());

				static::sendToSysLog(
					$templateId,
					0,
					Loc::getMessage('TASKS_REPLICATOR_NEXT_TIME', array(
						'#TIME#' => $nextTimeFormatted.' ('.UI::formatTimezoneOffsetUTC($timeZoneFromGmtInSeconds).')',
						'#PERIOD#' => $pPERIOD,
						'#SECONDS#' => Loc::getMessagePlural('TASKS_REPLICATOR_SECOND', $pPERIOD),
					))
				);

				return $agentName; // keep agent working
			}
			else
			{
				$firstError = $nextResult->getErrors()->first();
				if ($firstError)
				{
					$replicationCancelReason = $firstError->getMessage();
				}
			}
		}

		static::sendToSysLog(
			$templateId,
			0,
			Loc::getMessage('TASKS_REPLICATOR_PROCESS_STOPPED').($replicationCancelReason != ''? ': '.$replicationCancelReason : '')
		);
	}

	return ''; // agent will be simply deleted
}