• Модуль: tasks
  • Путь к файлу: ~/bitrix/modules/tasks/classes/general/tasknotifications.php
  • Класс: CTaskNotifications
  • Вызов: CTaskNotifications::sendUpdateMessage
static function sendUpdateMessage($arFields, $arTask, $bSpawnedByAgent = false, array $parameters = array())
{
	// previous fields = $arTask
	// updated fields = $arFields

	if (self::useNewNotifications())
	{
		$task = BitrixTasksInternalsRegistryTaskRegistry::getInstance()
			->drop((int)$arTask['ID'])
			->getObject((int)$arTask['ID'], true);

		if (!$task)
		{
			return;
		}

		$controller = new BitrixTasksInternalsNotificationController();
		$controller->onTaskUpdated($task, $arFields, $arTask, ['spawned_by_agent' => $bSpawnedByAgent]);
		$controller->push();
		return;
	}


	$occurAsUserId = self::getOccurAsUserId($arFields, $arTask, $bSpawnedByAgent, $parameters);
	$effectiveUserId = self::getEffectiveUserId($arFields, $arTask, $bSpawnedByAgent, $parameters);
	// generally, $occurAsUserId === $effectiveUserId, but sometimes dont

	/*
	$bSpawnedByAgent === true means that this task was "createdupdated" on angent, and
	in case of that, message author is defined as $arTask['CRAETED_BY'] (below)
	*/

	if (!$bSpawnedByAgent && ($parameters['THROTTLE_MESSAGES'] ?? null))
	{
		AgentManager::checkAgentIsAlive("notificationThrottleRelease", 300);
		ThrottleTable::submitUpdateMessage($arTask['ID'], $occurAsUserId, $arTask, $arFields);
		return;
	}

	$isBbCodeDescription = true;
	if (isset($arFields['DESCRIPTION_IN_BBCODE']))
	{
		if ($arFields['DESCRIPTION_IN_BBCODE'] === 'N')
			$isBbCodeDescription = false;
	}
	elseif (isset($arTask['DESCRIPTION_IN_BBCODE']))
	{
		if ($arTask['DESCRIPTION_IN_BBCODE'] === 'N')
			$isBbCodeDescription = false;
	}

	$taskReassignedTo = null;

	if (
		isset($arFields['RESPONSIBLE_ID'])
		&& ($arFields['RESPONSIBLE_ID'] > 0)
		&& ($arFields['RESPONSIBLE_ID'] != $arTask['RESPONSIBLE_ID'])
	)
	{
		$taskReassignedTo = $arFields['RESPONSIBLE_ID'];
	}

	foreach (array('CREATED_BY', 'RESPONSIBLE_ID', 'ACCOMPLICES', 'AUDITORS', 'TITLE') as $field)
	{
		if ( ! isset($arFields[$field])
			&& isset($arTask[$field])
		)
		{
			$arFields[$field] = $arTask[$field];
		}
	}

	$cacheWasEnabled = self::enableStaticCache();

	// $arChanges contains datetimes IN SERVER TIME, NOT CLIENT
	$arChanges = CTaskLog::GetChanges($arTask, $arFields);
	$trackedFields = CTaskLog::getTrackedFields();

	$arMerged = array(
		'ADDITIONAL_RECIPIENTS' => array()
	);

	// Pack prev users ids to ADDITIONAL_RECIPIENTS, to ensure,
	// that they all will receive message
	{
		if (isset($arTask['CREATED_BY']))
			$arMerged['ADDITIONAL_RECIPIENTS'][] = $arTask['CREATED_BY'];

		if (isset($arTask['RESPONSIBLE_ID']))
			$arMerged['ADDITIONAL_RECIPIENTS'][] = $arTask['RESPONSIBLE_ID'];

		if (isset($arTask['ACCOMPLICES']) && is_array($arTask['ACCOMPLICES']))
			foreach ($arTask['ACCOMPLICES'] as $userId)
				$arMerged['ADDITIONAL_RECIPIENTS'][] = $userId;

		if (isset($arTask['AUDITORS']) && is_array($arTask['AUDITORS']))
			foreach ($arTask['AUDITORS'] as $userId)
				$arMerged['ADDITIONAL_RECIPIENTS'][] = $userId;
	}

	if (isset($arFields['ADDITIONAL_RECIPIENTS']))
	{
		$arFields['ADDITIONAL_RECIPIENTS'] = array_merge (
			$arFields['ADDITIONAL_RECIPIENTS'],
			$arMerged['ADDITIONAL_RECIPIENTS']
		);
	}
	else
	{
		$arFields['ADDITIONAL_RECIPIENTS'] = $arMerged['ADDITIONAL_RECIPIENTS'];
	}

	$arUsers = CTaskNotifications::__GetUsers($arFields);

	$ignoreAuthor = isset($parameters['IGNORE_AUTHOR']) ? !!$parameters['IGNORE_AUTHOR'] : true;
	$arRecipientsIDs = array_unique(CTaskNotifications::GetRecipientsIDs($arFields, $ignoreAuthor, false, $occurAsUserId));

	if (
		!empty($arRecipientsIDs)
		&& (User::getId() || $arFields["CREATED_BY"])
	)
	{
		$arInvariantChangesStrs = [];
		$arVolatileDescriptions = [];
		$arRecipientsIDsByTimezone = [];
		$i = 0;
		foreach ($arChanges as $key => $value)
		{
			if ($key === 'DESCRIPTION')
			{
				$arInvariantChangesStrs[] = GetMessage('TASKS_MESSAGE_DESCRIPTION_UPDATED');
				continue;
			}

			if ($key === 'ACCOMPLICES' || $key === 'AUDITORS')
			{
				$fromUsers = explode(",", $value["FROM_VALUE"]);
				$toUsers = explode(",", $value["TO_VALUE"]);

				$addedUsers = array_unique(array_diff($toUsers, $fromUsers));
				$addedUsers = array_filter(
					$addedUsers,
					static function ($id) {
						return (int)$id > 0;
					}
				);
				$removedUsers = array_unique(array_diff($fromUsers, $toUsers));
				$removedUsers = array_filter(
					$removedUsers,
					static function ($id) {
						return (int)$id > 0;
					}
				);

				if (count($addedUsers) > 0)
				{
					$arInvariantChangesStrs[] =
						GetMessage("TASKS_MESSAGE_{$key}_ADDED")
						. CTaskNotifications::__Users2String($addedUsers, $arUsers, ($arFields['NAME_TEMPLATE'] ?? null))
					;
				}
				if (count($removedUsers) > 0)
				{
					$arInvariantChangesStrs[] =
						GetMessage("TASKS_MESSAGE_{$key}_REMOVED")
						. CTaskNotifications::__Users2String($removedUsers, $arUsers, ($arFields['NAME_TEMPLATE'] ?? null))
					;
				}
				continue;
			}

			++$i;
			$actionMessage = GetMessage("TASKS_MESSAGE_".$key);
			if($actionMessage == '' && isset($trackedFields[$key]) && ($trackedFields[$key]['TITLE'] ?? null) != '')
			{
				$actionMessage = $trackedFields[$key]['TITLE'];
			}

			if($actionMessage <> '')
			{
				// here we can display value changed for some fields
				$changeMessage = $actionMessage;
				$tmpStr = '';
				switch($key)
				{
					case 'TIME_ESTIMATE':
						$tmpStr .= self::formatTimeHHMM($value["FROM_VALUE"], true)
							." -> "
							.self::formatTimeHHMM($value["TO_VALUE"], true);
						break;

					case "TITLE":
						$tmpStr .= $value["FROM_VALUE"]." -> ".$value["TO_VALUE"];
						break;

					case "RESPONSIBLE_ID":
						$tmpStr .=
							CTaskNotifications::__Users2String($value["FROM_VALUE"], $arUsers, ($arFields["NAME_TEMPLATE"] ?? null))
							. ' -> '
							. CTaskNotifications::__Users2String($value["TO_VALUE"], $arUsers, ($arFields["NAME_TEMPLATE"] ?? null))
						;
						break;

					case "DEADLINE":
					case "START_DATE_PLAN":
					case "END_DATE_PLAN":
						// $arChanges ALREADY contains server time, no need to substract user timezone again
						$utsFromValue = $value['FROM_VALUE'];// - $curUserTzOffset;
						$utsToValue = $value['TO_VALUE'];// - $curUserTzOffset;

						// It will be replaced below to formatted string with correct dates for different timezones
						$placeholder = '###PLACEHOLDER###'.$i.'###';
						$tmpStr .= $placeholder;

						// Collect recipients' timezones
						foreach($arRecipientsIDs as $userId)
						{
							$tzOffset = (int)self::getUserTimeZoneOffset($userId);

							if(!isset($arVolatileDescriptions[$tzOffset]))
							{
								$arVolatileDescriptions[$tzOffset] = array();
							}

							if(!isset($arVolatileDescriptions[$tzOffset][$placeholder]))
							{
								// Make bitrix timestamps for given user
								$bitrixTsFromValue = $utsFromValue + $tzOffset;
								$bitrixTsToValue = $utsToValue + $tzOffset;

								$description = '';

								if($utsFromValue > 360000)        // is correct timestamp?
								{
									$fromValueAsString = BitrixTasksUI::formatDateTime($bitrixTsFromValue, '^'.BitrixTasksUI::getDateTimeFormat());

									$description .= $fromValueAsString;
								}

								$description .= ' --> ';

								if($utsToValue > 360000)        // is correct timestamp?
								{
									$toValueAsString = BitrixTasksUI::formatDateTime($bitrixTsToValue, '^'.BitrixTasksUI::getDateTimeFormat());

									$description .= $toValueAsString;
								}

								$arVolatileDescriptions[$tzOffset][$placeholder] = trim($description);
							}

							$arRecipientsIDsByTimezone[$tzOffset][] = $userId;
						}
						break;

					case "TAGS":
						$tmpStr .= ($value["FROM_VALUE"]? str_replace(",", ", ", $value["FROM_VALUE"])." -> " : "").($value["TO_VALUE"]? str_replace(",", ", ", $value["TO_VALUE"]) : GetMessage("TASKS_MESSAGE_NO_VALUE"));
						break;

					case "PRIORITY":
						$tmpStr .= GetMessage("TASKS_PRIORITY_".$value["FROM_VALUE"])." -> ".GetMessage("TASKS_PRIORITY_".$value["TO_VALUE"]);
						break;

					case "GROUP_ID":
						if($value["FROM_VALUE"] && self::checkUserCanViewGroup($effectiveUserId, $value["FROM_VALUE"]))
						{
							$arGroupFrom = self::getSocNetGroup($value["FROM_VALUE"]);
							{
								if($arGroupFrom)
								{
									$tmpStr .= $arGroupFrom["NAME"]." -> ";
								}
							}
						}
						if($value["TO_VALUE"] && self::checkUserCanViewGroup($effectiveUserId, $value["TO_VALUE"]))
						{
							$arGroupTo = self::getSocNetGroup($value["TO_VALUE"]);
							{
								if($arGroupTo)
								{
									$tmpStr .= $arGroupTo["NAME"];
								}
							}
						}
						else
						{
							$tmpStr .= GetMessage("TASKS_MESSAGE_NO_VALUE");
						}

						break;

					case "PARENT_ID":
						if($value["FROM_VALUE"])
						{
							$rsTaskFrom = CTasks::GetList(array(), array("ID" => $value["FROM_VALUE"]), array('ID', 'TITLE'));
							{
								if($arTaskFrom = $rsTaskFrom->GetNext())
								{
									$tmpStr .= BitrixMainTextEmoji::decode($arTaskFrom["TITLE"])." -> ";
								}
							}
						}
						if($value["TO_VALUE"])
						{
							$rsTaskTo = CTasks::GetList(array(), array("ID" => $value["TO_VALUE"]), array('ID', 'TITLE'));
							{
								if($arTaskTo = $rsTaskTo->GetNext())
								{
									$tmpStr .= BitrixMainTextEmoji::decode($arTaskTo["TITLE"]);
								}
							}
						}
						else
						{
							$tmpStr .= GetMessage("TASKS_MESSAGE_NO_VALUE");
						}
						break;

					case "DEPENDS_ON":
						$arTasksFromStr = array();
						if($value["FROM_VALUE"])
						{
							$rsTasksFrom = CTasks::GetList(array(), array("ID" => explode(",", $value["FROM_VALUE"])), array('ID', 'TITLE'));
							while($arTaskFrom = $rsTasksFrom->GetNext())
							{
								$arTasksFromStr[] = BitrixMainTextEmoji::decode($arTaskFrom["TITLE"]);
							}
						}
						$arTasksToStr = array();
						if($value["TO_VALUE"])
						{
							$rsTasksTo = CTasks::GetList(array(), array("ID" => explode(",", $value["TO_VALUE"])), array('ID', 'TITLE'));
							while($arTaskTo = $rsTasksTo->GetNext())
							{
								$arTasksToStr[] = BitrixMainTextEmoji::decode($arTaskTo["TITLE"]);
							}
						}
						$tmpStr .= ($arTasksFromStr? implode(", ", $arTasksFromStr)." -> " : "").($arTasksToStr? implode(", ", $arTasksToStr) : GetMessage("TASKS_MESSAGE_NO_VALUE"));
						break;

					case "MARK":
						$tmpStr .= (!$value["FROM_VALUE"]? GetMessage("TASKS_MARK_NONE") : GetMessage("TASKS_MARK_".$value["FROM_VALUE"]))." -> ".(!$value["TO_VALUE"]? GetMessage("TASKS_MARK_NONE") : GetMessage("TASKS_MARK_".$value["TO_VALUE"]));
						break;

					case "ADD_IN_REPORT":
						$tmpStr .= ($value["FROM_VALUE"] == "Y"? GetMessage("TASKS_MESSAGE_IN_REPORT_YES") : GetMessage("TASKS_MESSAGE_IN_REPORT_NO"))." -> ".($value["TO_VALUE"] == "Y"? GetMessage("TASKS_MESSAGE_IN_REPORT_YES") : GetMessage("TASKS_MESSAGE_IN_REPORT_NO"));
						break;

					case "DELETED_FILES":
						$tmpStr .= $value["FROM_VALUE"];
						$tmpStr .= $value["TO_VALUE"];
						break;

					case "NEW_FILES":
						$tmpStr .= $value["TO_VALUE"];
						break;
				}
				if ($tmpStr !== '')
				{
					$changeMessage .= ": ".trim($tmpStr);
				}

				$arInvariantChangesStrs[] = $changeMessage;
			}
		}

		$recp2tz = array();
		foreach($arRecipientsIDsByTimezone as $tz => $rcp)
		{
			foreach($rcp as $uid)
			{
				$recp2tz[$uid] = $tz;
			}
		}

		$invariantDescription = null;

		if ( ! empty($arInvariantChangesStrs) )
			$invariantDescription = implode("rn", $arInvariantChangesStrs);

		if (
			($invariantDescription !== null)
			&& ( ! empty($arRecipientsIDs) )
		)
		{
			// If there is no volatile part of descriptions, send to all recipients at once
			if (empty($arVolatileDescriptions))
			{
				$arVolatileDescriptions['some_timezone'] = array();
				$arRecipientsIDsByTimezone['some_timezone']  = $arRecipientsIDs;
			}

			$updateMessage = self::getGenderMessage($occurAsUserId, 'TASKS_TASK_CHANGED_MESSAGE');
			$taskName = self::formatTaskName($arTask['ID'], $arTask['TITLE'], $arTask['GROUP_ID'], false);

			$instant = str_replace(
				array(
					"#TASK_TITLE#"
				),
				array(
					$taskName
				),
				$updateMessage
			);

			$email = str_replace(
				array(
					"#TASK_TITLE#"
				),
				array(
					strip_tags($taskName)
				),
				$updateMessage
			);
			CTaskNotifications::sendMessageEx($arTask["ID"], $occurAsUserId, $arRecipientsIDs, array(
				'INSTANT' => $instant,
				'EMAIL' => $email,
				'PUSH' => self::makePushMessage('TASKS_TASK_CHANGED_MESSAGE', $occurAsUserId, $arTask)
			), array(
				'ENTITY_CODE' => 'TASK',
				'ENTITY_OPERATION' => 'UPDATE',
				'EVENT_DATA' => array(
					'ACTION'   => 'TASK_UPDATE',
					'arFields' => $arFields,
					'arChanges' => $arChanges
				),
				'TASK_DATA' => $arTask,
				'TASK_ASSIGNED_TO' => $taskReassignedTo,
				'CALLBACK' => array(
					'BEFORE_SEND' => function($message) use($isBbCodeDescription, $invariantDescription, $arVolatileDescriptions, $recp2tz)
					{
						$rcp = $message['TO_USER_IDS'][0];
						$volatile = (
							isset($recp2tz[$rcp], $arVolatileDescriptions[$recp2tz[$rcp]])
								? $arVolatileDescriptions[$recp2tz[$rcp]]
								: null
						);

						if(is_array($volatile))
						{
							$description = str_replace(array_keys($volatile), $volatile, $invariantDescription);
						}
						else
						{
							$description = $invariantDescription;
						}

						$message['MESSAGE']['INSTANT'] = str_replace(
							array(
								"#TASK_EXTRA#"
							),
							array(
								$description
							),
							$message['MESSAGE']['INSTANT']
						);

						if ($isBbCodeDescription)
						{
							$parser = new CTextParser();
							$description = str_replace(
								"t",
								'    ',
								$parser->convertText($description)
							);
						}

						$message['MESSAGE']['EMAIL'] = str_replace(
							array(
								"#TASK_EXTRA#"
							),
							array(
								$description
							),
							$message['MESSAGE']['EMAIL']
						);

						return $message;
					}
				)
			));
		}
	}

	// sonet log
	self::SendMessageToSocNet($arFields, $bSpawnedByAgent, $arChanges, $arTask, $parameters);

	if($cacheWasEnabled)
	{
		self::disableStaticCache();
	}
}