. * --------------------------------------------------------------------- */ if (!defined('GLPI_ROOT')) { die("Sorry. You can't access this file directly"); } use Glpi\CalDAV\Contracts\CalDAVCompatibleItemInterface; use Glpi\CalDAV\Traits\VobjectConverterTrait; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Property\FlatText; use Sabre\VObject\Property\IntegerValue; /** * ProjectTask Class * * @since 0.85 **/ class ProjectTask extends CommonDBChild implements CalDAVCompatibleItemInterface { use Glpi\Features\PlanningEvent; use VobjectConverterTrait; // From CommonDBTM public $dohistory = true; // From CommonDBChild static public $itemtype = 'Project'; static public $items_id = 'projects_id'; protected $team = []; static $rightname = 'projecttask'; protected $usenotepad = true; public $can_be_translated = true; const READMY = 1; const UPDATEMY = 1024; static function getTypeName($nb = 0) { return _n('Project task', 'Project tasks', $nb); } static function canPurge() { return static::canChild('canUpdate'); } static function canView() { return (Session::haveRightsOr('project', [Project::READALL, Project::READMY]) || Session::haveRight(self::$rightname, ProjectTask::READMY)); } /** * Is the current user have right to show the current task ? * * @return boolean **/ function canViewItem() { if (!Session::haveAccessToEntity($this->getEntityID(), $this->isRecursive())) { return false; } $project = new Project(); if ($project->getFromDB($this->fields['projects_id'])) { return (Session::haveRight('project', Project::READALL) || (Session::haveRight('project', Project::READMY) && (($project->fields["users_id"] === Session::getLoginUserID()) || $project->isInTheManagerGroup() || $project->isInTheTeam())) || (Session::haveRight(self::$rightname, self::READMY) && (($this->fields["users_id"] === Session::getLoginUserID()) || $this->isInTheTeam()))); } return false; } static function canCreate() { return (Session::haveRight('project', UPDATE)); } static function canUpdate() { return (parent::canUpdate() || Session::haveRight(self::$rightname, self::UPDATEMY)); } /** * Is the current user have right to edit the current task ? * * @return boolean **/ function canUpdateItem() { if (!Session::haveAccessToEntity($this->getEntityID())) { return false; } $project = new Project(); if ($project->getFromDB($this->fields['projects_id'])) { return (Session::haveRight('project', UPDATE) || (Session::haveRight(self::$rightname, self::UPDATEMY) && (($this->fields["users_id"] === Session::getLoginUserID()) || $this->isInTheTeam()))); } return false; } function cleanDBonPurge() { $this->deleteChildrenAndRelationsFromDb( [ ProjectTask_Ticket::class, ProjectTaskTeam::class, VObject::class, ] ); parent::cleanDBonPurge(); } /** * Duplicate all tasks from a project template to his clone * * @deprecated 9.5 * @since 9.2 * * @param integer $oldid ID of the item to clone * @param integer $newid ID of the item cloned **/ static function cloneProjectTask ($oldid, $newid) { global $DB; Toolbox::deprecated('Use clone'); $iterator = $DB->request(['FROM' => 'glpi_projecttasks', 'WHERE' => ['projects_id' => $oldid]]); while ($data = $iterator->next()) { $cd = new self(); unset($data['id']); $data['projects_id'] = $newid; $data = self::checkTemplateEntity($data, $data['projects_id'], Project::class); $data = Toolbox::addslashes_deep($data); $cd->add($data); } } /** * @see commonDBTM::getRights() **/ function getRights($interface = 'central') { $values = parent::getRights(); unset($values[READ], $values[CREATE], $values[UPDATE], $values[DELETE], $values[PURGE]); $values[self::READMY] = __('See (actor)'); $values[self::UPDATEMY] = __('Update (actor)'); return $values; } function defineTabs($options = []) { $ong = []; $this->addDefaultFormTab($ong); $this->addStandardTab(__CLASS__, $ong, $options); $this->addStandardTab('ProjectTaskTeam', $ong, $options); $this->addStandardTab('Document_Item', $ong, $options); $this->addStandardTab('ProjectTask_Ticket', $ong, $options); $this->addStandardTab('Notepad', $ong, $options); $this->addStandardTab('Log', $ong, $options); return $ong; } function post_getFromDB() { // Team $this->team = ProjectTaskTeam::getTeamFor($this->fields['id']); } function post_getEmpty() { $this->fields['percent_done'] = 0; } function post_updateItem($history = 1) { global $DB, $CFG_GLPI; if (in_array('plan_start_date', $this->updates) || in_array('plan_end_date', $this->updates)) { //dates has changed, check for planning conflicts on attached team $team = ProjectTaskTeam::getTeamFor($this->fields['id']); $users = []; foreach ($team as $type => $actors) { switch ($type) { case User::getType(): foreach ($actors as $actor) { $users[$actor['items_id']] = $actor['items_id']; } break; case Group::getType(): foreach ($actors as $actor) { $group_iterator = $DB->request([ 'SELECT' => 'users_id', 'FROM' => Group_User::getTable(), 'WHERE' => ['groups_id' => $actor['items_id']] ]); while ($row = $group_iterator->next()) { $users[$row['users_id']] = $row['users_id']; } } break; case Supplier::getType(): case Contact::getType(): //only Users can be checked for planning conflicts break; default: if (count($actors)) { throw new \RuntimeException($type . " is not (yet?) handled."); } } } foreach ($users as $user) { Planning::checkAlreadyPlanned( $user, $this->fields['plan_start_date'], $this->fields['plan_end_date'] ); } } if (in_array('auto_percent_done', $this->updates) && $this->input['auto_percent_done'] == 1) { // Auto-calculate was toggled. Force recalculation of this and parents self::recalculatePercentDone($this->getID()); } else { // Update parent percent_done if ($this->fields['projecttasks_id'] > 0) { self::recalculatePercentDone($this->fields['projecttasks_id']); } if ($this->fields['projects_id'] > 0) { Project::recalculatePercentDone($this->fields['projects_id']); } } if (isset($this->input['_old_projects_id'])) { // Recalculate previous parent project percent done Project::recalculatePercentDone($this->input['_old_projects_id']); } if (isset($this->input['_old_projecttasks_id'])) { // Recalculate previous parent task percent done self::recalculatePercentDone($this->input['_old_projecttasks_id']); } if (!isset($this->input['_disablenotif']) && $CFG_GLPI["use_notifications"]) { // Read again project to be sure that all data are up to date $this->getFromDB($this->fields['id']); NotificationEvent::raiseEvent("update", $this); } } function post_addItem() { global $CFG_GLPI; // ADD Documents $document_items = Document_Item::getItemsAssociatedTo($this->getType(), $this->fields['id']); $override_input['items_id'] = $this->getID(); foreach ($document_items as $document_item) { $document_item->clone($override_input); } if (!isset($this->input['_disablenotif']) && $CFG_GLPI["use_notifications"]) { // Clean reload of the project $this->getFromDB($this->fields['id']); NotificationEvent::raiseEvent('new', $this); } } /** * Is the current user in the team? * * @return boolean **/ function isInTheTeam() { if (isset($this->team['User']) && count($this->team['User'])) { foreach ($this->team['User'] as $data) { if ($data['items_id'] == Session::getLoginUserID()) { return true; } } } if (isset($_SESSION['glpigroups']) && count($_SESSION['glpigroups']) && isset($this->team['Group']) && count($this->team['Group'])) { foreach ($_SESSION['glpigroups'] as $groups_id) { foreach ($this->team['Group'] as $data) { if ($data['items_id'] == $groups_id) { return true; } } } } return false; } /** * Get team member count * * @return number */ function getTeamCount() { $nb = 0; if (is_array($this->team) && count($this->team)) { foreach ($this->team as $val) { $nb += count($val); } } return $nb; } function pre_deleteItem() { global $CFG_GLPI; if (!isset($this->input['_disablenotif']) && $CFG_GLPI['use_notifications']) { NotificationEvent::raiseEvent('delete', $this); } return true; } function prepareInputForUpdate($input) { if (isset($input['auto_percent_done']) && $input['auto_percent_done']) { unset($input['percent_done']); } if (isset($input["plan"])) { $input["plan_start_date"] = $input['plan']["begin"]; $input["plan_end_date"] = $input['plan']["end"]; } if (isset($input['is_milestone']) && $input['is_milestone']) { $input['plan_end_date'] = $input['plan_start_date']; $input['real_end_date'] = $input['real_start_date']; } if (isset($input['projecttasks_id']) && $input['projecttasks_id'] > 0) { if (self::checkCircularRelation($input['id'], $input['projecttasks_id'])) { Session::addMessageAfterRedirect(__('Circular relation found. Parent not updated.'), false, ERROR); unset($input['projecttasks_id']); } } if ($this->fields['projects_id'] > 0 && isset($input['projects_id']) && ($input['projects_id'] != $this->fields['projects_id'])) { $input['_old_projects_id'] = $this->fields['projects_id']; } if ($this->fields['projecttasks_id'] > 0 && isset($input['projecttasks_id']) && ($input['projecttasks_id'] != $this->fields['projecttasks_id'])) { $input['_old_projecttasks_id'] = $this->fields['projecttasks_id']; } return Project::checkPlanAndRealDates($input); } function prepareInputForAdd($input) { if (!isset($input['projects_id'])) { Session::addMessageAfterRedirect( __('A linked project is mandatory'), false, ERROR ); return false; } if (!isset($input['uuid'])) { $input['uuid'] = \Ramsey\Uuid\Uuid::uuid4(); } if (!isset($input['users_id'])) { $input['users_id'] = Session::getLoginUserID(); } if (!isset($input['date'])) { $input['date'] = $_SESSION['glpi_currenttime']; } if (isset($input["plan"])) { $input["plan_start_date"] = $input['plan']["begin"]; $input["plan_end_date"] = $input['plan']["end"]; } if (isset($input['is_milestone']) && $input['is_milestone']) { $input['plan_end_date'] = $input['plan_start_date']; $input['real_end_date'] = $input['real_start_date']; } return Project::checkPlanAndRealDates($input); } /** * Get all tasks for a project * * @param $ID integer Id of the project * * @return array of tasks ordered by dates **/ static function getAllForProject($ID) { global $DB; $tasks = []; $iterator = $DB->request([ 'FROM' => 'glpi_projecttasks', 'WHERE' => [ 'projects_id' => $ID ], 'ORDERBY' => ['plan_start_date', 'real_start_date'] ]); while ($data = $iterator->next()) { $tasks[] = $data; } return $tasks; } /** * Get all sub-tasks for a project task * @since 9.5.0 * @param $ID integer Id of the project task * * @return array of tasks ordered by dates **/ static function getAllForProjectTask($ID) { global $DB; $tasks = []; $iterator = $DB->request([ 'FROM' => 'glpi_projecttasks', 'WHERE' => [ 'projecttasks_id' => $ID ], 'ORDERBY' => ['plan_start_date', 'real_start_date'] ]); while ($data = $iterator->next()) { $tasks[] = $data; } return $tasks; } /** * Get all linked tickets for a project * * @param $ID integer Id of the project * * @return array of tickets **/ static function getAllTicketsForProject($ID) { global $DB; $iterator = $DB->request([ 'FROM' => 'glpi_projecttasks_tickets', 'INNER JOIN' => [ 'glpi_projecttasks' => [ 'ON' => [ 'glpi_projecttasks_tickets' => 'projecttasks_id', 'glpi_projecttasks' => 'id' ] ] ], 'FIELDS' => 'tickets_id', 'WHERE' => [ 'glpi_projecttasks.projects_id' => $ID ] ]); $tasks = []; while ($data = $iterator->next()) { $tasks[] = $data['tickets_id']; } return $tasks; } /** * Print the Project task form * * @param $ID integer Id of the project task * @param $options array of possible options: * - target form target * - projects_id ID of the software for add process * * @return true if displayed false if item not found or not right to display **/ function showForm($ID, $options = []) { global $CFG_GLPI; $rand_template = mt_rand(); $rand_name = mt_rand(); $rand_description = mt_rand(); $rand_comment = mt_rand(); $rand_project = mt_rand(); $rand_state = mt_rand(); $rand_type = mt_rand(); $rand_percent = mt_rand(); $rand_milestone = mt_rand(); $rand_plan_start_date = mt_rand(); $rand_plan_end_date = mt_rand(); $rand_real_start_date = mt_rand(); $rand_real_end_date = mt_rand(); $rand_effective_duration = mt_rand(); $rand_planned_duration = mt_rand(); if ($ID > 0) { $this->check($ID, READ); $projects_id = $this->fields['projects_id']; $projecttasks_id = $this->fields['projecttasks_id']; } else { $this->check(-1, CREATE, $options); $projects_id = $options['projects_id']; $projecttasks_id = $options['projecttasks_id']; $recursive = $this->fields['is_recursive']; } $this->showFormHeader($options); echo ""; echo ""._n('Project task template', 'Project task templates', 1).""; ProjectTaskTemplate::dropdown(['value' => $this->fields['projecttasktemplates_id'], 'entity' => $this->getEntityID(), 'rand' => $rand_template, 'on_change' => 'projecttasktemplate_update(this.value)']); echo ""; echo ""; echo ""; echo Html::scriptBlock(' function projecttasktemplate_update(value) { $.ajax({ url: "' . $CFG_GLPI["root_doc"] . '/ajax/projecttask.php", type: "POST", data: { projecttasktemplates_id: value } }).done(function(data) { // set input name $("#textfield_name'.$rand_name.'").val(data.name); // set textarea description if (tasktinymce = tinymce.get("description'.$rand_description.'")) { tasktinymce.setContent(data.description); } // set textarea comment $("#comment'.$rand_comment.'").val(data.comments); // set project task $("#dropdown_projecttasks_id'.$rand_project.'").trigger("setValue", data.projecttasks_id); // set state $("#dropdown_projectstates_id'.$rand_state.'").trigger("setValue", data.projectstates_id); // set type $("#dropdown_projecttasktypes_id'.$rand_type.'").trigger("setValue", data.projecttasktypes_id); // set percent done $("#dropdown_percent_done'.$rand_percent.'").trigger("setValue", data.percent_done); // set milestone $("#dropdown_is_milestone'.$rand_milestone.'").trigger("setValue", data.is_milestone); // set plan_start_date $("#showdate'.$rand_plan_start_date.'").val(data.plan_start_date); // set plan_end_date $("#showdate'.$rand_plan_end_date.'").val(data.plan_end_date); // set real_start_date $("#showdate'.$rand_real_start_date.'").val(data.real_start_date); // set real_end_date $("#showdate'.$rand_real_end_date.'").val(data.real_end_date); // set effective_duration $("#dropdown_effective_duration'.$rand_effective_duration.'").trigger("setValue", data.effective_duration); // set planned_duration $("#dropdown_planned_duration'.$rand_planned_duration.'").trigger("setValue", data.planned_duration); }); } '); echo ""._n('Project', 'Projects', Session::getPluralNumber()).""; echo ""; if ($this->isNewID($ID)) { echo ""; echo ""; } echo "". Dropdown::getDropdownName("glpi_projects", $projects_id).""; echo ""; echo "".__('As child of').""; echo ""; $this->dropdown([ 'entity' => $this->fields['entities_id'], 'value' => $projecttasks_id, 'rand' => $rand_project, 'condition' => ['glpi_projecttasks.projects_id' => $this->fields['projects_id']], 'used' => [$this->fields['id']] ]); echo ""; $showuserlink = 0; if (Session::haveRight('user', READ)) { $showuserlink = 1; } if ($ID) { echo ""; echo "".__('Creation date').""; echo ""; echo sprintf(__('%1$s by %2$s'), Html::convDateTime($this->fields["date"]), getUserName($this->fields["users_id"], $showuserlink)); echo ""; echo "".__('Last update').""; echo ""; echo Html::convDateTime($this->fields["date_mod"]); echo ""; } echo "".__('Name').""; echo ""; Html::autocompletionTextField($this, "name", ['size' => 80, 'rand' => $rand_name]); echo "\n"; echo ""; echo ""._x('item', 'State').""; echo ""; ProjectState::dropdown(['value' => $this->fields["projectstates_id"], 'rand' => $rand_state]); echo ""; echo ""._n('Type', 'Types', 1).""; echo ""; ProjectTaskType::dropdown(['value' => $this->fields["projecttasktypes_id"], 'rand' => $rand_type]); echo ""; echo ""; echo "".__('Percent done').""; echo ""; $percent_done_params = [ 'value' => $this->fields['percent_done'], 'rand' => $rand_percent, 'min' => 0, 'max' => 100, 'step' => 5, 'unit' => '%' ]; if ($this->fields['auto_percent_done']) { $percent_done_params['specific_tags'] = ['disabled' => 'disabled']; } Dropdown::showNumber("percent_done", $percent_done_params); $auto_percent_done_params = [ 'type' => 'checkbox', 'name' => 'auto_percent_done', 'title' => __('Automatically calculate'), 'onclick' => "$(\"select[name='percent_done']\").prop('disabled', !$(\"input[name='auto_percent_done']\").prop('checked'));" ]; if ($this->fields['auto_percent_done']) { $auto_percent_done_params['checked'] = 'checked'; } Html::showCheckbox($auto_percent_done_params); echo ""; Html::showToolTip(__('When automatic computation is active, percentage is computed based on the average of all child task percent done.')); echo ""; echo ""; echo ""; echo __('Milestone'); echo ""; echo ""; Dropdown::showYesNo("is_milestone", $this->fields["is_milestone"], -1, ['rand' => $rand_milestone]); $js = "$('#dropdown_is_milestone$rand_milestone').on('change', function(e) { $('tr.is_milestone').toggleClass('starthidden'); })"; echo Html::scriptBlock($js); echo ""; echo ""; echo "".__('Planning').""; echo ""; echo "".__('Planned start date').""; echo ""; Html::showDateTimeField("plan_start_date", ['value' => $this->fields['plan_start_date'], 'rand' => $rand_plan_start_date]); echo ""; echo "".__('Real start date').""; echo ""; Html::showDateTimeField("real_start_date", ['value' => $this->fields['real_start_date'], 'rand' => $rand_real_start_date]); echo "\n"; echo ""; echo "".__('Planned end date').""; echo ""; Html::showDateTimeField("plan_end_date", ['value' => $this->fields['plan_end_date'], 'rand' => $rand_plan_end_date]); echo ""; echo "".__('Real end date').""; echo ""; Html::showDateTimeField("real_end_date", ['value' => $this->fields['real_end_date'], 'rand' => $rand_real_end_date]); echo "\n"; echo ""; echo "".__('Planned duration').""; echo ""; $toadd = []; for ($i = 9; $i <= 100; $i++) { $toadd[] = $i*HOUR_TIMESTAMP; } Dropdown::showTimeStamp("planned_duration", ['min' => 0, 'max' => 8*HOUR_TIMESTAMP, 'rand' => $rand_planned_duration, 'value' => $this->fields["planned_duration"], 'addfirstminutes' => true, 'inhours' => true, 'toadd' => $toadd]); echo ""; echo "".__('Effective duration').""; echo ""; Dropdown::showTimeStamp("effective_duration", ['min' => 0, 'max' => 8*HOUR_TIMESTAMP, 'rand' => $rand_effective_duration, 'value' => $this->fields["effective_duration"], 'addfirstminutes' => true, 'inhours' => true, 'toadd' => $toadd]); if ($ID) { $ticket_duration = ProjectTask_Ticket::getTicketsTotalActionTime($this->getID()); echo "
"; printf(__('%1$s: %2$s'), __('Tickets duration'), Html::timestampToString($ticket_duration, false)); echo '
'; printf(__('%1$s: %2$s'), __('Total duration'), Html::timestampToString($ticket_duration+$this->fields["effective_duration"], false)); } echo "\n"; echo ""; echo "".__('Description').""; echo ""; Html::textarea([ 'name' => 'content', 'enable_richtext' => true, 'editor_id' => "description$rand_description", 'value' => $this->fields["content"], ]); echo ""; echo ""; echo "".__('Comments').""; echo ""; echo ""; echo "\n"; $this->showFormButtons($options); return true; } /** * Get total effective duration of a project task (sum of effective duration + sum of action time of tickets) * * @param $projecttasks_id integer $projecttasks_id ID of the project task * * @return integer total effective duration **/ static function getTotalEffectiveDuration($projecttasks_id) { global $DB; $item = new static(); $time = 0; if ($item->getFromDB($projecttasks_id)) { $time += $item->fields['effective_duration']; } $iterator = $DB->request([ 'SELECT' => new QueryExpression('SUM(glpi_tickets.actiontime) AS duration'), 'FROM' => self::getTable(), 'LEFT JOIN' => [ 'glpi_projecttasks_tickets' => [ 'FKEY' => [ 'glpi_projecttasks_tickets' => 'projecttasks_id', self::getTable() => 'id' ] ], 'glpi_tickets' => [ 'FKEY' => [ 'glpi_projecttasks_tickets' => 'tickets_id', 'glpi_tickets' => 'id' ] ] ], 'WHERE' => [self::getTable() . '.id' => $projecttasks_id] ]); if ($row = $iterator->next()) { $time += $row['duration']; } return $time; } /** * Get total effective duration of a project (sum of effective duration + sum of action time of tickets) * * @param $projects_id integer $project_id ID of the project * * @return integer total effective duration **/ static function getTotalEffectiveDurationForProject($projects_id) { global $DB; $iterator = $DB->request([ 'SELECT' => 'id', 'FROM' => self::getTable(), 'WHERE' => ['projects_id' => $projects_id] ]); $time = 0; while ($data = $iterator->next()) { $time += static::getTotalEffectiveDuration($data['id']); } return $time; } /** * Get total planned duration of a project * * @param $projects_id integer $project_id ID of the project * * @return integer total effective duration **/ static function getTotalPlannedDurationForProject($projects_id) { global $DB; $iterator = $DB->request([ 'SELECT' => new QueryExpression('SUM(planned_duration) AS duration'), 'FROM' => self::getTable(), 'WHERE' => ['projects_id' => $projects_id] ]); while ($data = $iterator->next()) { return $data['duration']; } return 0; } function rawSearchOptions() { $tab = []; $tab[] = [ 'id' => 'common', 'name' => __('Characteristics') ]; $tab[] = [ 'id' => '1', 'table' => $this->getTable(), 'field' => 'name', 'name' => __('Name'), 'datatype' => 'itemlink', 'massiveaction' => false, 'autocomplete' => true, ]; $tab[] = [ 'id' => '2', 'table' => 'glpi_projects', 'field' => 'name', 'name' => Project::getTypeName(1), 'massiveaction' => false, 'datatype' => 'dropdown' ]; $tab[] = [ 'id' => '13', 'table' => $this->getTable(), 'field' => 'name', 'name' => __('Father'), 'datatype' => 'dropdown', 'massiveaction' => false, // Add virtual condition to relink table 'joinparams' => [ 'condition' => 'AND 1=1' ] ]; $tab[] = [ 'id' => '21', 'table' => $this->getTable(), 'field' => 'content', 'name' => __('Description'), 'massiveaction' => false, 'datatype' => 'text' ]; $tab[] = [ 'id' => '12', 'table' => 'glpi_projectstates', 'field' => 'name', 'name' => _x('item', 'State'), 'datatype' => 'dropdown' ]; $tab[] = [ 'id' => '14', 'table' => 'glpi_projecttasktypes', 'field' => 'name', 'name' => _n('Type', 'Types', 1), 'datatype' => 'dropdown' ]; $tab[] = [ 'id' => '15', 'table' => $this->getTable(), 'field' => 'date', 'name' => __('Opening date'), 'datatype' => 'datetime', 'massiveaction' => false ]; $tab[] = [ 'id' => '19', 'table' => $this->getTable(), 'field' => 'date_mod', 'name' => __('Last update'), 'datatype' => 'datetime', 'massiveaction' => false ]; $tab[] = [ 'id' => '5', 'table' => $this->getTable(), 'field' => 'percent_done', 'name' => __('Percent done'), 'datatype' => 'number', 'unit' => '%', 'min' => 0, 'max' => 100, 'step' => 5 ]; $tab[] = [ 'id' => '24', 'table' => 'glpi_users', 'field' => 'name', 'linkfield' => 'users_id', 'name' => __('Creator'), 'datatype' => 'dropdown' ]; $tab[] = [ 'id' => '7', 'table' => $this->getTable(), 'field' => 'plan_start_date', 'name' => __('Planned start date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '8', 'table' => $this->getTable(), 'field' => 'plan_end_date', 'name' => __('Planned end date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '9', 'table' => $this->getTable(), 'field' => 'real_start_date', 'name' => __('Real start date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '10', 'table' => $this->getTable(), 'field' => 'real_end_date', 'name' => __('Real end date'), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '11', 'table' => $this->getTable(), 'field' => 'planned_duration', 'name' => __('Planned duration'), 'datatype' => 'timestamp', 'min' => 0, 'max' => 100*HOUR_TIMESTAMP, 'step' => HOUR_TIMESTAMP, 'addfirstminutes' => true, 'inhours' => true ]; $tab[] = [ 'id' => '17', 'table' => $this->getTable(), 'field' => 'effective_duration', 'name' => __('Effective duration'), 'datatype' => 'timestamp', 'min' => 0, 'max' => 100*HOUR_TIMESTAMP, 'step' => HOUR_TIMESTAMP, 'addfirstminutes' => true, 'inhours' => true ]; $tab[] = [ 'id' => '16', 'table' => $this->getTable(), 'field' => 'comment', 'name' => __('Comments'), 'datatype' => 'text' ]; $tab[] = [ 'id' => '18', 'table' => $this->getTable(), 'field' => 'is_milestone', 'name' => __('Milestone'), 'datatype' => 'bool' ]; $tab[] = [ 'id' => '50', 'table' => $this->getTable(), 'field' => 'template_name', 'name' => __('Template name'), 'datatype' => 'text', 'massiveaction' => false, 'nosearch' => true, 'nodisplay' => true, 'autocomplete' => true, ]; $tab[] = [ 'id' => '80', 'table' => 'glpi_entities', 'field' => 'completename', 'name' => Entity::getTypeName(1), 'datatype' => 'dropdown' ]; $tab[] = [ 'id' => '86', 'table' => $this->getTable(), 'field' => 'is_recursive', 'name' => __('Child entities'), 'datatype' => 'bool' ]; $tab = array_merge($tab, Notepad::rawSearchOptionsToAdd()); return $tab; } /** * Show tasks of a project * * @param $item Project or ProjectTask object * * @return void **/ static function showFor($item) { global $DB; $ID = $item->getField('id'); if (!$item->canViewItem()) { return false; } $columns = [ 'name' => self::getTypeName(Session::getPluralNumber()), 'tname' => _n('Type', 'Types', 1), 'sname' => __('Status'), 'percent_done' => __('Percent done'), 'plan_start_date' => __('Planned start date'), 'plan_end_date' => __('Planned end date'), 'planned_duration' => __('Planned duration'), '_effect_duration' => __('Effective duration'), 'fname' => __('Father') ]; $criteria = [ 'SELECT' => [ 'glpi_projecttasks.*', 'glpi_projecttasktypes.name AS tname', 'glpi_projectstates.name AS sname', 'glpi_projectstates.color', 'father.name AS fname', 'father.id AS fID' ], 'FROM' => 'glpi_projecttasks', 'LEFT JOIN' => [ 'glpi_projecttasktypes' => [ 'ON' => [ 'glpi_projecttasktypes' => 'id', 'glpi_projecttasks' => 'projecttasktypes_id' ] ], 'glpi_projectstates' => [ 'ON' => [ 'glpi_projectstates' => 'id', 'glpi_projecttasks' => 'projectstates_id' ] ], 'glpi_projecttasks AS father' => [ 'ON' => [ 'father' => 'id', 'glpi_projecttasks' => 'projecttasks_id' ] ] ], 'WHERE' => [], //$where 'ORDERBY' => [] // $sort $order"; ]; if (isset($_GET["order"]) && ($_GET["order"] == "DESC")) { $order = "DESC"; } else { $order = "ASC"; } if (!isset($_GET["sort"]) || empty($_GET["sort"])) { $_GET["sort"] = "plan_start_date"; } if (isset($_GET["sort"]) && !empty($_GET["sort"]) && isset($columns[$_GET["sort"]])) { $sort = [$_GET["sort"] . " $order"]; $ui_sort = $_GET['sort']; } else { $sort = ["plan_start_date $order", "name"]; $ui_sort = 'plan_start_date'; } $criteria['ORDERBY'] = $sort; $canedit = false; if ($item->getType() =='Project') { $canedit = $item->canEdit($ID); } switch ($item->getType()) { case 'Project' : $criteria['WHERE']['glpi_projecttasks.projects_id'] = $ID; break; case 'ProjectTask' : $criteria['WHERE']['glpi_projecttasks.projecttasks_id'] = $ID; break; default : // Not available type return; } echo "
"; if ($canedit) { echo "
"; echo "". _x('button', 'Add a task').""; echo "
"; } if (($item->getType() == 'ProjectTask') && $item->can($ID, UPDATE)) { $rand = mt_rand(); echo "
"; echo "
"; $projet = $item->fields['projects_id']; echo ""; echo __('Create a sub task from this task of project'); echo ""; Html::closeForm(); echo "
"; } if (Session::haveTranslations('ProjectTaskType', 'name')) { $criteria['SELECT'][] = 'namet2.value AS transname2'; $criteria['LEFT JOIN']['glpi_dropdowntranslations AS namet2'] = [ 'ON' => [ 'namet2' => 'items_id', 'glpi_projecttasks' => 'projecttasktypes_id', [ 'AND' => [ 'namet2.itemtype' => 'ProjectTaskType', 'namet2.language' => $_SESSION['glpilanguage'], 'namet2.field' => 'name' ] ] ] ]; } if (Session::haveTranslations('ProjectState', 'name')) { $criteria['SELECT'][] = 'namet3.value AS transname3'; $criteria['LEFT JOIN']['glpi_dropdowntranslations AS namet3'] = [ 'ON' => [ 'namet3' => 'items_id', 'glpi_projectstates' => 'id', [ 'AND' => [ 'namet3.itemtype' => 'ProjectState', 'namet3.language' => $_SESSION['glpilanguage'], 'namet3.field' => 'name' ] ] ] ]; } Session::initNavigateListItems('ProjectTask', //TRANS : %1$s is the itemtype name, // %2$s is the name of the item (used for headings of a list) sprintf(__('%1$s = %2$s'), $item::getTypeName(1), $item->getName())); $iterator = $DB->request($criteria); if (count($criteria)) { echo ""; $header = ''; foreach ($columns as $key => $val) { // Non order column if ($key[0] == '_') { $header .= ""; } else { $header .= "". "$val"; } } $header .= "\n"; echo $header; while ($data = $iterator->next()) { Session::addToNavigateListItems('ProjectTask', $data['id']); $rand = mt_rand(); echo ""; echo ""; $name = !empty($data['transname2'])?$data['transname2']:$data['tname']; echo ""; echo "".$statename.""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } echo $header; echo "
$val
"; $link = "".$data['name']. (empty($data['name'])?"(".$data['id'].")":"").""; echo sprintf(__('%1$s %2$s'), $link, Html::showToolTip(Html::entity_decode_deep($data['content']), ['display' => false, 'applyto' => "ProjectTask".$data["id"].$rand])); echo "".$name.""; echo Dropdown::getValueWithUnit($data["percent_done"], "%"); echo "".Html::convDateTime($data['plan_start_date'])."".Html::convDateTime($data['plan_end_date'])."".Html::timestampToString($data['planned_duration'], false)."".Html::timestampToString(self::getTotalEffectiveDuration($data['id']), false).""; if ($data['projecttasks_id']>0) { $father = Dropdown::getDropdownName('glpi_projecttasks', $data['projecttasks_id']); echo "".$father. (empty($father)?"(".$data['projecttasks_id'].")":"").""; } echo "
\n"; } else { echo ""; echo ""; echo "
".__('No item found')."
\n"; } echo "
"; } function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { $nb = 0; switch ($item->getType()) { case 'Project' : if ($_SESSION['glpishow_count_on_tabs']) { $nb = countElementsInTable($this->getTable(), ['projects_id' => $item->getID()]); } return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb); case __CLASS__ : if ($_SESSION['glpishow_count_on_tabs']) { $nb = countElementsInTable($this->getTable(), ['projecttasks_id' => $item->getID()]); } return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb); } return ''; } static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { switch ($item->getType()) { case 'Project' : self::showFor($item); break; case __CLASS__ : self::showFor($item); break; } return true; } /** * Show team for a project task * * @param $task ProjectTask object * * @return boolean **/ function showTeam(ProjectTask $task) { /// TODO : permit to simple add member of project team ? $ID = $task->fields['id']; $canedit = $task->canEdit($ID); $rand = mt_rand(); $nb = 0; $nb = $task->getTeamCount(); if ($canedit) { echo "
"; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo "
".__('Add a team member')."
"; $params = ['itemtypes' => ProjectTeam::$available_types, 'entity_restrict' => ($task->fields['is_recursive'] ? getSonsOf('glpi_entities', $task->fields['entities_id']) : $task->fields['entities_id']), 'checkright' => true]; $addrand = Dropdown::showSelectItemFromItemtypes($params); echo ""; echo ""; echo "
"; Html::closeForm(); echo "
"; } echo "
"; if ($canedit && $nb) { Html::openMassiveActionsForm('mass'.__CLASS__.$rand); $massiveactionparams = ['num_displayed' => min($_SESSION['glpilist_limit'], $nb), 'container' => 'mass'.__CLASS__.$rand]; Html::showMassiveActions($massiveactionparams); } echo ""; $header_begin = ""; $header_top = ''; $header_bottom = ''; $header_end = ''; if ($canedit && $nb) { $header_top .= ""; $header_bottom .= ""; } $header_end .= ""; $header_end .= ""; $header_end .= ""; echo $header_begin.$header_top.$header_end; foreach (ProjectTaskTeam::$available_types as $type) { if (isset($task->team[$type]) && count($task->team[$type])) { if ($item = getItemForItemtype($type)) { foreach ($task->team[$type] as $data) { $item->getFromDB($data['items_id']); echo ""; if ($canedit) { echo ""; } echo ""; echo ""; echo ""; } } } } if ($nb) { echo $header_begin.$header_bottom.$header_end; } echo "
".Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand); $header_top .= "".Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand); $header_bottom .= ""._n('Type', 'Types', 1).""._n('Member', 'Members', Session::getPluralNumber())."
"; Html::showMassiveActionCheckBox('ProjectTaskTeam', $data["id"]); echo "".$item->getTypeName(1)."".$item->getLink()."
"; if ($canedit && $nb) { $massiveactionparams['ontop'] =false; Html::showMassiveActions($massiveactionparams); Html::closeForm(); } echo "
"; // Add items return true; } /** Get data to display on GANTT for a project task * * @param $ID ID of the project task */ static function getDataToDisplayOnGantt($ID) { global $DB; $todisplay = []; $task = new self(); // echo $ID.'
'; if ($task->getFromDB($ID)) { $subtasks = []; foreach ($DB->request('glpi_projecttasks', ['projecttasks_id' => $ID, 'ORDER' => ['plan_start_date', 'real_start_date']]) as $data) { $subtasks += static::getDataToDisplayOnGantt($data['id']); } $real_begin = null; $real_end = null; // Use real if set if (!is_null($task->fields['real_start_date'])) { $real_begin = $task->fields['real_start_date']; } // Determine begin / end date of current task if not set (min/max sub projects / tasks) if (is_null($real_begin)) { if (!is_null($task->fields['plan_start_date'])) { $real_begin = $task->fields['plan_start_date']; } else { foreach ($subtasks as $subtask) { if (is_null($real_begin) || (!is_null($subtask['from']) && ($real_begin > $subtask['from']))) { $real_begin = $subtask['from']; } } } } // Use real if set if (!is_null($task->fields['real_end_date'])) { $real_end = $task->fields['real_end_date']; } if (is_null($real_end)) { if (!is_null($task->fields['plan_end_date'])) { $real_end = $task->fields['plan_end_date']; } else { foreach ($subtasks as $subtask) { if (is_null($real_end) || (!is_null($subtask['to']) && ($real_end < $subtask['to']))) { $real_end = $subtask['to']; } } } } $parents = 0; if ($task->fields['projecttasks_id'] > 0) { $parents = count(getAncestorsOf("glpi_projecttasks", $ID)); } if ($task->fields['is_milestone']) { $percent = ""; } else { $percent = isset($task->fields['percent_done'])?$task->fields['percent_done']:0; } // Add current task $todisplay[$real_begin.'#'.$real_end.'#task'.$task->getID()] = ['id' => $task->getID(), 'name' => $task->fields['name'], 'desc' => $task->fields['content'], 'link' => $task->getlink(), 'type' => 'task', 'percent' => $percent, 'from' => $real_begin, 'parents' => $parents, 'to' => $real_end, 'is_milestone' => $task->fields['is_milestone']]; // Add ordered subtasks foreach ($subtasks as $key => $val) { $todisplay[$key] = $val; } } return $todisplay; } /** Get data to display on GANTT for a project * * @param $ID ID of the project */ static function getDataToDisplayOnGanttForProject($ID) { global $DB; $todisplay = []; $task = new self(); // Get all tasks without father foreach ($DB->request('glpi_projecttasks', ['projects_id' => $ID, 'projecttasks_id' => 0, 'ORDER' => ['plan_start_date', 'real_start_date']]) as $data) { if ($task->getFromDB($data['id'])) { $todisplay += static::getDataToDisplayOnGantt($data['id']); } } return $todisplay; } /** * Display debug information for current object **/ function showDebug() { NotificationEvent::debugEvent($this); } /** * Populate the planning with planned project tasks * * @since 9.1 * * @param $options array of possible options: * - who ID of the user (0 = undefined) * - whogroup ID of the group of users (0 = undefined) * - begin Date * - end Date * - color * - event_type_color * * @return array of planning item **/ static function populatePlanning($options = []) :array { global $DB, $CFG_GLPI; $interv = []; $ttask = new self; if (!isset($options['begin']) || ($options['begin'] == 'NULL') || !isset($options['end']) || ($options['end'] == 'NULL')) { return $interv; } $default_options = [ 'genical' => false, 'color' => '', 'event_type_color' => '', ]; $options = array_merge($default_options, $options); $who = $options['who']; $whogroup = $options['whogroup']; $begin = $options['begin']; $end = $options['end']; // Get items to print $ADDWHERE = []; if ($whogroup === "mine") { if (isset($_SESSION['glpigroups'])) { $whogroup = $_SESSION['glpigroups']; } else if ($who > 0) { $whogroup = array_column(Group_User::getUserGroups($who), 'id'); } } if ($who > 0) { $ADDWHERE['glpi_projecttaskteams.itemtype'] = 'User'; $ADDWHERE['glpi_projecttaskteams.items_id'] = $who; } if ($whogroup > 0) { $ADDWHERE['glpi_projecttaskteams.itemtype'] = 'Group'; $ADDWHERE['glpi_projecttaskteams.items_id'] = $whogroup; } if (!count($ADDWHERE)) { $ADDWHERE = [ 'glpi_projecttaskteams.itemtype' => 'User', 'glpi_projecttaskteams.items_id' => new \QuerySubQuery([ 'SELECT' => 'glpi_profiles_users.users_id', 'DISTINCT' => true, 'FROM' => 'glpi_profiles', 'LEFT JOIN' => [ 'glpi_profiles_users' => [ 'ON' => [ 'glpi_profiles_users' => 'profiles_id', 'glpi_profiles' => 'id' ] ] ], 'WHERE' => [ 'glpi_profiles.interface' => 'central' ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $_SESSION['glpiactive_entity'], 1) ]) ]; } if (!isset($options['display_done_events']) || !$options['display_done_events']) { $ADDWHERE['glpi_projecttasks.percent_done'] = ['<', 100]; $ADDWHERE[] = ['OR' => [ ['glpi_projectstates.is_finished' => 0], ['glpi_projectstates.is_finished' => null] ]]; } $SELECT = [$ttask->getTable() . '.*']; $WHERE = $ADDWHERE; if (isset($options['not_planned'])) { //not planned case $bdate = "DATE_SUB(".$DB->quoteName($ttask->getTable() . '.date') . ", INTERVAL ".$DB->quoteName($ttask->getTable() . '.planned_duration')." SECOND)"; $SELECT[] = new QueryExpression($bdate . ' AS ' . $DB->quoteName('notp_date')); $edate = "DATE_ADD(".$DB->quoteName($ttask->getTable() . '.date') . ", INTERVAL ".$DB->quoteName($ttask->getTable() . '.planned_duration')." SECOND)"; $SELECT[] = new QueryExpression($edate . ' AS ' . $DB->quoteName('notp_edate')); $WHERE = [ $ttask->getTable() . '.plan_start_date' => null, $ttask->getTable() . '.plan_end_date' => null, $ttask->getTable() . '.planned_duration' => ['>', 0], //begin is replaced with creation tim minus duration new QueryExpression($edate . " >= '" . $begin . "'"), new QueryExpression($bdate . " <= '" . $end . "'") ]; } else { //std case: get tasks for current view dates $WHERE[$ttask->getTable() . '.plan_end_date'] = ['>=', $begin]; $WHERE[$ttask->getTable() . '.plan_start_date'] = ['<=', $end]; } $iterator = $DB->request([ 'SELECT' => $SELECT, 'FROM' => 'glpi_projecttaskteams', 'INNER JOIN' => [ $ttask->getTable() => [ 'ON' => [ 'glpi_projecttaskteams' => 'projecttasks_id', $ttask->getTable() => 'id' ] ] ], 'LEFT JOIN' => [ 'glpi_projectstates' => [ 'ON' => [ $ttask->getTable() => 'projectstates_id', 'glpi_projectstates' => 'id' ] ] ], 'WHERE' => $WHERE, 'ORDERBY' => $ttask->getTable() . '.plan_start_date' ]); $interv = []; $task = new self(); if (count($iterator)) { while ($data = $iterator->next()) { if ($task->getFromDB($data["id"])) { if (isset($data['notp_date'])) { $data['plan_start_date'] = $data['notp_date']; $data['plan_end_date'] = $data['notp_edate']; } $key = $data["plan_start_date"]. "$$$"."ProjectTask". "$$$".$data["id"]. "$$$".$who."$$$".$whogroup; $interv[$key]['color'] = $options['color']; $interv[$key]['event_type_color'] = $options['event_type_color']; $interv[$key]['itemtype'] = 'ProjectTask'; if (!$options['genical']) { $interv[$key]["url"] = Project::getFormURLWithID($task->fields['projects_id']); } else { $interv[$key]["url"] = $CFG_GLPI["url_base"]. Project::getFormURLWithID($task->fields['projects_id'], false); } $interv[$key]["ajaxurl"] = $CFG_GLPI["root_doc"]."/ajax/planning.php". "?action=edit_event_form". "&itemtype=ProjectTask". "&id=".$data['id']. "&url=".$interv[$key]["url"]; $interv[$key][$task->getForeignKeyField()] = $data["id"]; $interv[$key]["id"] = $data["id"]; $interv[$key]["users_id"] = $data["users_id"]; if (strcmp($begin, $data["plan_start_date"]) > 0) { $interv[$key]["begin"] = $begin; } else { $interv[$key]["begin"] = $data["plan_start_date"]; } if (strcmp($end, $data["plan_end_date"]) < 0) { $interv[$key]["end"] = $end; } else { $interv[$key]["end"] = $data["plan_end_date"]; } $interv[$key]["name"] = $task->fields["name"]; $interv[$key]["content"] = Html::resume_text($task->fields["content"], $CFG_GLPI["cut"]); $interv[$key]["status"] = $task->fields["percent_done"]; $ttask->getFromDB($data["id"]); $interv[$key]["editable"] = $ttask->canUpdateItem(); } } } return $interv; } /** * Populate the planning with not planned project tasks * * @since 9.1 * * @param $options array of possible options: * - who ID of the user (0 = undefined) * - whogroup ID of the group of users (0 = undefined) * - begin Date * - end Date * - color * - event_type_color * * @return array of planning item **/ static function populateNotPlanned($options = []) :array { $options['not_planned'] = true; return self::populatePlanning($options); } /** * Display a Planning Item * * @since 9.1 * * @param $val array of the item to display * @param $who ID of the user (0 if all) * @param $type position of the item in the time block (in, through, begin or end) * (default '') * @param $complete complete display (more details) (default 0) * * @return string **/ static function displayPlanningItem(array $val, $who, $type = "", $complete = 0) { global $CFG_GLPI; $html = ""; $rand = mt_rand(); $users_id = ""; // show users_id project task $img = "rdv_private.png"; // default icon for project task if ($val["users_id"] != Session::getLoginUserID()) { $users_id = "
".sprintf(__('%1$s: %2$s'), __('By'), getUserName($val["users_id"])); $img = "rdv_public.png"; } $html.= " "; $html.= ""; switch ($type) { case "in" : //TRANS: %1$s is the start time of a planned item, %2$s is the end $beginend = sprintf(__('From %1$s to %2$s'), date("H:i", strtotime($val["begin"])), date("H:i", strtotime($val["end"]))); $html.= sprintf(__('%1$s: %2$s'), $beginend, Html::resume_text($val["name"], 80)); break; case "through" : $html.= Html::resume_text($val["name"], 80); break; case "begin" : $start = sprintf(__('Start at %s'), date("H:i", strtotime($val["begin"]))); $html.= sprintf(__('%1$s: %2$s'), $start, Html::resume_text($val["name"], 80)); break; case "end" : $end = sprintf(__('End at %s'), date("H:i", strtotime($val["end"]))); $html.= sprintf(__('%1$s: %2$s'), $end, Html::resume_text($val["name"], 80)); break; } $html.= $users_id; $html.= ""; $html.= "
"; $html.= sprintf(__('%1$s: %2$s'), __('Percent done'), $val["status"]."%"); $html.= "
"; $html.= "
".html_entity_decode($val["content"])."
"; return $html; } /** * Update the specified project task's percent_done based on the percent_done of sub-tasks. * This function indirectly updates the percent done for all parent tasks if they are set to automatically update. * The parent project's percent_done is not updated here to avoid duplicate updates. * @since 9.5.0 * @return boolean False if the specified project task is not set to automatically update the percent done. */ public static function recalculatePercentDone($ID) { global $DB; $projecttask = new self(); $projecttask->getFromDB($ID); if (!$projecttask->fields['auto_percent_done']) { return false; } $iterator = $DB->request([ 'SELECT' => [ new QueryExpression('CAST(AVG('.$DB->quoteName('percent_done').') AS UNSIGNED) AS percent_done') ], 'FROM' => ProjectTask::getTable(), 'WHERE' => [ 'projecttasks_id' => $ID ] ]); if ($iterator->count()) { $percent_done = $iterator->next()['percent_done']; } else { $percent_done = 0; } $projecttask->update([ 'id' => $ID, 'percent_done' => $percent_done, ]); return true; } public static function getGroupItemsAsVCalendars($groups_id) { return self::getItemsAsVCalendars( [ ProjectTaskTeam::getTableField('itemtype') => Group::class, ProjectTaskTeam::getTableField('items_id') => $groups_id, ] ); } public static function getUserItemsAsVCalendars($users_id) { return self::getItemsAsVCalendars( [ ProjectTaskTeam::getTableField('itemtype') => User::class, ProjectTaskTeam::getTableField('items_id') => $users_id, ] ); } /** * Returns items as VCalendar objects. * * @param array $criteria * * @return \Sabre\VObject\Component\VCalendar[] */ private static function getItemsAsVCalendars(array $criteria) { global $DB; $query = [ 'FROM' => self::getTable(), 'INNER JOIN' => [ ProjectTaskTeam::getTable() => [ 'ON' => [ ProjectTaskTeam::getTable() => 'projecttasks_id', self::getTable() => 'id', ], ], ], 'WHERE' => $criteria, ]; $tasks_iterator = $DB->request($query); $vcalendars = []; foreach ($tasks_iterator as $task) { $item = new self(); $item->getFromResultSet($task); $vcalendar = $item->getAsVCalendar(); if (null !== $vcalendar) { $vcalendars[] = $vcalendar; } } return $vcalendars; } public function getAsVCalendar() { global $CFG_GLPI; if (!$this->canViewItem()) { return null; } $is_task = true; $is_planned = !empty($this->fields['plan_start_date']) && !empty($this->fields['plan_end_date']); $target_component = $this->getTargetCaldavComponent($is_planned, $is_task); if (null === $target_component) { return null; } $vcalendar = $this->getVCalendarForItem($this, $target_component); $fields = Html::entity_decode_deep($this->fields); $utc_tz = new \DateTimeZone('UTC'); $vcomp = $vcalendar->getBaseComponent(); if ('VTODO' === $target_component) { if ($is_planned) { $vcomp->DTSTART = (new \DateTime($fields['plan_start_date']))->setTimeZone($utc_tz); $vcomp->DUE = (new \DateTime($fields['plan_end_date']))->setTimeZone($utc_tz); } $vcomp->STATUS = 100 == $fields['percent_done'] ? 'COMPLETED' : 'NEEDS-ACTION'; $vcomp->{'PERCENT-COMPLETE'} = $fields['percent_done']; } else if ('VEVENT' === $target_component) { if ($is_planned) { $vcomp->DTSTART = (new \DateTime($fields['plan_start_date']))->setTimeZone($utc_tz); $vcomp->DTEND = (new \DateTime($fields['plan_end_date']))->setTimeZone($utc_tz); } } $vcomp->URL = $CFG_GLPI['url_base'] . Project::getFormURLWithID($fields['projects_id'], false); return $vcalendar; } public function getInputFromVCalendar(VCalendar $vcalendar) { $vtodo = $vcalendar->getBaseComponent(); if (null !== $vtodo->RRULE) { throw new UnexpectedValueException('RRULE not yet implemented for Project tasks'); } $input = $this->getCommonInputFromVcomponent($vtodo, $this->isNewItem()); if ($vtodo->DESCRIPTION instanceof FlatText) { // Description is not in HTML format $input['content'] = $vtodo->DESCRIPTION->getValue(); } if ($vtodo->{'PERCENT-COMPLETE'} instanceof IntegerValue) { $input['percent_done'] = $vtodo->{'PERCENT-COMPLETE'}->getValue(); } else if (array_key_exists('state', $input) && $input['state'] == \Planning::DONE) { // Consider task as done if status is DONE $input['percent_done'] = 100; } return $input; } public function prepareInputForClone($input) { $input['uuid'] = \Ramsey\Uuid\Uuid::uuid4(); return parent::prepareInputForClone($input); } }