Your IP : 18.222.204.214
<?
/**
* Bitrix Framework
* @package bitrix
* @subpackage sale
* @copyright 2001-2015 Bitrix
*
* @access private
*
* This class should be used in components, inside agent functions, in rest, ajax and more, bringing unification to all places and processes
*/
namespace Bitrix\Tasks\Manager;
use Bitrix\Tasks\Access\ActionDictionary;
use Bitrix\Tasks\Comments;
use Bitrix\Tasks\Integration\Extranet;
use Bitrix\Tasks\Integration\SocialNetwork\Group;
use Bitrix\Tasks\Integration\Timeman;
use Bitrix\Tasks\Internals\Task\ParameterTable;
use Bitrix\Tasks\Manager\Task\Accomplice;
use Bitrix\Tasks\Manager\Task\Auditor;
use Bitrix\Tasks\Manager\Task\Checklist;
use Bitrix\Tasks\Manager\Task\ElapsedTime;
use Bitrix\Tasks\Manager\Task\Log;
use Bitrix\Tasks\Manager\Task\Originator;
use Bitrix\Tasks\Manager\Task\Parameter;
use Bitrix\Tasks\Manager\Task\ParentTask;
use Bitrix\Tasks\Manager\Task\Project;
use Bitrix\Tasks\Manager\Task\ProjectDependence;
use Bitrix\Tasks\Manager\Task\RelatedTask;
use Bitrix\Tasks\Manager\Task\Reminder;
use Bitrix\Tasks\Manager\Task\Responsible;
use Bitrix\Tasks\Manager\Task\Tag;
use Bitrix\Tasks\Manager\Task\Template;
use Bitrix\Tasks\CheckList\Task\TaskCheckListFacade;
use Bitrix\Tasks\Util\User;
use Bitrix\Tasks\Util\Error\Collection;
use Bitrix\Tasks\Util\UserField\Task as UserField;
final class Task extends \Bitrix\Tasks\Manager
{
const LIMIT_PAGE_SIZE = 50;
// standard CRUD
/**
* @param int $userId
* @param mixed[] $data
* @param mixed[] $parameters
* <li>PUBLIC_MODE
* <li>SOURCE
* <li> TYPE (TEMPLATE or TASK)
* <li> ID
*/
public static function add($userId, array $data, array $parameters = array('PUBLIC_MODE' => false, 'RETURN_ENTITY' => false))
{
$errors = static::ensureHaveErrorCollection($parameters);
$task = null;
$can = array();
if ($parameters['PUBLIC_MODE'])
{
$data = static::filterData($data, static::getFieldMap(), $errors);
}
$parameters['ANALYTICS_DATA'] = static::getAnalyticsData($data);
if ($errors->checkNoFatals())
{
$cacheAFWasDisabled = \CTasks::disableCacheAutoClear();
$notifADWasDisabled = \CTaskNotifications::disableAutoDeliver();
$task = static::doAdd($userId, $data, $parameters);
if ($notifADWasDisabled)
{
\CTaskNotifications::enableAutoDeliver();
}
if ($cacheAFWasDisabled)
{
\CTasks::enableCacheAutoClear();
}
if ($errors->checkNoFatals())
{
$data = array('ID' => $task->getId());
if ($parameters[ 'RETURN_ENTITY' ])
{
$data = $task->getData(false);
$data[ static::ACT_KEY ] = $can = static::translateAllowedActionNames($task->getAllowedActions(true));
}
}
}
return array(
'TASK' => $task,
'ERRORS' => $errors,
'DATA' => $data,
'CAN' => $can
);
}
private static function getFieldMap()
{
// READ, WRITE, SORT, FILTER, DATE
$fieldMap = \CTasks::getPublicFieldMap();
$fieldMap[ 'REPLICATE' ] = array(1, 1, 0, 0, 0); // not allowed in rest, but allowed here
$fieldMap[ 'MULTITASK' ] = array(1, 1, 0, 0, 0); // not allowed in rest, but allowed here
$fieldMap[ 'ADD_TO_FAVORITE' ] = array(0, 1, 0, 0, 0); // virtual, for add() only
$fieldMap[ 'ADD_TO_TIMEMAN' ] = array(0, 1, 0, 0, 0); // virtual, for add() only
$fieldMap[ 'RESPONSIBLES' ] = array(0, 1, 0, 0, 0); // just for compatibility
return $fieldMap;
}
private static function doAdd($userId, array $data, array $parameters)
{
$userId = (int)$userId;
$errors = static::ensureHaveErrorCollection($parameters);
$data = static::normalizeData($data);
static::inviteMembers($data, $errors);
static::adaptSet($data);
static::ensureDatePlanChangeAllowed($userId, $data);
static::setDefaultUFValues($userId, $data);
$task = \CTaskItem::add(static::stripSubEntityData($data), $userId, $parameters);
$taskId = $task->getId();
if ($taskId)
{
$commentPoster = Comments\Task\CommentPoster::getInstance($taskId, $userId);
$commentPoster->enableDeferredPostMode();
$commentPoster->clearComments();
if (
$data['ADD_TO_TIMEMAN'] === 'Y'
&& $userId === (int)$data['RESPONSIBLE_ID']
&& $userId === User::getId()
&& !Extranet\User::isExtranet($userId)
)
{
// add the task to planner only if the user this method executed under is current and responsible for the task
\CTaskPlannerMaintance::plannerActions(['add' => [$taskId]]);
}
if ($data['ADD_TO_FAVORITE'] == "Y")
{
$task->addToFavorite();
}
// add sub-entities (SE_*)
$subEntityParams = array_merge($parameters, ['MODE' => static::MODE_ADD]);
if (array_key_exists(Reminder::getCode(true), $data))
{
Reminder::manageSet($userId, $taskId, $data[ Reminder::getCode(true) ], $subEntityParams);
}
if (array_key_exists(ProjectDependence::getCode(true), $data))
{
ProjectDependence::manageSet($userId, $taskId, $data[ ProjectDependence::getCode(true) ], $subEntityParams);
}
if (array_key_exists(Checklist::getCode(true), $data))
{
TaskCheckListFacade::merge(
$taskId,
$userId,
$data[CheckList::getCode(true)],
['analyticsData' => $parameters['ANALYTICS_DATA']]
);
}
if (array_key_exists('SE_PARAMETER', $data))
{
Parameter::manageSet($userId, $taskId, $data[ 'SE_PARAMETER' ], $subEntityParams);
}
Template::manageTaskReplication($userId, $taskId, $data, $subEntityParams);
}
return $task;
}
public static function normalizeData($data)
{
if (!is_array($data) || empty($data))
{
return array();
}
foreach ($data as $k => $v)
{
if ($seName = static::checkIsSubEntityKey($k))
{
$fName = __NAMESPACE__ . '\\Task\\' . $seName . '::normalizeData';
if (is_callable($fName))
{
$data[ $k ] = call_user_func_array($fName, array($v));
}
}
}
return $data;
}
private static function inviteMembers(&$data, Collection $errors)
{
//Originator::inviteMembers($data, $errors); // we may not invite originator
Auditor::inviteMembers($data, $errors);
Accomplice::inviteMembers($data, $errors);
Responsible::inviteMembers($data, $errors);
}
private static function adaptSet(&$data)
{
Originator::adaptSet($data);
Auditor::adaptSet($data);
Accomplice::adaptSet($data);
Tag::adaptSet($data);
CheckList::adaptSet($data);
RelatedTask::adaptSet($data);
ParentTask::adaptSet($data);
Project::adaptSet($data);
// special case: responsibles
Responsible::adaptSet($data);
if (is_array($data[ Responsible::getLegacyFieldName() ]))
{
$data[ Responsible::getLegacyFieldName() ] = array_shift($data[ Responsible::getLegacyFieldName() ]);
}
}
// specific functionality
private static function ensureDatePlanChangeAllowed($userId, array &$data)
{
$projdepKey = ProjectDependence::getCode(true);
// smth is meant to be added in project dependency, thus we must enable ALLOW_CHANGE_DEADLINE for the task
// todo: this is required for making dependencies in case of task update with rights loose. remove this when AUTHOR_ID field introduced
if (array_key_exists($projdepKey, $data) && !empty($data[ $projdepKey ]) && $userId == $data[ 'RESPONSIBLE_ID' ])
{
$data[ 'ALLOW_CHANGE_DEADLINE' ] = 'Y';
}
}
private static function setDefaultUFValues($userId, array &$data)
{
$scheme = UserField::getScheme(0, $userId);
foreach ($scheme as $field => $desc)
{
if (!array_key_exists($field, $data))
{
$default = UserField::getDefaultValue($field, $userId);
if ($default !== null)
{
$data[ $field ] = $default;
}
}
}
}
private static function translateAllowedActionNames($can)
{
$newCan = array();
if (is_array($can))
{
foreach ($can as $act => $flag)
{
$newCan[ str_replace('ACTION_', '', $act) ] = $flag;
}
static::replaceKey($newCan, 'CHANGE_DIRECTOR', 'EDIT.ORIGINATOR');
static::replaceKey($newCan, 'CHECKLIST_REORDER_ITEMS', 'CHECKLIST.REORDER');
static::replaceKey($newCan, 'ELAPSED_TIME_ADD', 'ELAPSEDTIME.ADD');
static::replaceKey($newCan, 'START_TIME_TRACKING', 'DAYPLAN.TIMER.TOGGLE');
// todo: when mobile stops using this fields, remove the third argument here
static::replaceKey($newCan, 'CHANGE_DEADLINE', 'EDIT.PLAN', false); // used in mobile already
static::replaceKey($newCan, 'CHECKLIST_ADD_ITEMS', 'CHECKLIST.ADD', false); // used in mobile already
static::replaceKey($newCan, 'ADD_FAVORITE', 'FAVORITE.ADD', false); // used in mobile already
static::replaceKey($newCan, 'DELETE_FAVORITE', 'FAVORITE.DELETE', false); // used in mobile already
}
return $newCan;
}
private static function replaceKey(array &$data, $from, $to, $dropFrom = true)
{
if (array_key_exists($from, $data))
{
$data[ $to ] = $data[ $from ];
if ($dropFrom)
{
unset($data[ $from ]);
}
}
}
// private methods
public static function update($userId, $taskId, array $data, array $parameters = array('PUBLIC_MODE' => false, 'RETURN_ENTITY' => false))
{
$errors = static::ensureHaveErrorCollection($parameters);
$task = null;
$can = array();
if ($parameters[ 'PUBLIC_MODE' ])
{
$data = static::filterData($data, static::getFieldMap(), $errors);
}
if ($errors->checkNoFatals())
{
$cacheAFWasDisabled = \CTasks::disableCacheAutoClear();
$notifADWasDisabled = \CTaskNotifications::disableAutoDeliver();
$updateParams = array(
'TASK_ACTION_UPDATE_PARAMETERS' => array(
'THROTTLE_MESSAGES' => $parameters[ 'THROTTLE_MESSAGES' ]
),
'PUBLIC_MODE' => $parameters[ 'PUBLIC_MODE' ],
'ERRORS' => $errors,
'ANALYTICS_DATA' => static::getAnalyticsData($data),
);
$task = static::doUpdate($userId, $taskId, $data, $updateParams);
if ($notifADWasDisabled)
{
\CTaskNotifications::enableAutoDeliver();
}
if ($cacheAFWasDisabled)
{
\CTasks::enableCacheAutoClear();
}
if ($errors->checkNoFatals())
{
$data = array('ID' => $task->getId());
if ($parameters[ 'RETURN_ENTITY' ])
{
$data = $task->getData(false);
$data[ static::ACT_KEY ] = $can = static::translateAllowedActionNames($task->getAllowedActions(true));
}
}
}
return array(
'TASK' => $task,
'ERRORS' => $errors,
'DATA' => $data,
'CAN' => $can
);
}
private static function doUpdate($userId, $taskId, array $data, array $parameters)
{
$errors = static::ensureHaveErrorCollection($parameters);
$task = static::getTask($userId, $taskId);
$data = static::normalizeData($data);
static::inviteMembers($data, $errors);
static::adaptSet($data);
if (!is_array($parameters[ 'TASK_ACTION_UPDATE_PARAMETERS' ]))
{
$parameters[ 'TASK_ACTION_UPDATE_PARAMETERS' ] = array();
}
static::ensureDatePlanChangeAllowed($userId, $data);
$cleanData = static::stripSubEntityData($data);
$commentPoster = Comments\Task\CommentPoster::getInstance($taskId, $userId);
$commentPoster->clearComments();
$action = ActionDictionary::ACTION_TASK_EDIT;
// under some conditions we may loose rights (for edit or read, or both) during update, so a little trick is needed
$canEditBefore = $task->checkAccess($action); // get our rights before doing anything
if (!empty($cleanData))
{
// spike: save parameters before CTasks::Update(), at low level, to be sure worker will work out correctly
// todo: get rid of this
if ($canEditBefore && array_key_exists('SE_PARAMETER', $data) && is_array($data[ 'SE_PARAMETER' ]))
{
\Bitrix\Tasks\Item\Task\Parameter::deleteByParent($taskId, array());
foreach ($data[ 'SE_PARAMETER' ] as $parameter)
{
unset($parameter[ 'ID' ]);
$parameter[ 'TASK_ID' ] = $taskId;
ParameterTable::add($parameter);
}
}
$commentPoster->enableDeferredPostMode();
$task->update($cleanData, $parameters['TASK_ACTION_UPDATE_PARAMETERS']); // do not check return result, because method will throw an exception on error
}
$canReadAfter = $task->checkCanRead();
$canEditAfter = $canReadAfter && $task->checkAccess($action);
$rightsLost = $canEditBefore && !$canEditAfter;
$adminUserId = \Bitrix\Tasks\Util\User::getAdminId();
// if we have had rights before, but have lost them now, do the rest of update under superuser`s rights, or else continue normally
// todo: instead of replacing userId make option "skipRights under current user"
$continueAs = $rightsLost ? $adminUserId : $userId;
if (!$canReadAfter) // at least become an auditor for that task
{
$sameTask = \CTaskItem::getInstance($taskId, $adminUserId);
$sameTask->startWatch($userId);
}
// update sub-entities (SE_*)
$subEntityParams = array_merge(
$parameters, array('MODE' => static::MODE_UPDATE, 'ERROR' => $errors)
);
if (array_key_exists(Reminder::getCode(true), $data))
{
Reminder::manageSet($userId, $taskId, $data[ Reminder::getCode(true) ], $subEntityParams);
}
if (array_key_exists(ProjectDependence::getCode(true), $data))
{
ProjectDependence::manageSet($continueAs, $taskId, $data[ ProjectDependence::getCode(true) ], $subEntityParams);
}
if (array_key_exists(Checklist::getCode(true), $data))
{
TaskCheckListFacade::merge(
$taskId,
$continueAs,
$data[Checklist::getCode(true)],
['analyticsData' => $parameters['ANALYTICS_DATA']]
);
}
if (array_key_exists('SE_PARAMETER', $data))
{
Parameter::manageSet($userId, $taskId, $data[ 'SE_PARAMETER' ], $subEntityParams);
}
Template::manageTaskReplication($userId, $taskId, $data, $subEntityParams);
$commentPoster->postComments();
$commentPoster->clearComments();
return $task;
}
public static function convertFromItem($item)
{
if (!\Bitrix\Tasks\Item::isA($item))
{
return array();
}
$data = $item->getArray();
// do some transformations...
Originator::formatSet($data);
Auditor::formatSet($data);
Accomplice::formatSet($data);
ParentTask::formatSet($data);
Project::formatSet($data);
Tag::formatSet($data);
RelatedTask::formatSet($data);
// special case for checklist... i hate special cases...
CheckList::parseSet($data);
return $data;
}
public static function makeItem($data, $userId = 0)
{
Originator::adaptSet($data);
Auditor::adaptSet($data);
Accomplice::adaptSet($data);
ParentTask::adaptSet($data);
Project::adaptSet($data);
Tag::adaptSet($data);
RelatedTask::adaptSet($data);
return new \Bitrix\Tasks\Item\Task($data, $userId);
}
public static function get($userId, $taskId, array $parameters = array())
{
$errors = static::ensureHaveErrorCollection($parameters);
// todo: filterArguments() and filterResult() here on public mode?
$data = static::getBasicData($userId, $taskId, $parameters);
$can = array();
if ($errors->checkNoFatals())
{
$can = array(static::ACT_KEY => &$data[ static::ACT_KEY ]); // for compatibility
// select sub-entity related data
if (!is_array($parameters[ 'ENTITY_SELECT' ]))
{
// by default none is selected
$parameters[ 'ENTITY_SELECT' ] = array();
// could be of static::getLegalSubEntities()
}
$entitySelect = array_flip($parameters[ 'ENTITY_SELECT' ]);
Originator::formatSet($data);
Auditor::formatSet($data);
Accomplice::formatSet($data);
ParentTask::formatSet($data);
Project::formatSet($data);
// special case: responsibles
$data[ Responsible::getCode(true) ] = array(array('ID' => $data[ 'RESPONSIBLE_ID' ]));
$code = Tag::getCode(true);
if (isset($entitySelect[ Tag::getCode() ]))
{
$mgrResult = Tag::getList($userId, $taskId);
$data[ $code ] = $mgrResult[ 'DATA' ];
if (!empty($mgrResult[ 'CAN' ]))
{
$can[ $code ] = $mgrResult[ 'CAN' ];
}
Tag::adaptSet($data); // for compatibility
}
$code = Checklist::getCode(true);
if (isset($entitySelect[ 'CHECKLIST' ]))
{
$mgrResult = TaskCheckListFacade::getItemsForEntity($taskId, $userId);
$data[$code] = $mgrResult;
foreach ($mgrResult as $id => $item)
{
$can[$code][$id]['ACTION'] = $item['ACTION'];
}
}
if (isset($entitySelect[ 'REMINDER' ]))
{
$mgrResult = Reminder::getListByParentEntity($userId, $taskId, $parameters);
$data[ static::SE_PREFIX . 'REMINDER' ] = $mgrResult[ 'DATA' ];
if (!empty($mgrResult[ 'CAN' ]))
{
$can[ static::SE_PREFIX . 'REMINDER' ] = $mgrResult[ 'CAN' ];
}
}
if (isset($entitySelect[ 'LOG' ]))
{
$mgrResult = Log::getListByParentEntity($userId, $taskId, $parameters);
$data[ static::SE_PREFIX . 'LOG' ] = $mgrResult[ 'DATA' ];
if (!empty($mgrResult[ 'CAN' ]))
{
$can[ static::SE_PREFIX . 'LOG' ] = $mgrResult[ 'CAN' ];
}
}
if (isset($entitySelect[ 'ELAPSEDTIME' ]))
{
$mgrResult = ElapsedTime::getListByParentEntity($userId, $taskId, $parameters);
$data[ static::SE_PREFIX . 'ELAPSEDTIME' ] = $mgrResult[ 'DATA' ];
if (!empty($mgrResult[ 'CAN' ]))
{
$can[ static::SE_PREFIX . 'ELAPSEDTIME' ] = $mgrResult[ 'CAN' ];
}
}
if (isset($entitySelect[ 'PROJECTDEPENDENCE' ]))
{
$mgrResult = ProjectDependence::getListByParentEntity($userId, $taskId, array_merge($parameters, array(
'TYPE' => ProjectDependence::INGOING,
'DIRECT' => true,
'DEPENDS_ON_DATA' => true
)));
$data[ static::SE_PREFIX . 'PROJECTDEPENDENCE' ] = $mgrResult[ 'DATA' ];
if (!empty($mgrResult[ 'CAN' ]))
{
$can[ static::SE_PREFIX . 'PROJECTDEPENDENCE' ] = $mgrResult[ 'CAN' ];
}
}
if (isset($entitySelect[ 'TEMPLATE' ]))
{
if ($data[ 'REPLICATE' ] == 'Y')
{
$template = Template::getByParentTask($userId, $taskId);
$data[ static::SE_PREFIX . 'TEMPLATE' ] = $template[ 'DATA' ];
}
}
if (isset($entitySelect[ 'TEMPLATE.SOURCE' ]))
{
if (intval($data[ 'FORKED_BY_TEMPLATE_ID' ]))
{
$template = Template::get($userId, intval($data[ 'FORKED_BY_TEMPLATE_ID' ]));
// todo: remove this
$tData = $template[ 'DATA' ];
if (!empty($tData))
{
$tData = array(
'ID' => $tData[ 'ID' ],
'TITLE' => $tData[ 'TITLE' ],
'TASK_ID' => $tData[ 'TASK_ID' ],
'TPARAM_TYPE' => $tData[ 'TPARAM_TYPE' ],
'REPLICATE_PARAMS' => $tData[ 'REPLICATE_PARAMS' ]
);
}
$data[ static::SE_PREFIX . 'TEMPLATE.SOURCE' ] = $tData;
}
}
if (isset($entitySelect[ 'RELATEDTASK' ]))
{
$mgrResult = RelatedTask::getListByParentEntity($userId, $taskId, $parameters);
$data[ static::SE_PREFIX . 'RELATEDTASK' ] = $mgrResult[ 'DATA' ];
if (!empty($mgrResult[ 'CAN' ]))
{
$can[ static::SE_PREFIX . 'RELATEDTASK' ] = $mgrResult[ 'CAN' ];
}
}
if (isset($entitySelect[ 'TIMEMANAGER' ]) || isset($entitySelect[ 'DAYPLAN' ])) // 'TIMEMANAGER' condition left for compatibility
{
$subData = array($data[ 'ID' ] => &$data);
$subCan = array($data[ 'ID' ] => &$can);
static::injectDayPlanFields($userId, $parameters, $subData, $subCan);
}
}
return array(
'DATA' => $data,
'CAN' => $can, // for compatibility
'ERRORS' => $errors
);
}
private static function getBasicData($userId, $taskId, array $parameters)
{
$data = array();
$denied = false;
try
{
$task = static::getTask($userId, $taskId);
$taskParameters = array();
if ($task !== null)
{
$data = $task->getData(!!$parameters[ 'ESCAPE_DATA' ]);
$data[ static::ACT_KEY ] = static::translateAllowedActionNames($task->getAllowedActions(true));
if (!intval($data[ 'FORUM_ID' ]))
{
$data[ 'FORUM_ID' ] = \CTasksTools::getForumIdForIntranet();
}
$data[ 'COMMENTS_COUNT' ] = intval($data[ 'COMMENTS_COUNT' ]);
// get task parameters
$res = ParameterTable::getList(array('filter' => array('=TASK_ID' => $taskId)));
while ($item = $res->fetch())
{
$taskParameters[] = $item;
}
}
if ($parameters[ 'DROP_PRIMARY' ])
{
unset($data[ 'ID' ]);
$data[ static::ACT_KEY ] = static::getFullRights($userId);
}
$data[ 'SE_PARAMETER' ] = $taskParameters;
}
catch (\TasksException $e) // todo: get rid of this annoying catch by making \Bitrix\Tasks\*Exception classes inherited from TasksException (dont forget about code)
{
if ($e->checkOfType(\TasksException::TE_TASK_NOT_FOUND_OR_NOT_ACCESSIBLE))
{
$denied = true;
}
else
{
throw $e; // let it log
}
}
catch (\Bitrix\Tasks\AccessDeniedException $e) // task not found or not accessible
{
$denied = true;
}
if ($denied)
{
$parameters[ 'ERRORS' ]->add('ACCESS_DENIED.NO_TASK', 'Task not found or not accessible');
}
return $data;
}
public static function getFullRights($userId)
{
// get rights as creator, just EDIT for now
return array(
'EDIT' => true,
'EDIT.PLAN' => true,
'CHECKLIST.ADD' => true,
'CHECKLIST.REORDER' => true,
'EDIT.ORIGINATOR' => true,
'FAVORITE.ADD' => true,
'FAVORITE.DELETE' => false,
'DAYPLAN.ADD' => (!Extranet\User::isExtranet($userId) && Timeman::canUse())
);
}
private static function injectDayPlanFields($userId, array $parameters, array &$data, array &$can)
{
if (empty($data))
{
return;
}
$extranetSite = Extranet::isExtranetSite();
$extranetUser = Extranet\User::isExtranet($userId);
// no dayplan for extranet site, even if intranet user goes to extranet site
$plan = array();
if (!$extranetSite && !$extranetUser)
{
$plan = \CTaskPlannerMaintance::getCurrentTasksList();
if (is_array($plan) && !empty($plan))
{
$plan = array_flip($plan);
}
}
foreach ($data as &$task)
{
$inDayPlan = false;
$canAddToPlan = false;
if ($task[ "RESPONSIBLE_ID" ] == $userId || (is_array($task[ 'ACCOMPLICES' ]) && in_array($userId, $task[ 'ACCOMPLICES' ])))
{
$canAddToPlan = true;
// if in day plan already
if (isset($plan[ $task[ 'ID' ] ]))
{
$inDayPlan = true;
$canAddToPlan = false;
}
}
$task[ 'IN_DAY_PLAN' ] = $inDayPlan;
$task[ 'TIME_ELAPSED' ] = intval($task[ 'TIME_SPENT_IN_LOGS' ]);
$task[ 'TIMER_IS_RUNNING_FOR_CURRENT_USER' ] = false;
$can[ $task[ 'ID' ] ][ 'ACTION' ][ 'ADD_TO_DAY_PLAN' ] = $can[ $task[ 'ID' ] ][ 'ACTION' ][ 'DAYPLAN.ADD' ] =
!$extranetUser && $canAddToPlan && Timeman::canUse();
}
unset($task);
// current timer
$runningTaskData = \CTaskTimerManager::getInstance($userId)->getRunningTask(false);
foreach ($data as $k => &$task)
{
if ($task[ 'ID' ] == $runningTaskData[ 'TASK_ID' ] && $task[ 'ALLOW_TIME_TRACKING' ] == 'Y')
{
$task[ 'TIME_ELAPSED' ] += (time() - $runningTaskData[ 'TIMER_STARTED_AT' ]); // elapsed time is a sum of times in task log plus time of the current timer
$task[ 'TIME_ELAPSED' ] = (string)$task[ 'TIME_ELAPSED' ]; // for consistency
$task[ 'TIMER_IS_RUNNING_FOR_CURRENT_USER' ] = true;
}
}
unset($task);
}
public static function getList($userId, array $listParameters = array(), array $parameters = array())
{
$data = [];
$can = [];
$errors = static::ensureHaveErrorCollection($parameters);
// todo: get rid of LIST_PARAMETERS, if can. Move limit, filter, sort, etc.. to the first level
if (array_key_exists('NAV_PARAMS', $listParameters) && !empty($listParameters['NAV_PARAMS']))
{
$params = ['NAV_PARAMS' => $listParameters['NAV_PARAMS']];
}
else
{
$navParams = static::prepareNav(
$listParameters['limit'],
$listParameters['offset'],
$listParameters['page'],
$parameters['PUBLIC_MODE']
);
$params = false;
if (!empty($navParams))
{
$params = ['NAV_PARAMS' => $navParams];
}
}
$getNewCommentsCount = in_array('NEW_COMMENTS_COUNT', $listParameters['select'], true);
if (
array_key_exists('SORTING', (array)$listParameters['order'])
&& array_key_exists('GROUP_ID', $listParameters['legacyFilter'])
)
{
$params['SORTING_GROUP_ID'] = $listParameters['legacyFilter']['GROUP_ID'];
}
if (array_key_exists('USE_MINIMAL_SELECT_LEGACY', $parameters))
{
$params['USE_MINIMAL_SELECT_LEGACY'] = $parameters['USE_MINIMAL_SELECT_LEGACY'];
}
if (array_key_exists('MAKE_ACCESS_FILTER', $parameters))
{
$params['MAKE_ACCESS_FILTER'] = $parameters['MAKE_ACCESS_FILTER'];
}
if ($getNewCommentsCount)
{
$listParameters['select'][] = 'CREATED_DATE';
$listParameters['select'][] = 'VIEWED_DATE';
}
if (
!array_key_exists('RETURN_ACCESS', $parameters)
|| (array_key_exists('RETURN_ACCESS', $parameters) && $parameters['RETURN_ACCESS'] != 'N')
)
{
$listParameters['select'][] = 'ALLOW_CHANGE_DEADLINE';
$listParameters['select'][] = 'TASK_CONTROL';
$listParameters['select'][] = 'ALLOW_TIME_TRACKING';
}
// an exception about sql error may fall here
[$items, $res] = \CTaskItem::fetchListArray(
$userId,
$listParameters['order'],
$listParameters['legacyFilter'],
$params,
$listParameters['select']
);
if (is_array($items) && !empty($items))
{
foreach ($items as $taskData)
{
$taskId = $taskData['ID'];
if (!array_key_exists('RETURN_ACCESS', $parameters) || $parameters['RETURN_ACCESS'] != 'N')
{
$taskData['ACTION'] = $can[$taskId]['ACTION'] = static::translateAllowedActionNames(
\CTaskItem::getAllowedActionsArray($userId, $taskData, true)
);
}
$data[$taskId] = $taskData;
}
}
return [
'DATA' => $data,
'CAN' => $can,
'ERRORS' => $errors,
'AUX' => [
'OBJ_RES' => $res,
],
];
}
public static function getCount(array $filter = array(), array $params = array())
{
return \CTasks::GetCountInt($filter, $params);
}
private static function prepareNav($limit = false, $offset = false, $page=1, $public = false)
{
$nav = array();
if ($limit !== false && $limit !== null)
{
$limit = intval($limit);
if ($public)
{
$limit = min($limit, static::LIMIT_PAGE_SIZE);
}
if ($offset !== false)
{
$nav[ 'nPageSize' ] = $limit;
}
else
{
$nav[ 'nTopCount' ] = $limit;
}
}
else
{
if ($public)
{
$nav[ 'nTopCount' ] = static::LIMIT_PAGE_SIZE;
}
}
if ($offset !== false && $offset !== null)
{
$nav[ 'iNumPageSize' ] = intval($offset);
$nav[ 'iNumPage' ] = intval($page);
}
return $nav;
}
public static function extendData(&$data, array $references = array())
{
if (is_array($references[ 'USER' ]))
{
Originator::extendData($data, $references[ 'USER' ]);
Responsible::extendData($data, $references[ 'USER' ]);
Auditor::extendData($data, $references[ 'USER' ]);
Accomplice::extendData($data, $references[ 'USER' ]);
}
if (is_array($references[ 'RELATED_TASK' ]))
{
RelatedTask::extendData($data, $references[ 'RELATED_TASK' ]);
ParentTask::extendData($data, $references[ 'RELATED_TASK' ]);
ProjectDependence::extendData($data, $references[ 'RELATED_TASK' ]);
}
if (is_array($references[ 'GROUP' ]))
{
Project::extendData($data, $references[ 'GROUP' ]);
}
}
public static function mergeData($primary = array(), $secondary = array())
{
if (is_array($secondary) && is_array($primary))
{
foreach ($secondary as $k => $v)
{
if (!array_key_exists($k, $primary) || $k == static::ACT_KEY) // force rights merging
{
$primary[ $k ] = $secondary[ $k ];
}
elseif ($seName = static::checkIsSubEntityKey($k))
{
$fName = __NAMESPACE__ . '\\Task\\' . $seName . '::mergeData';
if (is_callable($fName))
{
$primary[ $k ] = call_user_func_array($fName, array($primary[ $k ], $secondary[ $k ]));
}
}
}
}
return $primary;
}
/**
* @param array $task
* @param array $fields
* @return array|string
*/
public static function prepareSearchIndex(array $task, array $fields = [])
{
if (empty($task))
{
return '';
}
if (empty($fields))
{
$fields = [
'ID',
'TITLE',
'DESCRIPTION',
'CHECKLIST',
'RESPONSIBLE',
'ORIGINATOR',
'AUDITORS',
'ACCOMPLICES',
'CRM',
'TAGS',
'GROUP'
];
}
$index = [];
foreach ($fields as $field)
{
switch ($field)
{
default:
if (array_key_exists($field, $task) && !empty($task[$field]))
{
$index[] = $task[$field];
}
break;
// custom fields
case 'CHECKLIST':
/** Bitrix\Tasks\Item\Task\Collection\CheckList */
$checkList = (is_object($task['SE_CHECKLIST'])? $task['SE_CHECKLIST']->export() : (array)$task['CHECKLIST']);
foreach ($checkList as $item)
{
$index[] = $item['TITLE'];
}
break;
case 'RESPONSIBLE':
$index[] = join(' ', User::getUserName([$task['RESPONSIBLE_ID']]));
break;
case 'ORIGINATOR':
$index[] = join(' ', User::getUserName([$task['CREATED_BY']]));
break;
case 'AUDITORS':
if (array_key_exists('AUDITORS', $task))
{
$auditors = (is_object($task['AUDITORS'])? $task['AUDITORS']->toArray() : $task['AUDITORS']);
if ($auditors)
{
$index[] = join(' ', User::getUserName(array_unique($auditors)));
}
}
break;
case 'ACCOMPLICES':
if (array_key_exists('ACCOMPLICES', $task))
{
$accomplices = (is_object($task['ACCOMPLICES'])? $task['ACCOMPLICES']->toArray() : $task['ACCOMPLICES']);
if ($accomplices)
{
$index[] = join(' ', User::getUserName(array_unique($accomplices)));
}
}
break;
case 'CRM':
if (\Bitrix\Main\ModuleManager::isModuleInstalled('crm'))
{
$uf = (is_object($task['UF_CRM_TASK'])? $task['UF_CRM_TASK']->toArray() : (array)$task['UF_CRM_TASK']);
foreach ($uf as $item)
{
$crmElement = explode('_', $item);
$type = $crmElement[0];
$typeId = \CCrmOwnerType::ResolveID(\CCrmOwnerTypeAbbr::ResolveName($type));
$title = \CCrmOwnerType::GetCaption($typeId, $crmElement[1]);
$index[] = $title;
}
}
break;
case 'TAGS':
$tags = (is_object($task['TAGS'])? $task['TAGS']->export() : (array)$task['TAGS']);
$index[] = join(' ', $tags);
break;
case 'GROUP':
$groupId = $task['GROUP_ID'];
$groups = Group::getData([$groupId]);
$groupName = $groups[$groupId]['NAME'];
$index[] = $groupName;
break;
}
}
$strIndex = join(' ', $index);
$strIndex = array_unique(explode(' ', $strIndex));
$strIndex = join(' ', $strIndex);
$strIndex = toUpper($strIndex);
return $strIndex;
}
protected static function getLegalSubEntities()
{
static $legal;
if ($legal === null)
{
$legal = array(
Originator::getCode(),
Responsible::getCode(),
Auditor::getCode(),
Accomplice::getCode(),
Checklist::getCode(),
Reminder::getCode(),
ElapsedTime::getCode(),
Log::getCode(),
ProjectDependence::getCode(),
Template::getCode(),
Tag::getCode(),
RelatedTask::getCode(),
'DAYPLAN',
'TIMEMANAGER', // alias for DAYPLAN
);
}
return $legal;
}
/**
* @param $data
* @return array
* @throws \Bitrix\Main\SystemException
*/
protected static function getAnalyticsData(&$data)
{
$code = Checklist::getCode(true);
$checklistData = $data[$code];
if (!$checklistData)
{
return [];
}
$checklistParents = array_filter(
$checklistData,
static function($item)
{
return is_array($item) && $item['PARENT_NODE_ID'] === '0';
}
);
$analyticsData = [
'checklistCount' => count($checklistParents),
];
if ($checklistData['analyticsData'])
{
foreach (explode(',', $checklistData['analyticsData']) as $key => $value)
{
$analyticsData[$value] = 1;
}
}
if ($checklistData['fromDescription'])
{
$analyticsData['fromDescription'] = 1;
}
unset($data[$code]['analyticsData'], $data[$code]['fromDescription']);
return $analyticsData;
}
}