. * --------------------------------------------------------------------- */ if (!defined('GLPI_ROOT')) { die("Sorry. You can't access this file directly"); } /** * @since 9.4.0 */ class ITILFollowup extends CommonDBChild { // From CommonDBTM public $auto_message_on_action = false; static $rightname = 'followup'; private $item = null; static public $log_history_add = Log::HISTORY_LOG_SIMPLE_MESSAGE; static public $log_history_update = Log::HISTORY_LOG_SIMPLE_MESSAGE; static public $log_history_delete = Log::HISTORY_LOG_SIMPLE_MESSAGE; const SEEPUBLIC = 1; const UPDATEMY = 2; const ADDMYTICKET = 4; const UPDATEALL = 1024; const ADDGROUPTICKET = 2048; const ADDALLTICKET = 4096; const SEEPRIVATE = 8192; static public $itemtype = 'itemtype'; static public $items_id = 'items_id'; function getItilObjectItemType() { return str_replace('Followup', '', $this->getType()); } static function getTypeName($nb = 0) { return _n('Followup', 'Followups', $nb); } /** * can read the parent ITIL Object ? * * @return boolean */ function canReadITILItem() { $itemtype = $this->getItilObjectItemType(); $item = new $itemtype(); if (!$item->can($this->getField($item->getForeignKeyField()), READ)) { return false; } return true; } static function canView() { return (Session::haveRightsOr(self::$rightname, [self::SEEPUBLIC, self::SEEPRIVATE]) || Session::haveRight('ticket', Ticket::OWN)) || Session::haveRight('ticket', READ) || Session::haveRight('change', READ) || Session::haveRight('problem', READ); } static function canCreate() { return Session::haveRight('change', UPDATE) || Session::haveRight('problem', UPDATE) || (Session::haveRightsOr(self::$rightname, [self::ADDALLTICKET, self::ADDMYTICKET, self::ADDGROUPTICKET]) || Session::haveRight('ticket', Ticket::OWN)); } function canViewItem() { $itilobject = new $this->fields['itemtype']; if (!$itilobject->can($this->getField('items_id'), READ)) { return false; } if (Session::haveRight(self::$rightname, self::SEEPRIVATE)) { return true; } if (!$this->fields['is_private'] && Session::haveRight(self::$rightname, self::SEEPUBLIC)) { return true; } if ($itilobject instanceof Ticket) { if ($this->fields["users_id"] === Session::getLoginUserID()) { return true; } } else { return Session::haveRight($itilobject::$rightname, READ); } return false; } function canCreateItem() { if (!isset($this->fields['itemtype']) || strlen($this->fields['itemtype']) == 0) { return false; } $itilobject = new $this->fields['itemtype']; if (!$itilobject->can($this->getField('items_id'), READ) // No validation for closed tickets || in_array($itilobject->fields['status'], $itilobject->getClosedStatusArray()) && !$itilobject->canReopen() ) { return false; } return $itilobject->canAddFollowups(); } function canPurgeItem() { $itilobject = new $this->fields['itemtype']; if (!$itilobject->can($this->getField('items_id'), READ)) { return false; } if (Session::haveRight(self::$rightname, PURGE)) { return true; } return false; } function canUpdateItem() { if (($this->fields["users_id"] != Session::getLoginUserID()) && !Session::haveRight(self::$rightname, self::UPDATEALL)) { return false; } $itilobject = new $this->fields['itemtype']; if (!$itilobject->can($this->getField('items_id'), READ)) { return false; } if ($this->fields["users_id"] === Session::getLoginUserID()) { if (!Session::haveRight(self::$rightname, self::UPDATEMY)) { return false; } return true; } // Only the technician return (Session::haveRight(self::$rightname, self::UPDATEALL) || $itilobject->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()) || (isset($_SESSION["glpigroups"]) && $itilobject->haveAGroup(CommonITILActor::ASSIGN, $_SESSION['glpigroups']))); } function post_getEmpty() { if (isset($_SESSION['glpifollowup_private']) && $_SESSION['glpifollowup_private']) { $this->fields['is_private'] = 1; } if (isset($_SESSION["glpiname"])) { $this->fields['requesttypes_id'] = RequestType::getDefault('followup'); } } function post_addItem() { global $CFG_GLPI; // Add screenshots if needed, without notification $this->input = $this->addFiles($this->input, [ 'force_update' => true, 'name' => 'content', 'content_field' => 'content', ]); // Add documents if needed, without notification $this->input = $this->addFiles($this->input, [ 'force_update' => true, ]); $donotif = !isset($this->input['_disablenotif']) && $CFG_GLPI["use_notifications"]; // Check if stats should be computed after this change $no_stat = isset($this->input['_do_not_compute_takeintoaccount']); $parentitem = $this->input['_job']; $parentitem->updateDateMod( $this->input["items_id"], $no_stat, $this->input["users_id"] ); if (isset($this->input["_close"]) && $this->input["_close"] && ($parentitem->fields["status"] == CommonITILObject::SOLVED)) { $update = [ 'id' => $parentitem->fields['id'], 'status' => CommonITILObject::CLOSED, 'closedate' => $_SESSION["glpi_currenttime"], '_accepted' => true, ]; // Use update method for history $this->input["_job"]->update($update); $donotif = false; // Done for ITILObject update (new status) } //manage reopening of ITILObject $reopened = false; if (!isset($this->input['_status'])) { $this->input['_status'] = $parentitem->fields["status"]; } // if reopen set (from followup form or mailcollector) // and status is reopenable and not changed in form if (isset($this->input["_reopen"]) && $this->input["_reopen"] && in_array($parentitem->fields["status"], $parentitem::getReopenableStatusArray()) && $this->input['_status'] == $parentitem->fields["status"]) { if (($parentitem->countUsers(CommonITILActor::ASSIGN) > 0) || ($parentitem->countGroups(CommonITILActor::ASSIGN) > 0) || ($parentitem->countSuppliers(CommonITILActor::ASSIGN) > 0)) { $update['status'] = CommonITILObject::ASSIGNED; } else { $update['status'] = CommonITILObject::INCOMING; } $update['id'] = $parentitem->fields['id']; // Use update method for history $parentitem->update($update); $reopened = true; } //change ITILObject status only if imput change if (!$reopened && $this->input['_status'] != $parentitem->fields['status']) { $update['status'] = $this->input['_status']; $update['id'] = $parentitem->fields['id']; // don't notify on ITILObject - update event $update['_disablenotif'] = true; // Use update method for history $parentitem->update($update); } if ($donotif) { $options = ['followup_id' => $this->fields["id"], 'is_private' => $this->fields['is_private']]; NotificationEvent::raiseEvent("add_followup", $parentitem, $options); } // Add log entry in the ITILObject $changes = [ 0, '', $this->fields['id'], ]; Log::history($this->getField('items_id'), get_class($parentitem), $changes, $this->getType(), Log::HISTORY_ADD_SUBITEM); } function post_deleteFromDB() { global $CFG_GLPI; $donotif = $CFG_GLPI["use_notifications"]; if (isset($this->input['_disablenotif'])) { $donotif = false; } $job = new $this->fields['itemtype']; $job->getFromDB($this->fields[self::$items_id]); $job->updateDateMod($this->fields[self::$items_id]); // Add log entry in the ITIL Object $changes = [ 0, '', $this->fields['id'], ]; Log::history($this->getField(self::$items_id), $this->fields['itemtype'], $changes, $this->getType(), Log::HISTORY_DELETE_SUBITEM); if ($donotif) { $options = ['followup_id' => $this->fields["id"], // Force is_private with data / not available 'is_private' => $this->fields['is_private']]; NotificationEvent::raiseEvent('delete_followup', $job, $options); } } function prepareInputForAdd($input) { $input["_job"] = new $input['itemtype'](); if (empty($input['content']) && !isset($input['add_close']) && !isset($input['add_reopen'])) { Session::addMessageAfterRedirect(__("You can't add a followup without description"), false, ERROR); return false; } if (!$input["_job"]->getFromDB($input["items_id"])) { return false; } $input['_close'] = 0; if (!isset($input["users_id"])) { $input["users_id"] = 0; if ($uid = Session::getLoginUserID()) { $input["users_id"] = $uid; } } // if ($input["_isadmin"] && $input["_type"]!="update") { if (isset($input["add_close"])) { $input['_close'] = 1; if (empty($input['content'])) { $input['content'] = __('Solution approved'); } } unset($input["add_close"]); if (!isset($input["is_private"])) { $input['is_private'] = 0; } if (isset($input["add_reopen"])) { if ($input["content"] == '') { if (isset($input["_add"])) { // Reopen using add form Session::addMessageAfterRedirect(__('If you want to reopen this item, you must specify a reason'), false, ERROR); } else { // Refuse solution Session::addMessageAfterRedirect(__('If you reject the solution, you must specify a reason'), false, ERROR); } return false; } $input['_reopen'] = 1; } unset($input["add_reopen"]); // } unset($input["add"]); $itemtype = $input['itemtype']; $input['timeline_position'] = $itemtype::getTimelinePosition($input["items_id"], $this->getType(), $input["users_id"]); if (!isset($input['date'])) { $input["date"] = $_SESSION["glpi_currenttime"]; } return $input; } function prepareInputForUpdate($input) { if (!isset($this->fields['itemtype'])) { return false; } $input["_job"] = new $this->fields['itemtype'](); if (!$input["_job"]->getFromDB($this->fields["items_id"])) { return false; } // update last editor if content change if (($uid = Session::getLoginUserID()) && isset($input['content']) && ($input['content'] != $this->fields['content'])) { $input["users_id_editor"] = $uid; } return $input; } function post_updateItem($history = 1) { global $CFG_GLPI; $job = new $this->fields['itemtype'](); if (!$job->getFromDB($this->fields['items_id'])) { return; } // Add screenshots if needed, without notification $this->input = $this->addFiles($this->input, [ 'force_update' => true, 'name' => 'content', 'content_field' => 'content', ]); // Add documents if needed, without notification $this->input = $this->addFiles($this->input, [ 'force_update' => true, ]); //Get user_id when not logged (from mailgate) $uid = Session::getLoginUserID(); if ($uid === false) { if (isset($this->fields['users_id_editor'])) { $uid = $this->fields['users_id_editor']; } else { $uid = $this->fields['users_id']; } } $job->updateDateMod($this->fields['items_id'], false, $uid); if (count($this->updates)) { if (!isset($this->input['_disablenotif']) && $CFG_GLPI["use_notifications"] && (in_array("content", $this->updates) || isset($this->input['_need_send_mail']))) { //FIXME: _need_send_mail does not seems to be used $options = ['followup_id' => $this->fields["id"], 'is_private' => $this->fields['is_private']]; NotificationEvent::raiseEvent("update_followup", $job, $options); } } // change ITIL Object status (from splitted button) if (isset($this->input['_status']) && ($this->input['_status'] != $this->input['_job']->fields['status'])) { $update = [ 'status' => $this->input['_status'], 'id' => $this->input['_job']->fields['id'], '_disablenotif' => true, ]; $this->input['_job']->update($update); } // Add log entry in the ITIL Object $changes = [ 0, '', $this->fields['id'], ]; Log::history($this->getField('items_id'), $this->fields['itemtype'], $changes, $this->getType(), Log::HISTORY_UPDATE_SUBITEM); } function post_getFromDB() { $this->item = new $this->fields['itemtype']; $this->item->getFromDB($this->fields['items_id']); } protected function computeFriendlyName() { if (isset($this->fields['requesttypes_id'])) { if ($this->fields['requesttypes_id']) { return Dropdown::getDropdownName('glpi_requesttypes', $this->fields['requesttypes_id']); } return $this->getTypeName(); } return ''; } function rawSearchOptions() { $tab = []; $tab[] = [ 'id' => 'common', 'name' => __('Characteristics') ]; $tab[] = [ 'id' => '1', 'table' => $this->getTable(), 'field' => 'content', 'name' => __('Description'), 'datatype' => 'text' ]; $tab[] = [ 'id' => '2', 'table' => 'glpi_requesttypes', 'field' => 'name', 'name' => RequestType::getTypeName(1), 'forcegroupby' => true, 'datatype' => 'dropdown' ]; $tab[] = [ 'id' => '3', 'table' => $this->getTable(), 'field' => 'date', 'name' => _n('Date', 'Dates', 1), 'datatype' => 'datetime' ]; $tab[] = [ 'id' => '4', 'table' => $this->getTable(), 'field' => 'is_private', 'name' => __('Private'), 'datatype' => 'bool' ]; $tab[] = [ 'id' => '5', 'table' => 'glpi_users', 'field' => 'name', 'name' => User::getTypeName(1), 'datatype' => 'dropdown', 'right' => 'all' ]; $tab[] = [ 'id' => '6', 'table' => $this->getTable(), 'field' => 'itemtype', 'name' => RequestType::getTypeName(1), 'datatype' => 'dropdown' ]; return $tab; } static function rawSearchOptionsToAdd($itemtype = null) { $tab = []; $tab[] = [ 'id' => 'followup', 'name' => _n('Followup', 'Followups', Session::getPluralNumber()) ]; $followup_condition = ''; if (!Session::haveRight('followup', self::SEEPRIVATE)) { $followup_condition = "AND (`NEWTABLE`.`is_private` = 0 OR `NEWTABLE`.`users_id` = '".Session::getLoginUserID()."')"; } $tab[] = [ 'id' => '25', 'table' => static::getTable(), 'field' => 'content', 'name' => __('Description'), 'forcegroupby' => true, 'splititems' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'itemtype_item', 'condition' => $followup_condition ], 'datatype' => 'text', 'htmltext' => true ]; $tab[] = [ 'id' => '36', 'table' => static::getTable(), 'field' => 'date', 'name' => _n('Date', 'Dates', 1), 'datatype' => 'datetime', 'massiveaction' => false, 'forcegroupby' => true, 'joinparams' => [ 'jointype' => 'itemtype_item', 'condition' => $followup_condition ] ]; $tab[] = [ 'id' => '27', 'table' => static::getTable(), 'field' => 'id', 'name' => _x('quantity', 'Number of followups'), 'forcegroupby' => true, 'usehaving' => true, 'datatype' => 'count', 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'itemtype_item', 'condition' =>$followup_condition ] ]; $tab[] = [ 'id' => '29', 'table' => 'glpi_requesttypes', 'field' => 'name', 'name' => RequestType::getTypeName(1), 'datatype' => 'dropdown', 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'beforejoin' => [ 'table' => static::getTable(), 'joinparams' => [ 'jointype' => 'itemtype_item', 'condition' => $followup_condition ] ] ] ]; $tab[] = [ 'id' => '91', 'table' => static::getTable(), 'field' => 'is_private', 'name' => __('Private followup'), 'datatype' => 'bool', 'forcegroupby' => true, 'splititems' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'itemtype_item', 'condition' => $followup_condition ] ]; $tab[] = [ 'id' => '93', 'table' => 'glpi_users', 'field' => 'name', 'name' => __('Writer'), 'datatype' => 'itemlink', 'right' => 'all', 'forcegroupby' => true, 'massiveaction' => false, 'joinparams' => [ 'beforejoin' => [ 'table' => static::getTable(), 'joinparams' => [ 'jointype' => 'itemtype_item', 'condition' => $followup_condition ] ] ] ]; return $tab; } /** * form for soluce's approbation * * @param CommonITILObject $itilobject */ function showApprobationForm($itilobject) { if (($itilobject->fields["status"] == CommonITILObject::SOLVED) && $itilobject->canApprove() && $itilobject->isAllowedStatus($itilobject->fields['status'], CommonITILObject::CLOSED)) { echo "