. * --------------------------------------------------------------------- */ if (!defined('GLPI_ROOT')) { die("Sorry. You can't access this file directly"); } /** * LevelAgreement base Class for OLA & SLA * @since 9.2 **/ abstract class LevelAgreement extends CommonDBChild { // From CommonDBTM var $dohistory = true; static $rightname = 'slm'; // From CommonDBChild static public $itemtype = 'SLM'; static public $items_id = 'slms_id'; /** * Display a specific OLA or SLA warning. * Called into the above showForm() function * * @return void */ abstract function showFormWarning(); /** * Return the text needed for a confirmation of adding level agreement to a ticket * * @return array of strings */ abstract function getAddConfirmation(); /** * Get table fields * * @param integer $subtype of OLA/SLA, can be SLM::TTO or SLM::TTR * * @return array of 'date' and 'sla' field names */ static function getFieldNames($subtype) { $dateField = null; $laField = null; switch ($subtype) { case SLM::TTO: $dateField = static::$prefixticket.'time_to_own'; $laField = static::$prefix.'s_id_tto'; break; case SLM::TTR: $dateField = static::$prefixticket.'time_to_resolve'; $laField = static::$prefix.'s_id_ttr'; break; } return [$dateField, $laField]; } function defineTabs($options = []) { $ong = []; $this->addDefaultFormTab($ong); $this->addStandardTab(static::$levelclass, $ong, $options); $this->addStandardTab('Rule', $ong, $options); $this->addStandardTab('Ticket', $ong, $options); return $ong; } /** * Define calendar of the ticket using the SLA/OLA when using this calendar as sla/ola-s calendar * * @param integer $calendars_id calendars_id of the ticket **/ function setTicketCalendar($calendars_id) { if ($this->fields['calendars_id'] == -1) { $this->fields['calendars_id'] = $calendars_id; } } function post_getFromDB() { // get calendar from slm $slm = new SLM; if ($slm->getFromDB($this->fields['slms_id'])) { $this->fields['calendars_id'] = $slm->fields['calendars_id']; } } function post_getEmpty() { $this->fields['number_time'] = 4; $this->fields['definition_time'] = 'hour'; } /** * Print the form * * @param $ID integer ID of the item * @param $options array of possible options: * - target filename : where to go when done. * - withtemplate boolean : template or basic item * *@return boolean item found **/ function showForm($ID, $options = []) { $rowspan = 3; if ($ID > 0) { $rowspan = 5; } // Get SLM object $slm = new SLM(); if (isset($options['parent'])) { $slm = $options['parent']; } else { $slm->getFromDB($this->fields['slms_id']); } if ($ID > 0) { $this->check($ID, READ); } else { // Create item $options[static::$items_id] = $slm->getField('id'); //force itemtype of parent static::$itemtype = get_class($slm); $this->check(-1, CREATE, $options); } $this->showFormHeader($options); echo ""; echo "".__('Name').""; echo ""; Html::autocompletionTextField($this, "name", ['value' => $this->fields["name"]]); echo "".__('Comments').""; echo " "; echo ""; echo ""; echo "".__('SLM').""; echo ""; echo $slm->getLink(); echo ""; echo ""; if ($ID > 0) { echo ""; echo "".__('Last update').""; echo "".($this->fields["date_mod"] ? Html::convDateTime($this->fields["date_mod"]) : __('Never')); echo ""; } echo ""._n('Type', 'Types', 1).""; echo ""; self::getTypeDropdown(['value' => $this->fields["type"]]); echo ""; echo ""; echo "".__('Maximum time').""; echo ""; Dropdown::showNumber("number_time", ['value' => $this->fields["number_time"], 'min' => 0]); $possible_values = ['minute' => _n('Minute', 'Minutes', Session::getPluralNumber()), 'hour' => _n('Hour', 'Hours', Session::getPluralNumber()), 'day' => _n('Day', 'Days', Session::getPluralNumber())]; $rand = Dropdown::showFromArray('definition_time', $possible_values, ['value' => $this->fields["definition_time"], 'on_change' => 'appearhideendofworking()']); echo "\n\n"; echo ""; echo ""; echo "
".__('End of working day')."
"; echo ""; echo ""; $this->showFormWarning(); echo ""; echo ""; $this->showFormButtons($options); return true; } /** * Show for ticket * * @param Ticket $ticket Ticket item * @param integer $type * @param ITILTemplate $tt ticket template object * @param bool $canupdate update right */ function showForTicket(Ticket $ticket, $type, $tt, $canupdate) { list($dateField, $laField) = static::getFieldNames($type); $rand = mt_rand(); $pre = static::$prefix; echo ""; echo ""; if (!isset($ticket->fields[$dateField]) || $ticket->fields[$dateField] == 'NULL') { $ticket->fields[$dateField]=''; } if ($ticket->fields['id']) { if ($this->getDataForTicket($ticket->fields['id'], $type)) { echo ""; echo ""; } else { echo ""; } } else { // New Ticket echo ""; $data = $this->find( ['type' => $type] + getEntitiesRestrictCriteria('', '', $ticket->fields['entities_id'], true) ); if ($canupdate && !empty($data)) { echo $tt->getBeginHiddenFieldText($laField); if (!$tt->isHiddenField($laField) || $tt->isPredefinedField($laField)) { echo ""; } echo $tt->getEndHiddenFieldText($laField); echo ""; } } echo ""; echo "
"; echo $tt->getBeginHiddenFieldValue($dateField); echo Html::convDateTime($ticket->fields[$dateField]); echo $tt->getEndHiddenFieldValue($dateField, $ticket); echo ""; echo $tt->getBeginHiddenFieldText($laField); echo ""; echo Dropdown::getDropdownName(static::getTable(), $ticket->fields[$laField])." "; echo Html::hidden($laField, ['value' => $ticket->fields[$laField]]); $obj = new static(); $obj->getFromDB($ticket->fields[$laField]); $comment = isset($obj->fields['comment']) ? $obj->fields['comment'] : ''; $level = new static::$levelclass(); $nextaction = new static::$levelticketclass(); if ($nextaction->getFromDBForTicket($ticket->fields["id"], $type)) { $comment .= '
'. sprintf(__('Next escalation: %s'), Html::convDateTime($nextaction->fields['date'])). '
'; if ($level->getFromDB($nextaction->fields[$pre.'levels_id'])) { $comment .= ''. sprintf(__('%1$s: %2$s'), _n('Escalation level', 'Escalation levels', 1), $level->getName()). ''; } } $options = []; if (Session::haveRight('slm', READ)) { $options['link'] = $this->getLinkURL(); } Html::showToolTip($comment, $options); if ($canupdate) { $delete_field = strtolower(get_called_class())."_delete"; $fields = [$delete_field => $delete_field, 'id' => $ticket->getID(), 'type' => $type, '_glpi_csrf_token' => Session::getNewCSRFToken(), '_glpi_simple_form' => 1]; $ticket_url = $ticket->getFormURL(); echo Html::scriptBlock(" function delete_date$type$rand(e) { e.preventDefault(); if (nativeConfirm('".addslashes(__('Also delete date?'))."')) { submitGetLink('$ticket_url', ".json_encode(array_merge($fields, ['delete_date' => 1]))."); } else { submitGetLink('$ticket_url', ".json_encode(array_merge($fields, ['delete_date' => 0]))."); } }"); echo ""; echo ""._x('button', 'Delete permanently').""; echo ""; } echo $tt->getEndHiddenFieldText($laField); echo "
"; echo $tt->getBeginHiddenFieldValue($dateField); echo ""; if ($canupdate) { Html::showDateTimeField($dateField, ['value' => $ticket->fields[$dateField], 'maybeempty' => true]); } else { echo Html::convDateTime($ticket->fields[$dateField]); } echo ""; echo $tt->getEndHiddenFieldValue($dateField, $ticket); $data = $this->find( ['type' => $type] + getEntitiesRestrictCriteria('', '', $ticket->fields['entities_id'], true) ); if ($canupdate && !empty($data)) { echo $tt->getBeginHiddenFieldText($laField); echo ""; echo "getAddConfirmation(), "cleanhide('la_action$type$rand');cleandisplay('la_choice$type$rand');"). " class='pointer' title='".static::getTypeName()."'> "; echo ""; echo ""; echo $tt->getEndHiddenFieldText($laField); } echo ""; echo $tt->getBeginHiddenFieldValue($dateField); Html::showDateTimeField($dateField, ['value' => $ticket->fields[$dateField], 'maybeempty' => false, 'canedit' => $canupdate, 'required' => $tt->isMandatoryField($dateField)]); echo $tt->getEndHiddenFieldValue($dateField, $ticket); echo "".sprintf(__('%1$s%2$s'), static::getTypeName(), $tt->getMandatoryMark($laField))."".$tt->getBeginHiddenFieldValue($laField); static::dropdown([ 'name' => $laField, 'entity' => $ticket->fields["entities_id"], 'value' => isset($ticket->fields[$laField]) ? $ticket->fields[$laField] : 0, 'condition' => ['type' => $type] ]); echo $tt->getEndHiddenFieldValue($laField, $ticket); echo "
"; } /** * Print the HTML for a SLM * * @param SLM $slm Slm item */ static function showForSLM(SLM $slm) { global $CFG_GLPI; if (!$slm->can($slm->fields['id'], READ)) { return false; } $instID = $slm->fields['id']; $la = new static(); $calendar = new Calendar(); $rand = mt_rand(); $canedit = ($slm->canEdit($instID) && Session::getCurrentInterface() == "central"); if ($canedit) { echo "
\n"; echo ""; echo "
". ""; echo __('Add a new item')."
\n"; } // list $laList = $la->find(['slms_id' => $instID]); Session::initNavigateListItems(__CLASS__, sprintf(__('%1$s = %2$s'), $slm::getTypeName(1), $slm->getName())); echo "
"; if (count($laList)) { if ($canedit) { Html::openMassiveActionsForm('mass'.__CLASS__.$rand); $massiveactionparams = ['container' => 'mass'.__CLASS__.$rand]; Html::showMassiveActions($massiveactionparams); } echo ""; $header_begin = ""; $header_top = ''; $header_bottom = ''; $header_end = ''; if ($canedit) { $header_top .= ""; $header_bottom .= ""; } $header_end .= ""; $header_end .= ""; $header_end .= ""; $header_end .= ""; echo $header_begin.$header_top.$header_end; foreach ($laList as $val) { $edit = ($canedit ? "style='cursor:pointer' onClick=\"viewEditLa". $instID.$val["id"]."$rand();\"" : ''); echo "\n"; echo ""; echo ""; $la->getFromDB($val['id']); echo ""; echo ""; echo ""; if (!$slm->fields['calendars_id']) { $link = __('24/7'); } else if ($slm->fields['calendars_id'] == -1) { $link = __('Calendar of the ticket'); } else if ($calendar->getFromDB($slm->fields['calendars_id'])) { $link = $calendar->getLink(); } echo ""; echo ""; } echo $header_begin.$header_bottom.$header_end; echo "
".Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand); $header_top .= "".Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand); $header_bottom .= "".__('Name').""._n('Type', 'Types', 1)."".__('Maximum time').""._n('Calendar', 'Calendars', 1)."
"; if ($canedit) { Html::showMassiveActionCheckBox($la->getType(), $val['id']); } echo "".$la->getLink()."".$la->getSpecificValueToDisplay('type', $la->fields['type']).""; echo $la->getSpecificValueToDisplay('number_time', ['number_time' => $la->fields['number_time'], 'definition_time' => $la->fields['definition_time']]); echo "".$link."
"; if ($canedit) { $massiveactionparams['ontop'] = false; Html::showMassiveActions($massiveactionparams); Html::closeForm(); } } else { echo __('No item to display'); } echo "
"; } /** * Display a list of rule for the current sla/ola * @return void */ function showRulesList() { global $DB; $fk = static::getFieldNames($this->fields['type'])[1]; $rule = new RuleTicket; $rand = mt_rand(); $canedit = self::canUpdate(); $rules_id_list = iterator_to_array($DB->request([ 'SELECT' => 'rules_id', 'DISTINCT' => true, 'FROM' => 'glpi_ruleactions', 'WHERE' => [ 'field' => $fk, 'value' => $this->getID()]])); $nb = count($rules_id_list); echo "
"; if (!$nb) { echo ""; echo ""; echo "\n"; echo "
" . __('No item found') . "
\n"; } else { if ($canedit) { Html::openMassiveActionsForm('massRuleTicket'.$rand); $massiveactionparams = ['num_displayed' => min($_SESSION['glpilist_limit'], $nb), 'specific_actions' => ['update' => _x('button', 'Update'), 'purge' => _x('button', 'Delete permanently')]]; Html::showMassiveActions($massiveactionparams); } echo ""; $header_begin = ""; $header_top = ''; $header_bottom = ''; $header_end = ''; if ($canedit) { $header_begin .= ""; } $header_end .= ""; $header_end .= ""; $header_end .= ""; $header_end .= "\n"; echo $header_begin.$header_top.$header_end; Session::initNavigateListItems(get_class($this), sprintf(__('%1$s = %2$s'), $rule->getTypeName(1), $rule->getName())); foreach ($rules_id_list as $data) { $rule->getFromDB($data['rules_id']); Session::addToNavigateListItems(get_class($this), $rule->fields["id"]); echo ""; if ($canedit) { echo ""; $ruleclassname = get_class($rule); echo ""; } else { echo ""; } echo ""; echo ""; echo "\n"; } echo $header_begin.$header_bottom.$header_end; echo "
"; $header_top .= Html::getCheckAllAsCheckbox('massRuleTicket'.$rand); $header_bottom .= Html::getCheckAllAsCheckbox('massRuleTicket'.$rand); $header_end .= "" . RuleTicket::getTypeName($nb) . "" . __('Active') . "" . __('Description') . "
"; Html::showMassiveActionCheckBox("RuleTicket", $rule->fields["id"]); echo "" .$rule->fields["name"] ."" . $rule->fields["name"] . "" . Dropdown::getYesNo($rule->fields["is_active"]) . "" . $rule->fields["description"] . "
\n"; if ($canedit) { $massiveactionparams['ontop'] = false; Html::showMassiveActions($massiveactionparams); Html::closeForm(); } } echo "
"; } function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { if (!$withtemplate) { $nb = 0; switch ($item->getType()) { case 'SLM' : if ($_SESSION['glpishow_count_on_tabs']) { $nb = countElementsInTable(self::getTable(), ['slms_id' => $item->getField('id')]); } return self::createTabEntry(static::getTypeName($nb), $nb); } } return ''; } static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { switch ($item->getType()) { case 'SLM' : self::showForSLM($item); break; } return true; } /** * Get data by type and ticket * * @param $tickets_id * @param $type */ function getDataForTicket($tickets_id, $type) { global $DB; list($dateField, $field) = static::getFieldNames($type); $iterator = $DB->request([ 'SELECT' => [static::getTable() . '.id'], 'FROM' => static::getTable(), 'INNER JOIN' => [ 'glpi_tickets' => [ 'FKEY' => [ static::getTable() => 'id', 'glpi_tickets' => $field ] ] ], 'WHERE' => ['glpi_tickets.id' => $tickets_id], 'LIMIT' => 1 ]); if (count($iterator)) { return $this->getFromIter($iterator); } return false; } 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' => $this->getTable(), 'field' => 'id', 'name' => __('ID'), 'massiveaction' => false, 'datatype' => 'number' ]; $tab[] = [ 'id' => '5', 'table' => $this->getTable(), 'field' => 'number_time', 'name' => _x('hour', 'Time'), 'datatype' => 'specific', 'massiveaction' => false, 'nosearch' => true, 'additionalfields' => ['definition_time'] ]; $tab[] = [ 'id' => '6', 'table' => $this->getTable(), 'field' => 'end_of_working_day', 'name' => __('End of working day'), 'datatype' => 'bool', 'massiveaction' => false ]; $tab[] = [ 'id' => '7', 'table' => $this->getTable(), 'field' => 'type', 'name' => _n('Type', 'Types', 1), 'datatype' => 'specific' ]; $tab[] = [ 'id' => '8', 'table' => 'glpi_slms', 'field' => 'name', 'name' => __('SLM'), 'datatype' => 'dropdown' ]; $tab[] = [ 'id' => '16', 'table' => $this->getTable(), 'field' => 'comment', 'name' => __('Comments'), 'datatype' => 'text' ]; return $tab; } /** * @param $field * @param $values * @param $options array **/ static function getSpecificValueToDisplay($field, $values, array $options = []) { if (!is_array($values)) { $values = [$field => $values]; } switch ($field) { case 'number_time' : switch ($values['definition_time']) { case 'minute' : return sprintf(_n('%d minute', '%d minutes', $values[$field]), $values[$field]); case 'hour' : return sprintf(_n('%d hour', '%d hours', $values[$field]), $values[$field]); case 'day' : return sprintf(_n('%d day', '%d days', $values[$field]), $values[$field]); } break; case 'type' : return self::getOneTypeName($values[$field]); } return parent::getSpecificValueToDisplay($field, $values, $options); } /** * @param $field * @param $name (default '') * @param $values (default '') * @param $options array * * @return string **/ static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) { if (!is_array($values)) { $values = [$field => $values]; } $options['display'] = false; switch ($field) { case 'type': $options['value'] = $values[$field]; return self::getTypeDropdown($options); } return parent::getSpecificValueToSelect($field, $name, $values, $options); } /** * Get computed resolution time * * @return integer resolution time (default 0) **/ function getTime() { if (isset($this->fields['id'])) { if ($this->fields['definition_time'] == "minute") { return $this->fields['number_time'] * MINUTE_TIMESTAMP; } if ($this->fields['definition_time'] == "hour") { return $this->fields['number_time'] * HOUR_TIMESTAMP; } if ($this->fields['definition_time'] == "day") { return $this->fields['number_time'] * DAY_TIMESTAMP; } } return 0; } /** * Get active time between to date time for the active calendar * * @param datetime $start begin * @param datetime $end end * * @return integer timestamp of delay **/ function getActiveTimeBetween($start, $end) { if ($end < $start) { return 0; } if (isset($this->fields['id'])) { $cal = new Calendar(); $work_in_days = ($this->fields['definition_time'] == 'day'); // Based on a calendar if ($this->fields['calendars_id'] > 0) { if ($cal->getFromDB($this->fields['calendars_id'])) { return $cal->getActiveTimeBetween($start, $end, $work_in_days); } } else { // No calendar $timestart = strtotime($start); $timeend = strtotime($end); return ($timeend-$timestart); } } return 0; } /** * Get date for current agreement * * @param string $start_date datetime start date * @param integer $additional_delay integer additional delay to add or substract (for waiting time) * * @return string|null due date time (NULL if sla/ola not exists) **/ function computeDate($start_date, $additional_delay = 0) { if (isset($this->fields['id'])) { $delay = $this->getTime(); // Based on a calendar if ($this->fields['calendars_id'] > 0) { $cal = new Calendar(); $work_in_days = ($this->fields['definition_time'] == 'day'); if ($cal->getFromDB($this->fields['calendars_id']) && $cal->hasAWorkingDay()) { return $cal->computeEndDate($start_date, $delay, $additional_delay, $work_in_days, $this->fields['end_of_working_day']); } } // No calendar defined or invalid calendar if ($this->fields['number_time'] >= 0) { $starttime = strtotime($start_date); $endtime = $starttime + $delay + $additional_delay; return date('Y-m-d H:i:s', $endtime); } } return null; } /** * Get execution date of a level * * @param string $start_date start date * @param integer $levels_id sla/ola level id * @param integer $additional_delay additional delay to add or substract (for waiting time) * * @return string|null execution date time (NULL if ola/sla not exists) **/ function computeExecutionDate($start_date, $levels_id, $additional_delay = 0) { if (isset($this->fields['id'])) { $level = new static::$levelclass(); $fk = getForeignKeyFieldForItemType(get_called_class()); if ($level->getFromDB($levels_id)) { // level exists if ($level->fields[$fk] == $this->fields['id']) { // correct level $work_in_days = ($this->fields['definition_time'] == 'day'); $delay = $this->getTime(); // Based on a calendar if ($this->fields['calendars_id'] > 0) { $cal = new Calendar(); if ($cal->getFromDB($this->fields['calendars_id']) && $cal->hasAWorkingDay()) { return $cal->computeEndDate($start_date, $delay, $level->fields['execution_time'] + $additional_delay, $work_in_days); } } // No calendar defined or invalid calendar $delay += $additional_delay+$level->fields['execution_time']; $starttime = strtotime($start_date); $endtime = $starttime+$delay; return date('Y-m-d H:i:s', $endtime); } } } return null; } /** * Get types * * @return array array of types **/ static function getTypes() { return [SLM::TTO => __('Time to own'), SLM::TTR => __('Time to resolve')]; } /** * Get types name * * @param integer $type * @return string name **/ static function getOneTypeName($type) { $types = self::getTypes(); $name = null; if (isset($types[$type])) { $name = $types[$type]; } return $name; } /** * Get SLA types dropdown * * @param array $options * * @return string */ static function getTypeDropdown($options) { $params = ['name' => 'type']; foreach ($options as $key => $val) { $params[$key] = $val; } return Dropdown::showFromArray($params['name'], self::getTypes(), $options); } function prepareInputForAdd($input) { if ($input['definition_time'] != 'day') { $input['end_of_working_day'] = 0; } return $input; } function prepareInputForUpdate($input) { if (isset($input['definition_time']) && $input['definition_time'] != 'day') { $input['end_of_working_day'] = 0; } return $input; } /** * Add a level to do for a ticket * * @param Ticket $ticket Ticket object * @param integer $levels_id SlaLevel or OlaLevel ID * * @return void **/ function addLevelToDo(Ticket $ticket, $levels_id = 0) { $pre = static::$prefix; if (!$levels_id && isset($ticket->fields[$pre.'levels_id_ttr'])) { $levels_id = $ticket->fields[$pre."levels_id_ttr"]; } if ($levels_id) { $toadd = []; $date = $this->computeExecutionDate($ticket->fields['date'], $levels_id, $ticket->fields[$pre.'_waiting_duration']); if ($date != null) { $toadd['date'] = $date; $toadd[$pre.'levels_id'] = $levels_id; $toadd['tickets_id'] = $ticket->fields["id"]; $levelticket = new static::$levelticketclass(); $levelticket->add($toadd); } } } /** * remove a level to do for a ticket * * @param $ticket Ticket object * * @return void **/ static function deleteLevelsToDo(Ticket $ticket) { global $DB; $ticketfield = static::$prefix."levels_id_ttr"; if ($ticket->fields[$ticketfield] > 0) { $levelticket = new static::$levelticketclass(); $iterator = $DB->request([ 'SELECT' => 'id', 'FROM' => $levelticket::getTable(), 'WHERE' => ['tickets_id' => $ticket->fields['id']] ]); while ($data = $iterator->next()) { $levelticket->delete(['id' => $data['id']]); } } } function cleanDBonPurge() { global $DB; // Clean levels $classname = get_called_class(); $fk = getForeignKeyFieldForItemType($classname); $level = new static::$levelclass(); $level->deleteByCriteria([$fk => $this->getID()]); // Update tickets : clean SLA/OLA list($dateField, $laField) = static::getFieldNames($this->fields['type']); $iterator = $DB->request([ 'SELECT' => 'id', 'FROM' => 'glpi_tickets', 'WHERE' => [$laField => $this->fields['id']] ]); if (count($iterator)) { $ticket = new Ticket(); while ($data = $iterator->next()) { $ticket->deleteLevelAgreement($classname, $data['id'], $this->fields['type']); } } Rule::cleanForItemAction($this); } }