Your IP : 3.147.28.158


Current Path : /var/www/axolotl/data/www/yar.axolotls.ru/bitrix/modules/tasks/lib/item/
Upload File :
Current File : /var/www/axolotl/data/www/yar.axolotls.ru/bitrix/modules/tasks/lib/item/task.php

<?
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage tasks
 * @copyright 2001-2016 Bitrix
 */

namespace Bitrix\Tasks\Item;

use Bitrix\Main\Localization\Loc;
use Bitrix\Tasks\Access\TaskAccessController;
use Bitrix\Tasks\Comments\Task\CommentPoster;
use Bitrix\Tasks\Integration\Search;
use Bitrix\Tasks\Integration\Pull;
use Bitrix\Tasks\Integration\SocialNetwork\Group;
use Bitrix\Tasks\Internals\Counter;
use Bitrix\Tasks\Internals\SearchIndex;
use Bitrix\Tasks\Internals\TaskTable;
use Bitrix\Tasks\Internals\Task\FavoriteTable;
use Bitrix\Tasks\Internals\Task\LogTable;
use Bitrix\Tasks\Internals\Helper\Task\Dependence;
use Bitrix\Tasks\Internals\UserOption;
use Bitrix\Tasks\UI;
use Bitrix\Tasks\Util;
use Bitrix\Tasks\Util\User;
use Bitrix\Tasks\Util\Type\DateTime;

use Bitrix\Tasks\Item\Converter\Task\ToTemplate as TaskToTemplate;
use Bitrix\Tasks\Item\Converter\Task\ToTask as TaskToTask;

use \Bitrix\Tasks\Integration\Bizproc;

Loc::loadMessages(__FILE__);

/**
 * Class Task
 * @package Bitrix\Tasks\Item
 *
 * @property string $title
 */
final class Task extends \Bitrix\Tasks\Item
{
	public static function getDataSourceClass()
	{
		return TaskTable::getClass();
	}
	public static function getAccessControllerClass()
	{
		return Access\Task::getClass();
	}
	public static function getUserFieldControllerClass()
	{
		return Util\UserField\Task::getClass();
	}

	public function save($settings = array())
	{
		$result = parent::save($settings);
		$id = (int) $this->getId();
		if ($id)
		{
			TaskAccessController::getInstance($this->getUserId())->dropItemCache($id);
		}
		return $result;
	}

	protected static function generateMap(array $parameters = array())
	{
		$map = parent::generateMap(array(
			'EXCLUDE' => array(
				// deprecated
				'ZOMBIE' => true,

				// will be overwritten below
				'RESPONSIBLE_ID' => true,
				'CREATED_BY' => true,
			)
		));

		$map->placeFields(array(
			// override some tablet fields
			'RESPONSIBLE_ID' => new Task\Field\Legacy\MemberOne(array(
				'NAME' => 'RESPONSIBLE_ID',

				'TYPE' => 'R', // todo: replace with constant

				'SOURCE' => Field\Scalar::SOURCE_TABLET,
				'DB_READABLE' => false, // will be calculated from SE_MEMBER
				'OFFSET_GET_CACHEABLE' => false,
			)),
			'CREATED_BY' => new Task\Field\Legacy\MemberOne(array(
				'NAME' => 'CREATED_BY',
				'TYPE' => 'O', // todo: replace with constant

				'SOURCE' => Field\Scalar::SOURCE_TABLET,
				'DB_READABLE' => false, // will be calculated from SE_MEMBER
				'OFFSET_GET_CACHEABLE' => false,
			)),
			'ACCOMPLICES' => new Task\Field\Legacy\Member(array(
				'NAME' => 'ACCOMPLICES',
				'TYPE' => 'A', // todo: replace with constant

				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
				'OFFSET_GET_CACHEABLE' => false,
			)),
			'AUDITORS' => new Task\Field\Legacy\Member(array(
				'NAME' => 'AUDITORS',
				'TYPE' => 'U', // todo: replace with constant

				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
				'OFFSET_GET_CACHEABLE' => false,
			)),
			'TAGS' => new Task\Field\Legacy\Tag(array(
				'NAME' => 'TAGS',

				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
				'OFFSET_GET_CACHEABLE' => false,
			)),
			'DEPENDS_ON' => new Task\Field\Legacy\DependsOn(array(
				'NAME' => 'DEPENDS_ON',
				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
			)),

			// todo: ACCOMPLICES, AUDITORS, SE_ACCOMPLICE, SE_AUDITOR, SE_ORIGINATOR, SE_RESPONSIBLE - just aliases for SE_MEMBER with filtering

			// sub-entity
			'SE_CHECKLIST' => new Task\Field\CheckList(array(
				'NAME' => 'SE_CHECKLIST',
				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
			)),
			'SE_MEMBER' => new Task\Field\Member(array(
				'NAME' => 'SE_MEMBER',
				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
			)),

			// ingoing gantt dependences
			'SE_PROJECTDEPENDENCE' => new Task\Field\ProjectDependence(array(
				'NAME' => 'SE_PROJECTDEPENDENCE',
				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
			)),
			'SE_TAG' => new Task\Field\Tag(array(
				'NAME' => 'SE_TAG',
				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
			)),
			'SE_PARAMETER' => new Task\Field\Parameter(array(
				'NAME' => 'SE_PARAMETER',
				'SOURCE' => Field\Scalar::SOURCE_CUSTOM,
			)),
		));

		return $map;
	}

	public static function getFieldsDescription()
	{
		return static::generateMap();
	}

	public function prepareData($result)
	{
		if(parent::prepareData($result))
		{
			$id = $this->getId();
			$now = $this->getContext()->getNow();

			if(!$this->isFieldModified('CHANGED_DATE'))
			{
				$this['CHANGED_DATE'] = $now;
			}

			// move 0 to null in PARENT_ID to avoid constraint and query problems
			// todo: move PARENT_ID, GROUP_ID and other "foreign keys" to the unique way of keeping absence of relation: null, 0 or ''
			//			if($this->isFieldModified('PARENT_ID'))
			//			{
			//				$parentId = intval($this['PARENT_ID']);
			//				if(!intval($parentId))
			//				{
			//					$this['PARENT_ID'] = false;
			//				}
			//			} // i dont know why

			if(!$id)
			{
				if(!$this->isFieldModified('SITE_ID'))
				{
					$this['SITE_ID'] = $this->getContext()->getSiteId();
				}
				if(!$this->isFieldModified('RESPONSIBLE_ID'))
				{
					$this['RESPONSIBLE_ID'] = $this->getUserId();
				}
				if(!$this->isFieldModified('CREATED_BY'))
				{
					$this['CREATED_BY'] = $this->getUserId();
				}
				if(!$this->isFieldModified('GUID'))
				{
					$this['GUID'] = Util::generateUUID();
				}
				if(!$this->isFieldModified('OUTLOOK_VERSION'))
				{
					$this['OUTLOOK_VERSION'] = 1;
				}

				// force GROUP_ID to 0 if not set (prevent occur as NULL in database)
				$this['GROUP_ID'] = intval($this['GROUP_ID']);

				if(!$this->isFieldModified('CHANGED_BY'))
				{
					$this['CHANGED_BY'] = $this['CREATED_BY'];
				}
				if(!$this->isFieldModified('STATUS_CHANGED_BY'))
				{
					$this['STATUS_CHANGED_BY'] = $this['CHANGED_BY'];
				}

				if(!$this->isFieldModified('CREATED_DATE')) // created date was not set manually
				{
					$this['CREATED_DATE'] = $now;
				}
				if(!$this->isFieldModified('STATUS_CHANGED_DATE'))
				{
					$this['STATUS_CHANGED_DATE'] = $now;
				}

				if($this->isFieldModified('DESCRIPTION_IN_BBCODE') && $this['DESCRIPTION_IN_BBCODE'] != 'Y')
				{
					$result->addError('ILLEGAL_DESCRIPTION', 'Tasks with HTML description are not allowed');
				}

				// todo: move scheduler out of here, to the process manager
				$this->processSchedulerBefore();
			}
			else
			{
				// todo
			}
		}

		return $result->isSuccess();
	}

	public function checkData($result)
	{
		if(parent::checkData($result))
		{
			// data looks good for orm, now check some high-level conditions...

			//		$data = $this->getTransitionState();
			//		$result = $data->getResult();
			//		if($data->isInProgress())
			//		{
			//			$id = $this->getId();
			//
			//			// checking for update()
			//			if($id)
			//			{
			//				// todo: update action is not implemented currently
			//
			//				/*
			//				if($ID && ((isset($arFields['END_DATE_PLAN']) && (string) $arFields['END_DATE_PLAN'] == '')))
			//				{
			//					if(DependenceTable::checkItemLinked($ID))
			//					{
			//						$this->_errors[] = array("text" => GetMessage("TASKS_IS_LINKED_END_DATE_PLAN_REMOVE"), "id" => "ERROR_TASKS_IS_LINKED");
			//					}
			//				}
			//
			//				if($ID && (isset($arFields['PARENT_ID']) && intval($arFields['PARENT_ID']) > 0))
			//				{
			//					if(DependenceTable::checkLinkExists($ID, $arFields['PARENT_ID'], array('BIDIRECTIONAL' => true)))
			//					{
			//						$this->_errors[] = array("text" => GetMessage("TASKS_IS_LINKED_SET_PARENT"), "id" => "ERROR_TASKS_IS_LINKED");
			//					}
			//				}
			//
			//				if ($ID !== false && $ID == $arFields["PARENT_ID"])
			//				{
			//					$this->_errors[] = array("text" => GetMessage("TASKS_PARENT_SELF"), "id" => "ERROR_TASKS_PARENT_SELF");
			//				}
			//
			//				if ($ID !== false && is_array($arFields["DEPENDS_ON"]) && in_array($ID, $arFields["DEPENDS_ON"]))
			//				{
			//					$this->_errors[] = array("text" => GetMessage("TASKS_DEPENDS_ON_SELF"), "id" => "ERROR_TASKS_DEPENDS_ON_SELF");
			//				}
			//				 */
			//			}
			//
			//			// common checking for both add() and update()
			//
			//			// if plan dates were set
			//			if (isset($data['START_DATE_PLAN'])
			//				&& isset($data['END_DATE_PLAN'])
			//				&& ($data['START_DATE_PLAN'] != '')
			//				&& ($data['END_DATE_PLAN'] != '')
			//			)
			//			{
			//				$startDate = \MakeTimeStamp($data['START_DATE_PLAN']);
			//				$endDate   = \MakeTimeStamp($data['END_DATE_PLAN']);
			//
			//				// and they were really set
			//				if (($startDate > 0)
			//					&& ($endDate > 0)
			//				)
			//				{
			//					// and end date is before start date => then emit error
			//					if ($endDate < $startDate)
			//					{
			//						$result->getErrors()->add('ILLEGAL_PLAN_DATES', Loc::getMessage('TASKS_BAD_PLAN_DATES')); // todo: include lang file
			//					}
			//				}
			//			}
			//		}
		}

		return $result->isSuccess();
	}

	/**
	 * Runs extra code after actions (save() and delete() performed)
	 *
	 * @param State $state
	 * @return bool
	 */
	protected function doPostActions($state)
	{
		if ($state->isModeCreate())
		{
			// todo: TODO TODO TODO: create processors instead of this operations

			// todo: think about "config object" that will allow to switch off some of these actions,
			// todo: this config object may support hierarchy and partial update
			// todo: example: ['notification' => ['enable' => true, 'add' => ['enable' => false]], 'cache' => ['reset' = ['enable' => true]]]
			// todo: we can use \Bitrix\Tasks\Util\Type\StructureChecker to check such option structure, set default one, etc...

			// todo: refactor this later, get rid of CTasks completely

			// todo: NOTE: for add() this is okay, but for update() some fields may be missing,
			// todo: so its better to use actual data here, not from state (which eventually may be incomplete)

			// todo: BELOW: do other stuff related with SE_ managing and other additional "actions"
			// todo: it must not be hardcoded (as we do traditionally),
			// todo: but should look as some kind of high-level event handling to be able to register, un-register and optionally turn off additional "actions"

			$result = $state->getResult();

			$taskId = $this->getId();
			$userId = $this->getUserId();
			$data = $this->prepareLegacyData();
			$fullTaskData = $this->getData();

			$groupId = $data['GROUP_ID'];
			$parentId = (int)$data['PARENT_ID'];

			$participants = [$data['CREATED_BY'], $data['RESPONSIBLE_ID']];
			if (isset($data['ACCOMPLICES']) && is_array($data['ACCOMPLICES']))
			{
				$participants = array_merge($participants, $data['ACCOMPLICES']);
			}
			if (isset($data['AUDITORS']) && is_array($data['AUDITORS']))
			{
				$participants = array_merge($participants, $data['AUDITORS']);
			}
			$participants = array_unique($participants);

			// add to favorite, if parent is in the favorites too
			if ($parentId && FavoriteTable::check(['TASK_ID' => $parentId, 'USER_ID' => $userId]))
			{
				FavoriteTable::add(['TASK_ID' => $taskId, 'USER_ID' => $userId], ['CHECK_EXISTENCE' => false]);
			}

			// note that setting occur as is deprecated. use access checker switch off instead
			$occurAsUserId = (User::getOccurAsId() ?: $userId);

			\CTaskNotifications::sendAddMessage(
				array_merge($data, ['CHANGED_BY' => $occurAsUserId]),
				['SPAWNED_BY_AGENT' => $data['SPAWNED_BY_AGENT'] === 'Y' || $data['SPAWNED_BY_AGENT'] === true]
			);

			foreach ($data['AUDITORS'] as $auditorId)
			{
				UserOption::add($taskId, $auditorId, UserOption\Option::MUTED);
			}

			Counter::onAfterTaskAdd($data);
			Counter::sendPushCounters($participants);

			// changes log
			$this->addLogRecord([
				"TASK_ID" => $taskId,
				"USER_ID" => $occurAsUserId,
				"CREATED_DATE" => $state->getEnterTimeObject(),
				"FIELD" => "NEW",
			], $result);

			Search\Task::index($data); // todo: move this into a special processor
			SearchIndex::setTaskSearchIndex($taskId, $fullTaskData);

			\CTaskSync::addItem($data); // MS Exchange

			$commentPoster = CommentPoster::getInstance($taskId, $data['CREATED_BY']);
			$commentPoster->postCommentsOnTaskAdd($data);

			$this->sendPullEvents($data, $result);

			$batchState = static::getBatchState();
			$batchState->accumulateArray('USER', $participants);

			if ($groupId)
			{
				$batchState->accumulateArray('GROUP', [$groupId]);
			}

			// todo: this should be moved inside PARENT_ID field controller:
			if ($parentId)
			{
				Dependence::attachNew($taskId, $parentId);
			}

			// todo: move this into a separate processor
			$this->processSchedulerAfter();

			// if batch state is off, this will fire immediately.
			// otherwise, this will fire only when somebody calls ::leaveBatchState() on this class
			$batchState->fireLeaveCallback();

			Bizproc\Listener::onTaskAdd($taskId, $data);
		}
		elseif($state->isModeUpdate())
		{
			// todo: DO NOT remove template in case of REPLICATE falls to N
		}
	}

	/**
	 * @param State $state
	 * @return boolean
	 */
	protected function executeHooksBefore($state)
	{
		$this->fireLegacyEvent($state);

		return $state->getResult()->isSuccess();
	}

	/**
	 * @param State $state
	 * @return boolean
	 */
	protected function executeHooksAfter($state)
	{
		$this->fireLegacyEvent($state, false);

		return $state->getResult()->isSuccess();
	}

	/**
	 * Compatibility
	 *
	 * @param State $state
	 * @param bool $isBefore
	 * @return bool
	 */
	private function fireLegacyEvent($state, $isBefore = true)
	{
		$result = $state->getResult();

		$before = $isBefore ? 'Before' : '';
		$canAlterData = false;

		$arFields = $arFieldsSource = array();

		if($state->isModeCreate())
		{
			$name = 'On'.$before.'TaskAdd';
			$unknownErrorMessage = \GetMessage("TASKS_UNKNOWN_ADD_ERROR");

			$arFields = $arFieldsSource = $this->prepareLegacyData();

			if($isBefore)
			{
				$arguments = array(&$arFields);
				$canAlterData = true;
			}
			else
			{
				$arguments = array($this->getId(), &$arFields);
			}
		}
		elseif($state->isModeUpdate())
		{
			$name = 'On'.$before.'TaskUpdate';
			$unknownErrorMessage = \GetMessage("TASKS_UNKNOWN_UPDATE_ERROR");

			$arFields = $arFieldsSource = $this->prepareLegacyData(false, true);
			$arTaskCopy = $this->prepareLegacyData(true);

			$arguments = array($this->getId(), &$arFields, &$arTaskCopy);
			$canAlterData = true;
		}
		elseif($state->isModeDelete())
		{
			$name = 'On'.$before.'TaskDelete';
			$unknownErrorMessage = 'Unknown delete error';

			$arTaskCopy = $this->prepareLegacyData(true);

			$arguments = array($this->getId(), &$arTaskCopy);
		}
		else
		{
			return true;
		}

		global $APPLICATION;

		$executedOnce = false;
		foreach(GetModuleEvents('tasks', $name, true) as $event)
		{
			$executedOnce = true;
			$execResult = 0;

			if(array_key_exists('CALLBACK', $event))
			{
				$handlerName = 'Closure';
			}
			else
			{
				$handlerName = $event['TO_CLASS'].'::'.$event['TO_METHOD'].'();';
			}

			try
			{
				$execResult = ExecuteModuleEventEx($event, $arguments);
			}
			catch(\Exception $e)
			{
				$result->addException($e, 'Exception in event handler: '.$handlerName);
			}

			if($execResult === false)
			{
				$e = $APPLICATION->getException();

				$hasExplanation = false;
				if($e instanceof \CAdminException)
				{
					if (is_array($e->messages))
					{
						foreach($e->messages as $msg)
						{
							$hasExplanation = true;
							$result->addError('EVENT_HANDLER_ERROR', $msg);
						}
					}
				}
				else
				{
					$hasExplanation = true;
					$result->addError('EVENT_HANDLER_ERROR.'.$e->getId(), $e->getString());
				}

				if(!$hasExplanation)
				{
					$result->addError('EVENT_HANDLER_ERROR', $unknownErrorMessage);
				}

				return false;
			}
		}

		if($canAlterData && $executedOnce)
		{
			// find difference between $arFields and $arFieldsSource, and update $this

			foreach($arFields as $key => $newValue)
			{
				// key was added or changed
				if(!array_key_exists($key, $arFieldsSource) || $arFields[$key] != $arFieldsSource[$key])
				{
					$this[$key] = $newValue;
				}
			}

			foreach($arFieldsSource as $key => $oldValue)
			{
				// key was removed
				if(!array_key_exists($key, $arFields))
				{
					$this[$key] = null;
				}
			}
		}

		return true;
	}

	public static function processEnterBatchMode(State\Trigger $state)
	{
		\CTaskNotifications::disableAutoDeliver(); // start buffering notifications
	}

	public static function processLeaveBatchMode(State\Trigger $state)
	{
		global $CACHE_MANAGER;

		$users = $state->getArray('USER');
		$groups = $state->getArray('GROUP');

		// todo: think about "config object" that will allow to switch off some of these actions

		foreach($groups as $group)
		{
			$CACHE_MANAGER->ClearByTag("tasks_group_".$group);
			Group::updateLastActivity($group);
		}
		foreach($users as $userId)
		{
			$CACHE_MANAGER->ClearByTag("tasks_user_".$userId);
		}

		\CTaskNotifications::enableAutoDeliver(); // flush buffer and stop buffering

		return new Result(); // formally
	}

	public function getShortPreview()
	{
		return $this['TITLE'].' ['.$this->getId().']';
	}

	public function getDuration($start = null, $end = null, array $parameters = array())
	{
		if($start === null)
		{
			$start = $this['START_DATE_PLAN'];
		}
		if($end === null)
		{
			$end = $this['END_DATE_PLAN'];
		}

		$start = DateTime::createFrom($start, 0);
		$end = DateTime::createFrom($end, 0);

		if($end === null)
		{
			return INF;
		}
		if($start == null)
		{
			return -INF;
		}

		if(array_key_exists('MATCH_WORK_TIME', $parameters))
		{
			$matchWorkTime = $parameters['MATCH_WORK_TIME'];
		}
		else
		{
			$matchWorkTime = $this['MATCH_WORK_TIME'];
		}
		$matchWorkTime = $matchWorkTime == 'Y' || $matchWorkTime === true || $matchWorkTime === 1  || $matchWorkTime === '1';

		if($matchWorkTime)
		{
			$calendar = new Util\Calendar();
			return $calendar->calculateDuration($start, $end);
		}
		else
		{
			return $end->getTimestamp() - $start->getTimestamp();
		}
	}

	/**
	 * Set task change time to the specified value
	 *
	 * @param null $time
	 * @return $this
	 */
	public function touch($time = null)
	{
		if(!$this->isImmutable())
		{
			if($time === null)
			{
				$time = $this->getContext()->getNow();
			}

			$this['CHANGED_DATE'] = UI::formatDateTime($time);
		}

		return $this;
	}

	/**
	 * Set task status to 'completed' or 'awaiting approval'
	 *
	 * @param array $params
	 *
	 * @return $this
	 * @throws \Bitrix\Main\SystemException
	 */
	public function complete(array $params = array())
	{
		if(!$this->isImmutable())
		{
			$status = 5;
			if($this->taskControl == 'Y' && $this->userId == $this->responsibleId && $this->userId != $this->createdBy)
			{
				$status = 4;
			}

			$this->status = $status;
			$this->save($params);
		}

		return $this;
	}

	/**
	 * Creates new virtual (not presented in database) instance of \Bitrix\Tasks\Item\Task\Template based on
	 * data from $this
	 *
	 * @return Converter\Result
	 */
	public function transformToTemplate()
	{
		// todo: better to use the same converter over and over again, so use object pool here when ready
		$converter = new TaskToTemplate();

		return $this->transform($converter);
	}

	/**
	 * Creates new virtual (not presented in database) instance of \Bitrix\Tasks\Item\Task based on
	 * data from $this
	 *
	 * @return Converter\Result
	 */
	public function transformToTask()
	{
		// todo: better to use the same converter over and over again, so use object pool here when ready
		$converter = new TaskToTask();

		return $this->transform($converter);
	}

	private function sendPullEvents($data, $result)
	{
		$id = $this->getId();
		$recipients = array_merge(array($data["CREATED_BY"], $data["RESPONSIBLE_ID"]), $data["ACCOMPLICES"], $data["AUDITORS"]);

		try
		{
			$groupId = intval($data['GROUP_ID']);

			$arPullData = array(
				'TASK_ID' => $id,
				'AFTER' => array(
					'GROUP_ID' => $groupId
				),
				'TS' => time(),
				'event_GUID' => isset($data['META::EVENT_GUID']) ? $data['META::EVENT_GUID'] : sha1(uniqid('AUTOGUID', true))
			);

			Pull::emitMultiple($recipients, 'TASKS_GENERAL_#USER_ID#', 'task_add', $arPullData);
			Pull::emitMultiple($recipients, 'TASKS_TASK_'.$id, 'task_add', $arPullData);
		}
		catch (\Exception $e)
		{
			Util::log($e);
			$result->getErrors()->addWarning('POST_ACTION_FAILURE.PULL', Loc::getMessage('TASKS_ITEM_TASK_PULL_NOT_SENT'), array('EXCEPTION' => $e));
		}
	}

	private function addLogRecord($logData, $result)
	{
		$addResult = LogTable::add($logData);
		$result->adoptErrors($addResult, array(
			'TYPE' => Util\Error::TYPE_WARNING,
			'CODE' => 'POST_ACTION.LOG',
			'MESSAGE' => Loc::getMessage('TASKS_ITEM_TASK_LOG_NOT_CREATED').': #MESSAGE#',
		));
	}

	/**
	 * Compatibility
	 */
	private function prepareLegacyData($pristine = false, $onlyModified = false)
	{
		$allowed = array_merge(array(
			'ID',
			'PRIORITY',
			'TITLE',
			'DESCRIPTION',
			'DESCRIPTION',
			'DEADLINE',
			'START_DATE_PLAN',
			'DURATION_TYPE',
			'END_DATE_PLAN',
			'ALLOW_CHANGE_DEADLINE',
			'MATCH_WORK_TIME',
			'TASK_CONTROL',
			'ALLOW_TIME_TRACKING',
			'TIME_ESTIMATE',
			'REPLICATE',
			'CREATED_BY',
			'RESPONSIBLE_ID',
			'AUDITORS',
			'ACCOMPLICES',
			'TAGS',
			'DEPENDS_ON',
			'PARENT_ID',
			'GROUP_ID',
			'CHANGED_BY',
			'CHANGED_DATE',
			'OUTLOOK_VERSION',
			'DURATION_PLAN',
		), $this->getMap()->getUserFieldNames());

		if($onlyModified)
		{
			$modified = $this->getModifiedFields();
			$modified[] = 'ID';

			$allowed = array_intersect($allowed, $modified);
		}

		if($pristine)
		{
			$this->setDataContext('pristine');
		}

		$data = $this->export($allowed);

		if($pristine)
		{
			$this->setDefaultDataContext();
		}

		$data['SE_TAG'] = '';

		/** @var \Bitrix\Tasks\Item\Task\Collection\Tag $tags */
		$tags = $this['SE_TAG'];
		if($tags)
		{
			$data['SE_TAG'] = $tags->joinNames();
		}

		return $data;
	}

	/**
	 * this will work only for add()
	 * @see \CTasks::Add
	 * @throws \Bitrix\Main\ArgumentException
	 */
	private function processSchedulerBefore()
	{
		$shiftResult = null;
		if((string) $this['START_DATE_PLAN'] != '' || (string) $this['END_DATE_PLAN'] != '')
		{
			$scheduler = \Bitrix\Tasks\Processor\Task\Scheduler::getInstance($this->getUserId());
			$shiftResult = $scheduler->processEntity(0, $this->getData('~'), array(
				'MODE' => 'BEFORE_ATTACH',
			));
			if($shiftResult->isSuccess())
			{
				$shiftData = $shiftResult->getImpactById(0);
				if($shiftData)
				{
					// will be saved...
					$this['START_DATE_PLAN'] = $shiftData['START_DATE_PLAN'];
					$this['END_DATE_PLAN'] = $shiftData['END_DATE_PLAN'];
					$this['DURATION_PLAN_SECONDS'] = $shiftData['DURATION_PLAN_SECONDS'];
				}

				$this->getTransitionState()->setValue('PROCESSOR.SCHEDULER.RESULT', $shiftResult);
			}
		}
	}

	/**
	 * this will work only for add()
	 * @see \CTasks::Add
	 */
	private function processSchedulerAfter()
	{
		$shiftResult = $this->getTransitionState()->get('PROCESSOR.SCHEDULER.RESULT');
		if($shiftResult instanceof \Bitrix\Tasks\Processor\Task\Result)
		{
			$shiftResult->save(array('!ID' => 0));
		}
	}
}