. * --------------------------------------------------------------------- */ use Glpi\Event; if (!defined('GLPI_ROOT')) { die("Sorry. You can't access this file directly"); } /** * Ticket Class **/ class Ticket extends CommonITILObject { // From CommonDBTM public $dohistory = true; static protected $forward_entity_to = ['TicketValidation', 'TicketCost']; // From CommonITIL public $userlinkclass = 'Ticket_User'; public $grouplinkclass = 'Group_Ticket'; public $supplierlinkclass = 'Supplier_Ticket'; static $rightname = 'ticket'; protected $userentity_oncreate = true; const MATRIX_FIELD = 'priority_matrix'; const URGENCY_MASK_FIELD = 'urgency_mask'; const IMPACT_MASK_FIELD = 'impact_mask'; const STATUS_MATRIX_FIELD = 'ticket_status'; // HELPDESK LINK HARDWARE DEFINITION : CHECKSUM SYSTEM : BOTH=1*2^0+1*2^1=3 const HELPDESK_MY_HARDWARE = 0; const HELPDESK_ALL_HARDWARE = 1; // Specific ones /// Hardware datas used by getFromDBwithData public $hardwaredatas = []; /// Is a hardware found in getHardwareData / getFromDBwithData : hardware link to the job public $computerfound = 0; // Request type const INCIDENT_TYPE = 1; // Demand type const DEMAND_TYPE = 2; const READMY = 1; const READALL = 1024; const READGROUP = 2048; const READASSIGN = 4096; const ASSIGN = 8192; const STEAL = 16384; const OWN = 32768; const CHANGEPRIORITY = 65536; const SURVEY = 131072; function getForbiddenStandardMassiveAction() { $forbidden = parent::getForbiddenStandardMassiveAction(); if (!Session::haveRightsOr(self::$rightname, [DELETE, PURGE])) { $forbidden[] = 'delete'; $forbidden[] = 'purge'; $forbidden[] = 'restore'; } return $forbidden; } /** * Name of the type * * @param $nb : number of item in the type (default 0) **/ static function getTypeName($nb = 0) { return _n('Ticket', 'Tickets', $nb); } /** * @see CommonGLPI::getMenuShorcut() * * @since 0.85 **/ static function getMenuShorcut() { return 't'; } /** * @see CommonGLPI::getAdditionalMenuContent() * * @since 0.85 **/ static function getAdditionalMenuContent() { if (static::canCreate()) { $menu = [ 'create_ticket' => [ 'title' => __('Create ticket'), 'page' => static::getFormURL(false), 'icon' => 'fas fa-plus', ], ]; return $menu; } else { return self::getAdditionalMenuOptions(); } } /** * @see CommonGLPI::getAdditionalMenuLinks() * * @since 0.85 **/ static function getAdditionalMenuLinks() { global $CFG_GLPI; $links = parent::getAdditionalMenuLinks(); if (Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights())) { $opt = []; $opt['reset'] = 'reset'; $opt['criteria'][0]['field'] = 55; // validation status $opt['criteria'][0]['searchtype'] = 'equals'; $opt['criteria'][0]['value'] = CommonITILValidation::WAITING; $opt['criteria'][0]['link'] = 'AND'; $opt['criteria'][1]['field'] = 59; // validation aprobator $opt['criteria'][1]['searchtype'] = 'equals'; $opt['criteria'][1]['value'] = Session::getLoginUserID(); $opt['criteria'][1]['link'] = 'AND'; $opt['criteria'][2]['field'] = 52; // global validation status $opt['criteria'][2]['searchtype'] = 'equals'; $opt['criteria'][2]['value'] = CommonITILValidation::WAITING; $opt['criteria'][2]['link'] = 'AND'; $opt['criteria'][3]['field'] = 12; // ticket status $opt['criteria'][3]['searchtype'] = 'equals'; $opt['criteria'][3]['value'] = Ticket::CLOSED; $opt['criteria'][3]['link'] = 'AND NOT'; $opt['criteria'][4]['field'] = 12; // ticket status $opt['criteria'][4]['searchtype'] = 'equals'; $opt['criteria'][4]['value'] = Ticket::SOLVED; $opt['criteria'][4]['link'] = 'AND NOT'; $pic_validate = ""; $links[$pic_validate] = Ticket::getSearchURL(false) . '?'.Toolbox::append_params($opt, '&'); } return $links; } function canAssign() { if (isset($this->fields['is_deleted']) && ($this->fields['is_deleted'] == 1) || isset($this->fields['status']) && in_array($this->fields['status'], $this->getClosedStatusArray()) ) { return false; } return Session::haveRight(static::$rightname, self::ASSIGN); } function canAssignToMe() { if (isset($this->fields['is_deleted']) && $this->fields['is_deleted'] == 1 || isset($this->fields['status']) && in_array($this->fields['status'], $this->getClosedStatusArray()) ) { return false; } return (Session::haveRight(self::$rightname, self::STEAL) || (Session::haveRight(self::$rightname, self::OWN) && ($this->countUsers(CommonITILActor::ASSIGN) == 0))); } static function canUpdate() { // To allow update of urgency and category for post-only if (Session::getCurrentInterface() == "helpdesk") { return Session::haveRight(self::$rightname, CREATE); } return Session::haveRightsOr(self::$rightname, [UPDATE, self::ASSIGN, self::STEAL, self::OWN, self::CHANGEPRIORITY]); } static function canView() { return (Session::haveRightsOr(self::$rightname, [self::READALL, self::READMY, UPDATE, self::READASSIGN, self::READGROUP, self::OWN]) || Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights())); } /** * Is the current user have right to show the current ticket ? * * @return boolean **/ function canViewItem() { if (!Session::haveAccessToEntity($this->getEntityID())) { return false; } return (Session::haveRight(self::$rightname, self::READALL) || (Session::haveRight(self::$rightname, self::READMY) && (($this->fields["users_id_recipient"] === Session::getLoginUserID()) || $this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) || $this->isUser(CommonITILActor::OBSERVER, Session::getLoginUserID()))) || (Session::haveRight(self::$rightname, self::READGROUP) && isset($_SESSION["glpigroups"]) && ($this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION["glpigroups"]) || $this->haveAGroup(CommonITILActor::OBSERVER, $_SESSION["glpigroups"]))) || (Session::haveRight(self::$rightname, self::READASSIGN) && ($this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()) || (isset($_SESSION["glpigroups"]) && $this->haveAGroup(CommonITILActor::ASSIGN, $_SESSION["glpigroups"])) || (Session::haveRight(self::$rightname, self::ASSIGN) && ($this->fields["status"] == self::INCOMING)))) || (Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights()) && TicketValidation::canValidate($this->fields["id"]))); } /** * Is the current user have right to approve solution of the current ticket ? * * @return boolean **/ function canApprove() { return ((($this->fields["users_id_recipient"] === Session::getLoginUserID()) && Session::haveRight('ticket', Ticket::SURVEY)) || $this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) || (isset($_SESSION["glpigroups"]) && $this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION["glpigroups"]))); } /** * @see CommonDBTM::canMassiveAction() **/ function canMassiveAction($action, $field, $value) { switch ($action) { case 'update' : switch ($field) { case 'status' : if (!self::isAllowedStatus($this->fields['status'], $value)) { return false; } break; } break; } return true; } /** * Check if current user can take into account the ticket. * * @return boolean */ public function canTakeIntoAccount() { // Can take into account if user is assigned user if ($this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()) || (isset($_SESSION["glpigroups"]) && $this->haveAGroup(CommonITILActor::ASSIGN, $_SESSION['glpigroups']))) { return true; } // Cannot take into account if user is a requester (and not assigned) if ($this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) || (isset($_SESSION["glpigroups"]) && $this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION['glpigroups']))) { return false; } $canAddTask = Session::haveRight("task", CommonITILTask::ADDALLITEM); $canAddFollowup = Session::haveRightsOr( 'followup', [ ITILFollowup::ADDALLTICKET, ITILFollowup::ADDMYTICKET, ITILFollowup::ADDGROUPTICKET, ] ); // Can take into account if user has rights to add tasks or followups, // assuming that users that does not have those rights cannot treat the ticket. return $canAddTask || $canAddFollowup; } /** * Check if ticket has already been taken into account. * * @return boolean */ public function isAlreadyTakenIntoAccount() { return array_key_exists('takeintoaccount_delay_stat', $this->fields) && $this->fields['takeintoaccount_delay_stat'] != 0; } /** * Get Datas to be added for SLA add * * @param $slas_id SLA id * @param $entities_id entity ID of the ticket * @param $date begin date of the ticket * @param $type type of SLA * * @since 9.1 (before getDatasToAddSla without type parameter) * * @return array of datas to add in ticket **/ function getDatasToAddSLA($slas_id, $entities_id, $date, $type) { list($dateField, $slaField) = SLA::getFieldNames($type); $calendars_id = Entity::getUsedConfig('calendars_id', $entities_id); $data = []; $sla = new SLA(); if ($sla->getFromDB($slas_id)) { $sla->setTicketCalendar($calendars_id); if ($sla->fields['type'] == SLM::TTR) { $data["slalevels_id_ttr"] = SlaLevel::getFirstSlaLevel($slas_id); } // Compute time_to_resolve $data[$dateField] = $sla->computeDate($date); $data['sla_waiting_duration'] = 0; } else { $data["slalevels_id_ttr"] = 0; $data[$slaField] = 0; $data['sla_waiting_duration'] = 0; } return $data; } /** * Get Datas to be added for OLA add * * @param $olas_id OLA id * @param $entities_id entity ID of the ticket * @param $date begin date of the ticket * @param $type type of OLA * * @since 9.2 (before getDatasToAddOla without type parameter) * * @return array of datas to add in ticket **/ function getDatasToAddOLA($olas_id, $entities_id, $date, $type) { list($dateField, $olaField) = OLA::getFieldNames($type); $calendars_id = Entity::getUsedConfig('calendars_id', $entities_id); $data = []; $ola = new OLA(); if ($ola->getFromDB($olas_id)) { $ola->setTicketCalendar($calendars_id); if ($ola->fields['type'] == SLM::TTR) { $data["olalevels_id_ttr"] = OlaLevel::getFirstOlaLevel($olas_id); $data['ola_ttr_begin_date'] = $date; } // Compute time_to_resolve $data[$dateField] = $ola->computeDate($date); $data['ola_waiting_duration'] = 0; } else { $data["olalevels_id_ttr"] = 0; $data[$olaField] = 0; $data['ola_waiting_duration'] = 0; } return $data; } /** * Delete Level Agreement for the ticket * * @since 9.2 * * @param string $laType (SLA | OLA) * @param integer $id the sla/ola id * @param integer $subtype (SLM::TTR | SLM::TTO) * @param bool $delete_date (default false) * * @return bool **/ function deleteLevelAgreement($laType, $la_id, $subtype, $delete_date = false) { switch ($laType) { case "SLA": $prefix = "sla"; $prefix_ticket = ""; $level_ticket = new SlaLevel_Ticket(); break; case "OLA": $prefix = "ola"; $prefix_ticket = "internal_"; $level_ticket = new OlaLevel_Ticket(); break; } $input = []; switch ($subtype) { case SLM::TTR : $input[$prefix.'s_id_ttr'] = 0; if ($delete_date) { $input[$prefix_ticket.'time_to_resolve'] = ''; } break; case SLM::TTO : $input[$prefix.'s_id_tto'] = 0; if ($delete_date) { $input[$prefix_ticket.'time_to_own'] = ''; } break; } $input[$prefix.'_waiting_duration'] = 0; $input['id'] = $la_id; $level_ticket->deleteForTicket($la_id, $subtype); return $this->update($input); } /** * Is the current user have right to create the current ticket ? * * @return boolean **/ function canCreateItem() { if (!Session::haveAccessToEntity($this->getEntityID())) { return false; } return self::canCreate(); } /** * Is the current user have right to update the current ticket ? * * @return boolean **/ function canUpdateItem() { if (!$this->checkEntity()) { return false; } // for all, if no modification in ticket return true if ($can_requester = $this->canRequesterUpdateItem()) { return true; } // for self-service only, if modification in ticket, we can't update the ticket if (Session::getCurrentInterface() == "helpdesk" && !$can_requester) { return false; } // if we don't have global UPDATE right, maybe we can own the current ticket if (!Session::haveRight(self::$rightname, UPDATE) && !$this->ownItem()) { //we always return false, as ownItem() = true is managed by below self::canUpdate return false; } return self::canupdate(); } /** * Is the current user is a requester of the current ticket and have the right to update it ? * * @return boolean */ function canRequesterUpdateItem() { return ($this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) || $this->fields["users_id_recipient"] === Session::getLoginUserID()) && $this->fields['status'] != self::SOLVED && $this->fields['status'] != self::CLOSED && $this->numberOfFollowups() == 0 && $this->numberOfTasks() == 0; } /** * Is the current user have OWN right and is the assigned to the ticket * * @return boolean */ function ownItem() { return Session::haveRight(self::$rightname, self::OWN) && $this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()); } /** * @since 0.85 **/ static function canDelete() { // to allow delete for self-service only if no action on the ticket if (Session::getCurrentInterface() == "helpdesk") { return Session::haveRight(self::$rightname, CREATE); } return Session::haveRight(self::$rightname, DELETE); } /** * is the current user could reopen the current ticket * @since 9.2 * @return boolean */ function canReopen() { return Session::haveRight('followup', CREATE) && in_array($this->fields["status"], $this->getClosedStatusArray()) && ($this->isAllowedStatus($this->fields['status'], self::INCOMING) || $this->isAllowedStatus($this->fields['status'], self::ASSIGNED)); } /** * Is the current user have right to delete the current ticket ? * * @return boolean **/ function canDeleteItem() { if (!Session::haveAccessToEntity($this->getEntityID())) { return false; } // user can delete his ticket if no action on it if (Session::getCurrentInterface() == "helpdesk" && (!($this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) || $this->fields["users_id_recipient"] === Session::getLoginUserID()) || $this->numberOfFollowups() > 0 || $this->numberOfTasks() > 0 || $this->fields["date"] != $this->fields["date_mod"])) { return false; } return static::canDelete(); } /** * @see CommonITILObject::getDefaultActor() **/ function getDefaultActor($type) { if ($type == CommonITILActor::ASSIGN) { if (Session::haveRight(self::$rightname, self::OWN) && $_SESSION['glpiset_default_tech']) { return Session::getLoginUserID(); } } if ($type == CommonITILActor::REQUESTER) { if (Session::haveRight(self::$rightname, CREATE) && $_SESSION['glpiset_default_requester']) { return Session::getLoginUserID(); } } return 0; } /** * @see CommonITILObject::getDefaultActorRightSearch() **/ function getDefaultActorRightSearch($type) { $right = "all"; if ($type == CommonITILActor::ASSIGN) { $right = "own_ticket"; if (!Session::haveRight(self::$rightname, self::ASSIGN)) { $right = 'id'; } } return $right; } function pre_deleteItem() { global $CFG_GLPI; if (!isset($this->input['_disablenotif']) && $CFG_GLPI['use_notifications']) { NotificationEvent::raiseEvent('delete', $this); } return true; } function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { if (static::canView()) { $nb = 0; $title = self::getTypeName(Session::getPluralNumber()); if ($_SESSION['glpishow_count_on_tabs']) { switch ($item->getType()) { case 'User' : $nb = countElementsInTable( ['glpi_tickets', 'glpi_tickets_users'], [ 'glpi_tickets_users.tickets_id' => new \QueryExpression(DB::quoteName('glpi_tickets.id')), 'glpi_tickets_users.users_id' => $item->getID(), 'glpi_tickets_users.type' => CommonITILActor::REQUESTER ] + getEntitiesRestrictCriteria(self::getTable()) ); $title = __('Created tickets'); break; case 'Supplier' : $nb = countElementsInTable( ['glpi_tickets', 'glpi_suppliers_tickets'], [ 'glpi_suppliers_tickets.tickets_id' => new \QueryExpression(DB::quoteName('glpi_tickets.id')), 'glpi_suppliers_tickets.suppliers_id' => $item->getID() ] + getEntitiesRestrictCriteria(self::getTable()) ); break; case 'SLA' : $nb = countElementsInTable( 'glpi_tickets', [ 'OR' => [ 'slas_id_tto' => $item->getID(), 'slas_id_ttr' => $item->getID() ] ] ); break; case 'OLA' : $nb = countElementsInTable( 'glpi_tickets', [ 'OR' => [ 'olas_id_tto' => $item->getID(), 'olas_id_ttr' => $item->getID() ] ] ); break; case 'Group' : $nb = countElementsInTable( ['glpi_tickets', 'glpi_groups_tickets'], [ 'glpi_groups_tickets.tickets_id' => new \QueryExpression(DB::quoteName('glpi_tickets.id')), 'glpi_groups_tickets.groups_id' => $item->getID(), 'glpi_groups_tickets.type' => CommonITILActor::REQUESTER ] + getEntitiesRestrictCriteria(self::getTable()) ); $title = __('Created tickets'); break; default : // Direct one $nb = countElementsInTable( 'glpi_items_tickets', [ 'INNER JOIN' => [ 'glpi_tickets' => [ 'FKEY' => [ 'glpi_items_tickets' => 'tickets_id', 'glpi_tickets' => 'id' ] ] ], 'WHERE' => [ 'itemtype' => $item->getType(), 'items_id' => $item->getID(), 'is_deleted' => 0 ] ] ); // Linked items $linkeditems = $item->getLinkedItems(); if (count($linkeditems)) { foreach ($linkeditems as $type => $tab) { foreach ($tab as $ID) { $nb += countElementsInTable( 'glpi_items_tickets', [ 'INNER JOIN' => [ 'glpi_tickets' => [ 'FKEY' => [ 'glpi_items_tickets' => 'tickets_id', 'glpi_tickets' => 'id' ] ] ], 'WHERE' => [ 'itemtype' => $type, 'items_id' => $ID, 'is_deleted' => 0 ] ] ); } } } break; } } // glpishow_count_on_tabs // Not for Ticket class if ($item->getType() != __CLASS__) { return self::createTabEntry($title, $nb); } } // self::READALL right check // Not check self::READALL for Ticket itself switch ($item->getType()) { case __CLASS__ : $ong = []; $timeline = $item->getTimelineItems(); $nb_elements = count($timeline); $ong[1] = __("Processing ticket")." $nb_elements"; // enquete si statut clos $satisfaction = new TicketSatisfaction(); if ($satisfaction->getFromDB($item->getID()) && $item->fields['status'] == self::CLOSED) { $ong[3] = __('Satisfaction'); } if ($item->canView()) { $ong[4] = __('Statistics'); } return $ong; // default : // return _n('Ticket','Tickets', Session::getPluralNumber()); } return ''; } static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { switch ($item->getType()) { case __CLASS__ : switch ($tabnum) { case 1 : echo "
".__('Satisfaction survey expired')."
"; } } else { echo "".__('No generated survey')."
"; } break; case 4 : $item->showStats(); break; } break; case 'Group' : case 'SLA' : case 'OLA' : default : self::showListForItem($item, $withtemplate); } return true; } function defineTabs($options = []) { $ong = []; $this->defineDefaultObjectTabs($ong, $options); $this->addStandardTab('TicketValidation', $ong, $options); $this->addStandardTab('KnowbaseItem_Item', $ong, $options); $this->addStandardTab('Item_Ticket', $ong, $options); if ($this->hasImpactTab()) { $this->addStandardTab('Impact', $ong, $options); } $this->addStandardTab('TicketCost', $ong, $options); $this->addStandardTab('Itil_Project', $ong, $options); $this->addStandardTab('ProjectTask_Ticket', $ong, $options); $this->addStandardTab('Problem_Ticket', $ong, $options); $this->addStandardTab('Change_Ticket', $ong, $options); $entity = $this->getEntityID(); if (!(Entity::getUsedConfig('anonymize_support_agents', $entity) && Session::getCurrentInterface() == 'helpdesk') ) { $this->addStandardTab('Log', $ong, $options); } return $ong; } /** * Retrieve data of the hardware linked to the ticket if exists * * @return void **/ function getAdditionalDatas() { $this->hardwaredatas = []; if (!empty($this->fields["id"])) { $item_ticket = new Item_Ticket(); $data = $item_ticket->find(['tickets_id' => $this->fields["id"]]); foreach ($data as $val) { if (!empty($val["itemtype"]) && ($item = getItemForItemtype($val["itemtype"]))) { if ($item->getFromDB($val["items_id"])) { $this->hardwaredatas[] = $item; } } } } } function cleanDBonPurge() { // OlaLevel_Ticket does not extends CommonDBConnexity $olaLevel_ticket = new OlaLevel_Ticket(); $olaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTO); $olaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTR); // SlaLevel_Ticket does not extends CommonDBConnexity $slaLevel_ticket = new SlaLevel_Ticket(); $slaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTO); $slaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTR); // TicketSatisfaction does not extends CommonDBConnexity $tf = new TicketSatisfaction(); $tf->deleteByCriteria(['tickets_id' => $this->fields['id']]); // CommonITILTask does not extends CommonDBConnexity $tt = new TicketTask(); $tt->deleteByCriteria(['tickets_id' => $this->fields['id']]); $this->deleteChildrenAndRelationsFromDb( [ Change_Ticket::class, Item_Ticket::class, Problem_Ticket::class, ProjectTask_Ticket::class, TicketCost::class, Ticket_Ticket::class, TicketValidation::class, ] ); parent::cleanDBonPurge(); } function prepareInputForUpdate($input) { global $DB; // Get ticket : need for comparison $this->getFromDB($input['id']); // Clean new lines before passing to rules if (isset($input["content"])) { $input["content"] = preg_replace('/\\\\r\\\\n/', "\n", $input['content']); $input["content"] = preg_replace('/\\\\n/', "\n", $input['content']); } // automatic recalculate if user changes urgence or technician change impact $canpriority = Session::haveRight(self::$rightname, self::CHANGEPRIORITY); if ((isset($input['urgency']) && $input['urgency'] != $this->fields['urgency']) || (isset($input['impact']) && $input['impact'] != $this->fields['impact']) && ($canpriority && !isset($input['priority']) || !$canpriority) ) { if (!isset($input['urgency'])) { $input['urgency'] = $this->fields['urgency']; } if (!isset($input['impact'])) { $input['impact'] = $this->fields['impact']; } $input['priority'] = self::computePriority($input['urgency'], $input['impact']); } // Security checks if (!Session::isCron() && !Session::haveRight(self::$rightname, self::ASSIGN)) { if (isset($input["_itil_assign"]) && isset($input['_itil_assign']['_type']) && ($input['_itil_assign']['_type'] == 'user')) { // must own_ticket to grab a non assign ticket if ($this->countUsers(CommonITILActor::ASSIGN) == 0) { if ((!Session::haveRightsOr(self::$rightname, [self::STEAL, self::OWN])) || !isset($input["_itil_assign"]['users_id']) || ($input["_itil_assign"]['users_id'] != Session::getLoginUserID())) { unset($input["_itil_assign"]); } } else { // Can not steal or can steal and not assign to me if (!Session::haveRight(self::$rightname, self::STEAL) || !isset($input["_itil_assign"]['users_id']) || ($input["_itil_assign"]['users_id'] != Session::getLoginUserID())) { unset($input["_itil_assign"]); } } } // No supplier assign if (isset($input["_itil_assign"]) && isset($input['_itil_assign']['_type']) && ($input['_itil_assign']['_type'] == 'supplier')) { unset($input["_itil_assign"]); } // No group if (isset($input["_itil_assign"]) && isset($input['_itil_assign']['_type']) && ($input['_itil_assign']['_type'] == 'group')) { unset($input["_itil_assign"]); } } //must be handled here for tickets. @see CommonITILObject::prepareInputForUpdate() $input = $this->handleTemplateFields($input); if ($input === false) { return false; } if (isset($input['entities_id'])) { $entid = $input['entities_id']; } else { $entid = $this->fields['entities_id']; } // Process Business Rules $this->fillInputForBusinessRules($input); // Add actors on standard input $rules = new RuleTicketCollection($entid); $rule = $rules->getRuleClass(); $changes = []; $post_added = []; $tocleanafterrules = []; $usertypes = [ CommonITILActor::ASSIGN => 'assign', CommonITILActor::REQUESTER => 'requester', CommonITILActor::OBSERVER => 'observer' ]; foreach ($usertypes as $k => $t) { //handle new input if (isset($input['_itil_'.$t]) && isset($input['_itil_'.$t]['_type'])) { $field = $input['_itil_'.$t]['_type'].'s_id'; if (isset($input['_itil_'.$t][$field]) && !isset($input[$field.'_'.$t])) { $input['_'.$field.'_'.$t][] = $input['_itil_'.$t][$field]; $tocleanafterrules['_'.$field.'_'.$t][] = $input['_itil_'.$t][$field]; } } //handle existing actors: load all existing actors from ticket //to make sure business rules will receive all informations, and not just //what have been entered in the html form. // //ref also this actor into $post_added to avoid the filling of $changes //and triggering businness rules when not needed $users = $this->getUsers($k); if (count($users)) { $field = 'users_id'; foreach ($users as $user) { if (!isset($input['_'.$field.'_'.$t]) || !in_array($user[$field], $input['_'.$field.'_'.$t])) { if (!isset($input['_'.$field.'_'.$t])) { $post_added['_'.$field.'_'.$t] = '_'.$field.'_'.$t; } $input['_'.$field.'_'.$t][] = $user[$field]; $tocleanafterrules['_'.$field.'_'.$t][] = $user[$field]; } } } $groups = $this->getGroups($k); if (count($groups)) { $field = 'groups_id'; foreach ($groups as $group) { if (!isset($input['_'.$field.'_'.$t]) || !in_array($group[$field], $input['_'.$field.'_'.$t])) { if (!isset($input['_'.$field.'_'.$t])) { $post_added['_'.$field.'_'.$t] = '_'.$field.'_'.$t; } $input['_'.$field.'_'.$t][] = $group[$field]; $tocleanafterrules['_'.$field.'_'.$t][] = $group[$field]; } } } $suppliers = $this->getSuppliers($k); if (count($suppliers)) { $field = 'suppliers_id'; foreach ($suppliers as $supplier) { if (!isset($input['_'.$field.'_'.$t]) || !in_array($supplier[$field], $input['_'.$field.'_'.$t])) { if (!isset($input['_'.$field.'_'.$t])) { $post_added['_'.$field.'_'.$t] = '_'.$field.'_'.$t; } $input['_'.$field.'_'.$t][] = $supplier[$field]; $tocleanafterrules['_'.$field.'_'.$t][] = $supplier[$field]; } } } } foreach ($rule->getCriterias() as $key => $val) { if (array_key_exists($key, $input) && !array_key_exists($key, $post_added)) { if (!isset($this->fields[$key]) || ($DB->escape($this->fields[$key]) != $input[$key])) { $changes[] = $key; } } } // Business Rules do not override manual SLA and OLA $manual_slas_id = []; $manual_olas_id = []; foreach ([SLM::TTR, SLM::TTO] as $slmType) { list($dateField, $slaField) = SLA::getFieldNames($slmType); if (isset($input[$slaField]) && ($input[$slaField] > 0)) { $manual_slas_id[$slmType] = $input[$slaField]; } list($dateField, $olaField) = OLA::getFieldNames($slmType); if (isset($input[$olaField]) && ($input[$olaField] > 0)) { $manual_olas_id[$slmType] = $input[$olaField]; } } // Only process rules on changes if (count($changes)) { if (in_array('_users_id_requester', $changes)) { // If _users_id_requester changed : set users_locations $user = new User(); if (isset($input["_itil_requester"]["users_id"]) && $user->getFromDB($input["_itil_requester"]["users_id"])) { $input['users_locations'] = $user->fields['locations_id']; $changes[] = 'users_locations'; } // If _users_id_requester changed : add _groups_id_of_requester to changes $changes[] = '_groups_id_of_requester'; } $input = $rules->processAllRules($input, $input, ['recursive' => true, 'entities_id' => $entid], ['condition' => RuleTicket::ONUPDATE, 'only_criteria' => $changes]); $input = Toolbox::stripslashes_deep($input); } // Clean actors fields added for rules foreach ($tocleanafterrules as $key => $val) { if ($input[$key] == $val) { unset($input[$key]); } } // Manage fields from auto update or rules : map rule actions to standard additional ones $usertypes = ['assign', 'requester', 'observer']; $actortypes = ['user','group','supplier']; foreach ($usertypes as $t) { foreach ($actortypes as $a) { if (isset($input['_'.$a.'s_id_'.$t])) { switch ($a) { case 'user' : $additionalfield = '_additional_'.$t.'s'; $input[$additionalfield][] = ['users_id' => $input['_'.$a.'s_id_'.$t]]; break; default : $additionalfield = '_additional_'.$a.'s_'.$t.'s'; $input[$additionalfield][] = $input['_'.$a.'s_id_'.$t]; break; } } } } if (isset($input['_link'])) { $ticket_ticket = new Ticket_Ticket(); if (!empty($input['_link']['tickets_id_2'])) { if ($ticket_ticket->can(-1, CREATE, $input['_link'])) { if ($ticket_ticket->add($input['_link'])) { $input['_forcenotif'] = true; } } else { Session::addMessageAfterRedirect(__('Unknown ticket'), false, ERROR); } } } // SLA / OLA affect by rules : reset time_to_resolve / internal_time_to_resolve // Manual SLA / OLA defined : reset time_to_resolve / internal_time_to_resolve // No manual SLA / OLA and due date defined : reset auto SLA / OLA foreach ([SLM::TTR, SLM::TTO] as $slmType) { $this->slaAffect($slmType, $input, $manual_slas_id); $this->olaAffect($slmType, $input, $manual_olas_id); } if (isset($input['content'])) { if (isset($input['_filename']) || isset($input['_content'])) { $input['_disablenotif'] = true; } else { $input['_donotadddocs'] = true; } } $input = parent::prepareInputForUpdate($input); return $input; } /** * SLA affect by rules : reset time_to_resolve and time_to_own * Manual SLA defined : reset time_to_resolve and time_to_own * No manual SLA and due date defined : reset auto SLA * * @since 9.1 * * @param $type * @param $input * @param $manual_slas_id */ function slaAffect($type, &$input, $manual_slas_id) { list($dateField, $slaField) = SLA::getFieldNames($type); // Restore slas if (isset($manual_slas_id[$type]) && !isset($input['_'.$slaField])) { $input[$slaField] = $manual_slas_id[$type]; } // Ticket update if (isset($this->fields['id']) && $this->fields['id'] > 0) { if (!isset($manual_slas_id[$type]) && isset($input[$slaField]) && ($input[$slaField] > 0) && ($input[$slaField] != $this->fields[$slaField])) { if (isset($input[$dateField])) { // Unset due date unset($input[$dateField]); } } if (isset($input[$slaField]) && ($input[$slaField] > 0) && ($input[$slaField] != $this->fields[$slaField])) { $date = $this->fields['date']; /// Use updated date if also done if (isset($input["date"])) { $date = $input["date"]; } // Get datas to initialize SLA and set it $sla_data = $this->getDatasToAddSLA($input[$slaField], $this->fields['entities_id'], $date, $type); if (count($sla_data)) { foreach ($sla_data as $key => $val) { $input[$key] = $val; } } } } else { // Ticket add if (!isset($manual_slas_id[$type]) && isset($input[$dateField]) && ($input[$dateField] != 'NULL')) { // Valid due date if ($input[$dateField] >= $input['date']) { if (isset($input[$slaField])) { unset($input[$slaField]); } } else { // Unset due date unset($input[$dateField]); } } if (isset($input[$slaField]) && ($input[$slaField] > 0)) { // Get datas to initialize SLA and set it $sla_data = $this->getDatasToAddSLA($input[$slaField], $input['entities_id'], $input['date'], $type); if (count($sla_data)) { foreach ($sla_data as $key => $val) { $input[$key] = $val; } } } } } /** * OLA affect by rules : reset internal_time_to_resolve and internal_time_to_own * Manual OLA defined : reset internal_time_to_resolve and internal_time_to_own * No manual OLA and due date defined : reset auto OLA * * @since 9.1 * * @param $type * @param $input * @param $manual_olas_id */ function olaAffect($type, &$input, $manual_olas_id) { list($dateField, $olaField) = OLA::getFieldNames($type); // Restore olas if (isset($manual_olas_id[$type]) && !isset($input['_'.$olaField])) { $input[$olaField] = $manual_olas_id[$type]; } // Ticket update if (isset($this->fields['id']) && $this->fields['id'] > 0) { if (!isset($manual_olas_id[$type]) && isset($input[$olaField]) && ($input[$olaField] > 0) && ($input[$olaField] != $this->fields[$olaField])) { if (isset($input[$dateField])) { // Unset due date unset($input[$dateField]); } } if (isset($input[$olaField]) && ($input[$olaField] > 0) && ($input[$olaField] != $this->fields[$olaField] || isset($input['_'.$olaField]))) { $date = date('Y-m-d H:i:s'); // Get datas to initialize OLA and set it $ola_data = $this->getDatasToAddOLA($input[$olaField], $this->fields['entities_id'], $date, $type); if (count($ola_data)) { foreach ($ola_data as $key => $val) { $input[$key] = $val; } } } } else { // Ticket add if (!isset($manual_olas_id[$type]) && isset($input[$dateField]) && ($input[$dateField] != 'NULL')) { // Valid due date if ($input[$dateField] >= $input['date']) { if (isset($input[$olaField])) { unset($input[$olaField]); } } else { // Unset due date unset($input[$dateField]); } } if (isset($input[$olaField]) && ($input[$olaField] > 0)) { // Get datas to initialize OLA and set it $ola_data = $this->getDatasToAddOLA($input[$olaField], $input['entities_id'], $input['date'], $type); if (count($ola_data)) { foreach ($ola_data as $key => $val) { $input[$key] = $val; } } } } } /** * Manage SLA level escalation * * @since 9.1 * * @param $slas_id **/ function manageSlaLevel($slas_id) { $calendars_id = Entity::getUsedConfig('calendars_id', $this->fields['entities_id']); // Add first level in working table $slalevels_id = SlaLevel::getFirstSlaLevel($slas_id); $sla = new SLA(); if ($sla->getFromDB($slas_id)) { $sla->setTicketCalendar($calendars_id); $sla->addLevelToDo($this, $slalevels_id); } SlaLevel_Ticket::replayForTicket($this->getID(), $sla->getField('type')); } /** * Manage OLA level escalation * * @since 9.1 * * @param $slas_id **/ function manageOlaLevel($slas_id) { $calendars_id = Entity::getUsedConfig('calendars_id', $this->fields['entities_id']); // Add first level in working table $olalevels_id = OlaLevel::getFirstOlaLevel($slas_id); $ola = new OLA(); if ($ola->getFromDB($slas_id)) { $ola->setTicketCalendar($calendars_id); $ola->addLevelToDo($this, $olalevels_id); } OlaLevel_Ticket::replayForTicket($this->getID(), $ola->getField('type')); } function pre_updateInDB() { if (!$this->isTakeIntoAccountComputationBlocked($this->input) && !$this->isAlreadyTakenIntoAccount() && $this->canTakeIntoAccount() && !$this->isNew() ) { $this->updates[] = "takeintoaccount_delay_stat"; $this->fields['takeintoaccount_delay_stat'] = $this->computeTakeIntoAccountDelayStat(); } parent::pre_updateInDB(); } /** * Compute take into account stat of the current ticket **/ function computeTakeIntoAccountDelayStat() { if (isset($this->fields['id']) && !empty($this->fields['date'])) { $calendars_id = $this->getCalendar(); $calendar = new Calendar(); // Using calendar if (($calendars_id > 0) && $calendar->getFromDB($calendars_id)) { return max(1, $calendar->getActiveTimeBetween($this->fields['date'], $_SESSION["glpi_currenttime"])); } // Not calendar defined return max(1, strtotime($_SESSION["glpi_currenttime"])-strtotime($this->fields['date'])); } return 0; } function post_updateItem($history = 1) { global $CFG_GLPI; parent::post_updateItem($history); //Action for send_validation rule : do validation before clean $this->manageValidationAdd($this->input); // Put same status on duplicated tickets when solving or closing (autoclose on solve) if (isset($this->input['status']) && in_array('status', $this->updates) && (in_array($this->input['status'], $this->getSolvedStatusArray()) || in_array($this->input['status'], $this->getClosedStatusArray()))) { Ticket_Ticket::manageLinkedTicketsOnSolved($this->getID()); } $donotif = count($this->updates); if (isset($this->input['_forcenotif'])) { $donotif = true; } // Manage SLA / OLA Level : add actions foreach ([SLM::TTR, SLM::TTO] as $slmType) { list($dateField, $slaField) = SLA::getFieldNames($slmType); if (in_array($slaField, $this->updates) && ($this->fields[$slaField] > 0)) { $this->manageSlaLevel($this->fields[$slaField]); } list($dateField, $olaField) = OLA::getFieldNames($slmType); if (in_array($olaField, $this->updates) && ($this->fields[$olaField] > 0)) { $this->manageOlaLevel($this->fields[$olaField]); } } if (count($this->updates)) { // Update Ticket Tco if (in_array("actiontime", $this->updates) || in_array("cost_time", $this->updates) || in_array("cost_fixed", $this->updates) || in_array("cost_material", $this->updates)) { if (!empty($this->input["items_id"])) { foreach ($this->input["items_id"] as $itemtype => $items) { foreach ($items as $items_id) { if ($itemtype && ($item = getItemForItemtype($itemtype))) { if ($item->getFromDB($items_id)) { $newinput = []; $newinput['id'] = $items_id; $newinput['ticket_tco'] = self::computeTco($item); $item->update($newinput); } } } } } } $donotif = true; } if (isset($this->input['_disablenotif'])) { $donotif = false; } if ($donotif && $CFG_GLPI["use_notifications"]) { $mailtype = "update"; if (isset($this->input["status"]) && $this->input["status"] && in_array("status", $this->updates) && in_array($this->input["status"], $this->getSolvedStatusArray())) { $mailtype = "solved"; } if (isset($this->input["status"]) && $this->input["status"] && in_array("status", $this->updates) && in_array($this->input["status"], $this->getClosedStatusArray())) { $mailtype = "closed"; } // to know if a solution is approved or not if ((isset($this->input['solvedate']) && ($this->input['solvedate'] == 'NULL') && isset($this->oldvalues['solvedate']) && $this->oldvalues['solvedate']) && (isset($this->input['status']) && ($this->input['status'] != $this->oldvalues['status']) && ($this->oldvalues['status'] == self::SOLVED))) { $mailtype = "rejectsolution"; } // Read again ticket to be sure that all data are up to date $this->getFromDB($this->fields['id']); NotificationEvent::raiseEvent($mailtype, $this); } // inquest created immediatly if delay = O $inquest = new TicketSatisfaction(); $rate = Entity::getUsedConfig('inquest_config', $this->fields['entities_id'], 'inquest_rate'); $delay = Entity::getUsedConfig('inquest_config', $this->fields['entities_id'], 'inquest_delay'); $type = Entity::getUsedConfig('inquest_config', $this->fields['entities_id']); $max_closedate = $this->fields['closedate']; if (in_array("status", $this->updates) && in_array($this->input["status"], $this->getClosedStatusArray()) && ($delay == 0) && ($rate > 0) && (mt_rand(1, 100) <= $rate)) { // For reopened ticket if ($inquest->getFromDB($this->fields['id'])) { $resp = $inquest->fields; $inquest->delete($resp); } $inquest->add( [ 'tickets_id' => $this->fields['id'], 'date_begin' => $_SESSION["glpi_currenttime"], 'entities_id' => $this->fields['entities_id'], 'type' => $type, 'max_closedate' => $max_closedate, ] ); } } function prepareInputForAdd($input) { // Standard clean datas $input = parent::prepareInputForAdd($input); if ($input === false) { return false; } if (!isset($input["requesttypes_id"])) { $input["requesttypes_id"] = RequestType::getDefault('helpdesk'); } if (!isset($input['global_validation'])) { $input['global_validation'] = CommonITILValidation::NONE; } // Set additional default dropdown $dropdown_fields = ['users_locations', 'items_locations']; foreach ($dropdown_fields as $field) { if (!isset($input[$field])) { $input[$field] = 0; } } if (!isset($input['itemtype']) || !isset($input['items_id']) || !($input['items_id'] > 0)) { $input['itemtype'] = ''; } // Get first item location $item = null; if (isset($input["items_id"]) && is_array($input["items_id"]) && (count($input["items_id"]) > 0)) { $infocom = new Infocom(); foreach ($input["items_id"] as $itemtype => $items) { foreach ($items as $items_id) { if ($item = getItemForItemtype($itemtype)) { $item->getFromDB($items_id); $input['items_states'] = $item->fields['states_id']; $input['items_locations'] = $item->fields['locations_id']; if ($infocom->getFromDBforDevice($itemtype, $items_id)) { $input['items_businesscriticities'] = Dropdown::getDropdownName('glpi_businesscriticities', $infocom->fields['businesscriticities_id']); } if (isset($item->fields['groups_id'])) { $input['items_groups'] = $item->fields['groups_id']; } break(2); } } } } // Business Rules do not override manual SLA and OLA $manual_slas_id = []; $manual_olas_id = []; foreach ([SLM::TTR, SLM::TTO] as $slmType) { list($dateField, $slaField) = SLA::getFieldNames($slmType); if (isset($input[$slaField]) && ($input[$slaField] > 0)) { $manual_slas_id[$slmType] = $input[$slaField]; } list($dateField, $olaField) = OLA::getFieldNames($slmType); if (isset($input[$olaField]) && ($input[$olaField] > 0)) { $manual_olas_id[$slmType] = $input[$olaField]; } } // fill auto-assign when no tech defined (only for tech) if (!isset($input['_auto_import']) && isset($_SESSION['glpiset_default_tech']) && $_SESSION['glpiset_default_tech'] && Session::getCurrentInterface() == 'central' && (!isset($input['_users_id_assign']) || $input['_users_id_assign'] == 0) && Session::haveRight("ticket", Ticket::OWN) ) { $input['_users_id_assign'] = Session::getLoginUserID(); } // Process Business Rules $this->fillInputForBusinessRules($input); $rules = new RuleTicketCollection($input['entities_id']); // Set unset variables with are needed $tmprequester = 0; $user = new User(); if (isset($input["_users_id_requester"])) { if (!is_array($input["_users_id_requester"]) && $user->getFromDB($input["_users_id_requester"])) { $input['users_locations'] = $user->fields['locations_id']; $input['users_default_groups'] = $user->fields['groups_id']; $tmprequester = $input["_users_id_requester"]; } else if (is_array($input["_users_id_requester"]) && ($user_id = reset($input["_users_id_requester"])) !== false) { if ($user->getFromDB($user_id)) { $input['users_locations'] = $user->fields['locations_id']; $input['users_default_groups'] = $user->fields['groups_id']; } } } // Clean new lines before passing to rules if (isset($input["content"])) { $input["content"] = preg_replace('/\\\\r\\\\n/', "\\n", $input['content']); $input["content"] = preg_replace('/\\\\n/', "\\n", $input['content']); } $input = $rules->processAllRules($input, $input, ['recursive' => true], ['condition' => RuleTicket::ONADD]); $input = Toolbox::stripslashes_deep($input); // Recompute default values based on values computed by rules $input = $this->computeDefaultValuesForAdd($input); if (isset($input['_users_id_requester']) && !is_array($input['_users_id_requester']) && ($input['_users_id_requester'] != $tmprequester)) { // if requester set by rule, clear address from mailcollector unset($input['_users_id_requester_notif']); } if (isset($input['_users_id_requester_notif']) && isset($input['_users_id_requester_notif']['alternative_email']) && is_array($input['_users_id_requester_notif']['alternative_email'])) { foreach ($input['_users_id_requester_notif']['alternative_email'] as $email) { if ($email && !NotificationMailing::isUserAddressValid($email)) { Session::addMessageAfterRedirect( sprintf(__('Invalid email address %s'), $email), false, ERROR ); return false; } } } // Manage auto assign $auto_assign_mode = Entity::getUsedConfig('auto_assign_mode', $input['entities_id']); switch ($auto_assign_mode) { case Entity::CONFIG_NEVER : break; case Entity::AUTO_ASSIGN_HARDWARE_CATEGORY : if ($item != null) { // Auto assign tech from item if ((!isset($input['_users_id_assign']) || ($input['_users_id_assign'] == 0)) && $item->isField('users_id_tech')) { $input['_users_id_assign'] = $item->getField('users_id_tech'); } // Auto assign group from item if ((!isset($input['_groups_id_assign']) || ($input['_groups_id_assign'] == 0)) && $item->isField('groups_id_tech')) { $input['_groups_id_assign'] = $item->getField('groups_id_tech'); } } // Auto assign tech/group from Category if (($input['itilcategories_id'] > 0) && ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) || (!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']))) { $cat = new ITILCategory(); $cat->getFromDB($input['itilcategories_id']); if ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) && $cat->isField('users_id')) { $input['_users_id_assign'] = $cat->getField('users_id'); } if ((!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']) && $cat->isField('groups_id')) { $input['_groups_id_assign'] = $cat->getField('groups_id'); } } break; case Entity::AUTO_ASSIGN_CATEGORY_HARDWARE : // Auto assign tech/group from Category if (($input['itilcategories_id'] > 0) && ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) || (!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']))) { $cat = new ITILCategory(); $cat->getFromDB($input['itilcategories_id']); if ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) && $cat->isField('users_id')) { $input['_users_id_assign'] = $cat->getField('users_id'); } if ((!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']) && $cat->isField('groups_id')) { $input['_groups_id_assign'] = $cat->getField('groups_id'); } } if ($item != null) { // Auto assign tech from item if ((!isset($input['_users_id_assign']) || ($input['_users_id_assign'] == 0)) && $item->isField('users_id_tech')) { $input['_users_id_assign'] = $item->getField('users_id_tech'); } // Auto assign group from item if ((!isset($input['_groups_id_assign']) || ($input['_groups_id_assign'] == 0)) && $item->isField('groups_id_tech')) { $input['_groups_id_assign'] = $item->getField('groups_id_tech'); } } break; } // Replay setting auto assign if set in rules engine or by auto_assign_mode // Do not force status if status has been set by rules if (((isset($input["_users_id_assign"]) && ((!is_array($input['_users_id_assign']) && $input["_users_id_assign"] > 0) || is_array($input['_users_id_assign']) && count($input['_users_id_assign']) > 0)) || (isset($input["_groups_id_assign"]) && ((!is_array($input['_groups_id_assign']) && $input["_groups_id_assign"] > 0) || is_array($input['_groups_id_assign']) && count($input['_groups_id_assign']) > 0)) || (isset($input["_suppliers_id_assign"]) && ((!is_array($input['_suppliers_id_assign']) && $input["_suppliers_id_assign"] > 0) || is_array($input['_suppliers_id_assign']) && count($input['_suppliers_id_assign']) > 0))) && (in_array($input['status'], $this->getNewStatusArray())) && !$this->isStatusComputationBlocked($input)) { $input["status"] = self::ASSIGNED; } // Manage SLA / OLA asignment // Manual SLA / OLA defined : reset due date // No manual SLA / OLA and due date defined : reset auto SLA / OLA foreach ([SLM::TTR, SLM::TTO] as $slmType) { $this->slaAffect($slmType, $input, $manual_slas_id); $this->olaAffect($slmType, $input, $manual_olas_id); } // auto set type if not set if (!isset($input["type"])) { $input['type'] = Entity::getUsedConfig('tickettype', $input['entities_id'], '', Ticket::INCIDENT_TYPE); } return $input; } function post_addItem() { global $CFG_GLPI; $this->manageValidationAdd($this->input); // Log this event $username = 'anonymous'; if (isset($_SESSION["glpiname"])) { $username = $_SESSION["glpiname"]; } Event::log($this->fields['id'], "ticket", 4, "tracking", sprintf(__('%1$s adds the item %2$s'), $username, $this->fields['id'])); if (isset($this->input["_followup"]) && is_array($this->input["_followup"]) && (strlen($this->input["_followup"]['content']) > 0)) { $fup = new ITILFollowup(); $type = "new"; if (isset($this->fields["status"]) && ($this->fields["status"] == self::SOLVED)) { $type = "solved"; } $toadd = ['type' => $type, 'items_id' => $this->fields['id'], 'itemtype' => 'Ticket']; if (isset($this->input["_followup"]['content']) && (strlen($this->input["_followup"]['content']) > 0)) { $toadd["content"] = $this->input["_followup"]['content']; } if (isset($this->input["_followup"]['is_private'])) { $toadd["is_private"] = $this->input["_followup"]['is_private']; } // $toadd['_no_notif'] = true; $fup->add($toadd); } if ((isset($this->input["plan"]) && count($this->input["plan"])) || (isset($this->input["actiontime"]) && ($this->input["actiontime"] > 0))) { $task = new TicketTask(); $type = "new"; if (isset($this->fields["status"]) && ($this->fields["status"] == self::SOLVED)) { $type = "solved"; } $toadd = ["type" => $type, "tickets_id" => $this->fields['id'], "actiontime" => $this->input["actiontime"]]; if (isset($this->input["plan"]) && count($this->input["plan"])) { $toadd["plan"] = $this->input["plan"]; } if (isset($_SESSION['glpitask_private'])) { $toadd['is_private'] = $_SESSION['glpitask_private']; } // $toadd['_no_notif'] = true; $task->add($toadd); } $ticket_ticket = new Ticket_Ticket(); // From interface if (isset($this->input['_link'])) { $this->input['_link']['tickets_id_1'] = $this->fields['id']; // message if ticket's ID doesn't exist if (!empty($this->input['_link']['tickets_id_2'])) { if ($ticket_ticket->can(-1, CREATE, $this->input['_link'])) { $ticket_ticket->add($this->input['_link']); } else { Session::addMessageAfterRedirect(__('Unknown ticket'), false, ERROR); } } } // From mailcollector : do not check rights if (isset($this->input["_linkedto"])) { $input2 = [ 'tickets_id_1' => $this->fields['id'], 'tickets_id_2' => $this->input["_linkedto"], 'link' => Ticket_Ticket::LINK_TO, ]; $ticket_ticket->add($input2); } // Manage SLA / OLA Level : add actions foreach ([SLM::TTR, SLM::TTO] as $slmType) { list($dateField, $slaField) = SLA::getFieldNames($slmType); if (isset($this->input[$slaField]) && ($this->input[$slaField] > 0)) { $this->manageSlaLevel($this->input[$slaField]); } list($dateField, $olaField) = OLA::getFieldNames($slmType); if (isset($this->input[$olaField]) && ($this->input[$olaField] > 0)) { $this->manageOlaLevel($this->input[$olaField]); } } // Add project task link if needed if (isset($this->input['_projecttasks_id'])) { $projecttask = new ProjectTask(); if ($projecttask->getFromDB($this->input['_projecttasks_id'])) { $pt = new ProjectTask_Ticket(); $pt->add(['projecttasks_id' => $this->input['_projecttasks_id'], 'tickets_id' => $this->fields['id'], /*'_no_notif' => true*/]); } } if (isset($this->input['_promoted_fup_id']) && $this->input['_promoted_fup_id'] > 0) { $fup = new ITILFollowup(); $fup->getFromDB($this->input['_promoted_fup_id']); $fup->update([ 'id' => $this->input['_promoted_fup_id'], 'sourceof_items_id' => $this->getID() ]); Event::log($this->getID(), "ticket", 4, "tracking", sprintf(__('%s promotes a followup from ticket %s'), $_SESSION["glpiname"], $fup->fields['items_id'])); } if (!empty($this->input['items_id'])) { $item_ticket = new Item_Ticket(); foreach ($this->input['items_id'] as $itemtype => $items) { foreach ($items as $items_id) { $item_ticket->add(['items_id' => $items_id, 'itemtype' => $itemtype, 'tickets_id' => $this->fields['id'], '_disablenotif' => true]); } } } parent::post_addItem(); // Processing Email if (!isset($this->input['_disablenotif']) && $CFG_GLPI["use_notifications"]) { // Clean reload of the ticket $this->getFromDB($this->fields['id']); $type = "new"; if (isset($this->fields["status"]) && ($this->fields["status"] == self::SOLVED)) { $type = "solved"; } NotificationEvent::raiseEvent($type, $this); } if (isset($_SESSION['glpiis_ids_visible']) && !$_SESSION['glpiis_ids_visible']) { Session::addMessageAfterRedirect(sprintf(__('%1$s (%2$s)'), __('Your ticket has been registered, its treatment is in progress.'), sprintf(__('%1$s: %2$s'), __('Ticket'), "". $this->fields['id'].""))); } } /** * Manage Validation add from input * * @since 0.85 * * @param $input array : input array * * @return boolean **/ function manageValidationAdd($input) { //Action for send_validation rule if (isset($input["_add_validation"])) { if (isset($input['entities_id'])) { $entid = $input['entities_id']; } else if (isset($this->fields['entities_id'])) { $entid = $this->fields['entities_id']; } else { return false; } $validations_to_send = []; if (!is_array($input["_add_validation"])) { $input["_add_validation"] = [$input["_add_validation"]]; } foreach ($input["_add_validation"] as $key => $validation) { switch ($validation) { case 'requester_supervisor' : if (isset($input['_groups_id_requester']) && $input['_groups_id_requester']) { $users = Group_User::getGroupUsers( $input['_groups_id_requester'], ['is_manager' => 1] ); foreach ($users as $data) { $validations_to_send[] = $data['id']; } } // Add to already set groups foreach ($this->getGroups(CommonITILActor::REQUESTER) as $d) { $users = Group_User::getGroupUsers( $d['groups_id'], ['is_manager' => 1] ); foreach ($users as $data) { $validations_to_send[] = $data['id']; } } break; case 'assign_supervisor' : if (isset($input['_groups_id_assign']) && $input['_groups_id_assign']) { $users = Group_User::getGroupUsers( $input['_groups_id_assign'], ['is_manager' => 1] ); foreach ($users as $data) { $validations_to_send[] = $data['id']; } } foreach ($this->getGroups(CommonITILActor::ASSIGN) as $d) { $users = Group_User::getGroupUsers( $d['groups_id'], ['is_manager' => 1] ); foreach ($users as $data) { $validations_to_send[] = $data['id']; } } break; case 'requester_responsible': if (isset($input['_users_id_requester'])) { if (is_array($input['_users_id_requester'])) { foreach ($input['_users_id_requester'] as $users_id) { $user = new User(); if ($user->getFromDB($users_id)) { $validations_to_send[] = $user->getField('users_id_supervisor'); } } } else { $user = new User(); if ($user->getFromDB($input['_users_id_requester'])) { $validations_to_send[] = $user->getField('users_id_supervisor'); } } } break; default : // Group case from rules if ($key === 'group') { foreach ($validation as $groups_id) { $validation_right = 'validate_incident'; if (isset($input['type']) && ($input['type'] == Ticket::DEMAND_TYPE)) { $validation_right = 'validate_request'; } $opt = ['groups_id' => $groups_id, 'right' => $validation_right, 'entity' => $entid]; $data_users = TicketValidation::getGroupUserHaveRights($opt); foreach ($data_users as $user) { $validations_to_send[] = $user['id']; } } } else { $validations_to_send[] = $validation; } } } // Validation user added on ticket form if (isset($input['users_id_validate'])) { if (array_key_exists('groups_id', $input['users_id_validate'])) { foreach ($input['users_id_validate'] as $key => $validation_to_add) { if (is_numeric($key)) { $validations_to_send[] = $validation_to_add; } } } else { foreach ($input['users_id_validate'] as $key => $validation_to_add) { if (is_numeric($key)) { $validations_to_send[] = $validation_to_add; } } } } // Keep only one $validations_to_send = array_unique($validations_to_send); $validation = new TicketValidation(); if (count($validations_to_send)) { $values = []; $values['tickets_id'] = $this->fields['id']; if (isset($input['id']) && $input['id'] != $this->fields['id']) { $values['_ticket_add'] = true; } // to know update by rules if (isset($input["_rule_process"])) { $values['_rule_process'] = $input["_rule_process"]; } // if auto_import, tranfert it for validation if (isset($input['_auto_import'])) { $values['_auto_import'] = $input['_auto_import']; } // Cron or rule process of hability to do if (Session::isCron() || isset($input["_auto_import"]) || isset($input["_rule_process"]) || $validation->can(-1, CREATE, $values)) { // cron or allowed user $add_done = false; foreach ($validations_to_send as $user) { // Do not auto add twice same validation if (!TicketValidation::alreadyExists($values['tickets_id'], $user)) { $values["users_id_validate"] = $user; if ($validation->add($values)) { $add_done = true; } } } if ($add_done) { Event::log($this->fields['id'], "ticket", 4, "tracking", sprintf(__('%1$s updates the item %2$s'), $_SESSION["glpiname"], $this->fields['id'])); } } } } return true; } /** * Get active or solved tickets for an hardware last X days * * @since 0.83 * * @param $itemtype string Item type * @param $items_id integer ID of the Item * @param $days integer day number * * @return array **/ function getActiveOrSolvedLastDaysTicketsForItem($itemtype, $items_id, $days) { global $DB; $result = []; $iterator = $DB->request([ 'FROM' => $this->getTable(), 'LEFT JOIN' => [ 'glpi_items_tickets' => [ 'ON' => [ 'glpi_items_tickets' => 'tickets_id', $this->getTable() => 'id' ] ] ], 'WHERE' => [ 'glpi_items_tickets.items_id' => $items_id, 'glpi_items_tickets.itemtype' => $itemtype, 'OR' => [ [ 'NOT' => [ $this->getTable() . '.status' => array_merge( $this->getClosedStatusArray(), $this->getSolvedStatusArray() ) ] ], [ 'NOT' => [$this->getTable() . '.solvedate' => null], new \QueryExpression( "ADDDATE(" . $DB->quoteName($this->getTable()) . ".".$DB->quoteName('solvedate').", INTERVAL $days DAY) > NOW()" ) ] ] ] ]); while ($tick = $iterator->next()) { $result[$tick['id']] = $tick['name']; } return $result; } /** * Count active tickets for an hardware * * @since 0.83 * * @param $itemtype string Item type * @param $items_id integer ID of the Item * * @return integer **/ function countActiveTicketsForItem($itemtype, $items_id) { global $DB; $result = $DB->request([ 'COUNT' => 'cpt', 'FROM' => $this->getTable(), 'LEFT JOIN' => [ 'glpi_items_tickets' => [ 'ON' => [ 'glpi_items_tickets' => 'tickets_id', $this->getTable() => 'id' ] ] ], 'WHERE' => [ 'glpi_items_tickets.itemtype' => $itemtype, 'glpi_items_tickets.items_id' => $items_id, 'NOT' => [ $this->getTable() . '.status' => array_merge( $this->getSolvedStatusArray(), $this->getClosedStatusArray() ) ] ] ])->next(); return $result['cpt']; } /** * Get active tickets for an item * * @since 9.5 * * @param string $itemtype Item type * @param integer $items_id ID of the Item * @param string $type Type of the tickets (incident or request) * * @return DBmysqlIterator */ public function getActiveTicketsForItem($itemtype, $items_id, $type) { global $DB; return $DB->request([ 'SELECT' => [ $this->getTable() . '.id', $this->getTable() . '.name', $this->getTable() . '.priority', ], 'FROM' => $this->getTable(), 'LEFT JOIN' => [ 'glpi_items_tickets' => [ 'ON' => [ 'glpi_items_tickets' => 'tickets_id', $this->getTable() => 'id' ] ] ], 'WHERE' => [ 'glpi_items_tickets.itemtype' => $itemtype, 'glpi_items_tickets.items_id' => $items_id, $this->getTable() . '.is_deleted' => 0, $this->getTable() . '.type' => $type, 'NOT' => [ $this->getTable() . '.status' => array_merge( $this->getSolvedStatusArray(), $this->getClosedStatusArray() ) ] ] ]); } /** * Count solved tickets for an hardware last X days * * @since 0.83 * * @param $itemtype string Item type * @param $items_id integer ID of the Item * @param $days integer day number * * @return integer **/ function countSolvedTicketsForItemLastDays($itemtype, $items_id, $days) { global $DB; $result = $DB->request([ 'COUNT' => 'cpt', 'FROM' => $this->getTable(), 'LEFT JOIN' => [ 'glpi_items_tickets' => [ 'ON' => [ 'glpi_items_tickets' => 'tickets_id', $this->getTable() => 'id' ] ] ], 'WHERE' => [ 'glpi_items_tickets.itemtype' => $itemtype, 'glpi_items_tickets.items_id' => $items_id, $this->getTable() . '.status' => array_merge( $this->getSolvedStatusArray(), $this->getClosedStatusArray() ), new \QueryExpression( "ADDDATE(".$DB->quoteName($this->getTable().".solvedate").", INTERVAL $days DAY) > NOW()" ), 'NOT' => [ $this->getTable() . '.solvedate' => null ] ] ])->next(); return $result['cpt']; } /** * Update date mod of the ticket * * @since 0.83.3 new proto * * @param $ID ID of the ticket * @param $no_stat_computation boolean do not cumpute take into account stat (false by default) * @param $users_id_lastupdater integer to force last_update id (default 0 = not used) **/ function updateDateMod($ID, $no_stat_computation = false, $users_id_lastupdater = 0) { if ($this->getFromDB($ID)) { if (!$no_stat_computation && !$this->isAlreadyTakenIntoAccount() && ($this->canTakeIntoAccount() || isCommandLine())) { return $this->update( [ 'id' => $ID, 'takeintoaccount_delay_stat' => $this->computeTakeIntoAccountDelayStat(), '_disablenotif' => true ] ); } parent::updateDateMod($ID, $no_stat_computation, $users_id_lastupdater); } } /** * Overloaded from commonDBTM * * @since 0.83 * * @param $type itemtype of object to add * * @return rights **/ function canAddItem($type) { if ($type == 'Document') { if ($this->getField('status') == self::CLOSED) { return false; } if ($this->canAddFollowups()) { return true; } } // as self::canUpdate & $this->canUpdateItem checks more general rights // (like STEAL or OWN), // we specify only the rights needed for this action return $this->checkEntity() && (Session::haveRight(self::$rightname, UPDATE) || $this->canRequesterUpdateItem()); } /** * Check if user can add followups to the ticket. * * @param integer $user_id * * @return boolean */ public function canUserAddFollowups($user_id) { $entity_id = $this->fields['entities_id']; $group_user = new Group_User(); $user_groups = $group_user->getUserGroups($user_id, ['entities_id' => $entity_id]); $user_groups_ids = []; foreach ($user_groups as $user_group) { $user_groups_ids[] = $user_group['id']; } $rightname = ITILFollowup::$rightname; return ( Profile::haveUserRight($user_id, $rightname, ITILFollowup::ADDMYTICKET, $entity_id) && ($this->isUser(CommonITILActor::REQUESTER, $user_id) || ( isset($this->fields['users_id_recipient']) && ($this->fields['users_id_recipient'] === $user_id) ) ) ) || Profile::haveUserRight($user_id, $rightname, ITILFollowup::ADDALLTICKET, $entity_id) || ( Profile::haveUserRight($user_id, $rightname, ITILFollowup::ADDGROUPTICKET, $entity_id) && $this->haveAGroup(CommonITILActor::REQUESTER, $user_groups_ids) ) || $this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()) || $this->haveAGroup(CommonITILActor::ASSIGN, $user_groups_ids); } /** * Get default values to search engine to override **/ static function getDefaultSearchRequest() { $search = ['criteria' => [0 => ['field' => 12, 'searchtype' => 'equals', 'value' => 'notclosed']], 'sort' => 19, 'order' => 'DESC']; if (Session::haveRight(self::$rightname, self::READALL)) { $search['criteria'][0]['value'] = 'notold'; } return $search; } /** * @see CommonDBTM::getSpecificMassiveActions() **/ function getSpecificMassiveActions($checkitem = null) { $actions = []; if (Session::getCurrentInterface() == 'central') { if (Ticket::canUpdate() && Ticket::canDelete()) { $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'merge_as_followup'] = "". __('Merge as Followup'); } if (Item_Ticket::canCreate()) { $actions['Item_Ticket'.MassiveAction::CLASS_ACTION_SEPARATOR.'add_item'] = "". _x('button', 'Add an item'); } if (ITILFollowup::canCreate()) { $actions['ITILFollowup'.MassiveAction::CLASS_ACTION_SEPARATOR.'add_followup'] = "". __('Add a new followup'); } if (TicketTask::canCreate()) { $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'add_task'] = "". __('Add a new task'); } if (TicketValidation::canCreate()) { $actions['TicketValidation'.MassiveAction::CLASS_ACTION_SEPARATOR.'submit_validation'] = "". __('Approval request'); } if (Item_Ticket::canDelete()) { $actions['Item_Ticket'.MassiveAction::CLASS_ACTION_SEPARATOR.'delete_item'] = _x('button', 'Remove an item'); } if (Session::haveRight(self::$rightname, UPDATE)) { $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'add_actor'] = "". __('Add an actor'); $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'update_notif'] = __('Set notifications for all actors'); $actions['Ticket_Ticket'.MassiveAction::CLASS_ACTION_SEPARATOR.'add'] = "". _x('button', 'Link tickets'); KnowbaseItem_Item::getMassiveActionsForItemtype($actions, __CLASS__, 0, $checkitem); } } $actions += parent::getSpecificMassiveActions($checkitem); return $actions; } static function showMassiveActionsSubForm(MassiveAction $ma) { switch ($ma->getAction()) { case 'merge_as_followup' : $rand = mt_rand(); $mergeparam = [ 'name' => "_mergeticket", 'used' => $ma->items['Ticket'], 'displaywith' => ['id'], 'rand' => $rand ]; echo ""; Ticket::dropdown($mergeparam); echo " | |||
"; Html::showCheckbox([ 'name' => 'with_followups', 'id' => 'with_followups', 'checked' => true ]); echo " | "; Html::showCheckbox([ 'name' => 'with_documents', 'id' => 'with_documents', 'checked' => true ]); echo " | ||
"; Html::showCheckbox([ 'name' => 'with_tasks', 'id' => 'with_tasks', 'checked' => true ]); echo " | "; Html::showCheckbox([ 'name' => 'with_actors', 'id' => 'with_actors', 'checked' => true ]); echo " | ||
"; Dropdown::showFromArray('link_type', [ 0 => __('None'), Ticket_Ticket::LINK_TO => __('Linked to'), Ticket_Ticket::DUPLICATE_WITH => __('Duplicates'), Ticket_Ticket::SON_OF => __('Son of'), Ticket_Ticket::PARENT_OF => __('Parent of') ], ['value' => Ticket_Ticket::SON_OF, 'rand' => $rand]); echo " | |||
"; echo Html::submit(_x('button', 'Merge'), [ 'name' => 'merge', 'confirm' => __('Confirm the merge? This ticket will be deleted!') ]); echo " |