.
* ---------------------------------------------------------------------
*/
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 " |