Files
MYSOPHAL/inc/ticketrecurrent.class.php
2025-08-07 13:15:31 +01:00

584 lines
20 KiB
PHP

<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2020 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* Ticket Recurrent class
*
* @since 0.83
**/
class TicketRecurrent extends CommonDropdown {
// From CommonDBTM
public $dohistory = true;
// From CommonDropdown
public $first_level_menu = "helpdesk";
public $second_level_menu = "ticketrecurrent";
public $display_dropdowntitle = false;
static $rightname = 'ticketrecurrent';
public $can_be_translated = false;
static function getTypeName($nb = 0) {
return __('Recurrent tickets');
}
static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
switch ($item->getType()) {
case 'TicketRecurrent' :
switch ($tabnum) {
case 1 :
$item->showInfos();
return true;
}
break;
}
return false;
}
function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
if (Session::haveRight('itiltemplate', READ)) {
switch ($item->getType()) {
case 'TicketRecurrent' :
$ong[1] = _n('Information', 'Information', Session::getPluralNumber());
return $ong;
}
}
return '';
}
function defineTabs($options = []) {
$ong = [];
$this->addDefaultFormTab($ong);
$this->addStandardTab(__CLASS__, $ong, $options);
$this->addStandardTab('Log', $ong, $options);
return $ong;
}
function prepareInputForAdd($input) {
$input['next_creation_date'] = $this->computeNextCreationDate($input['begin_date'],
$input['end_date'],
$input['periodicity'],
$input['create_before'],
$input['calendars_id']);
return $input;
}
function prepareInputForUpdate($input) {
if (isset($input['begin_date'])
&& isset($input['periodicity'])
&& isset($input['create_before'])) {
$input['next_creation_date'] = $this->computeNextCreationDate($input['begin_date'],
$input['end_date'],
$input['periodicity'],
$input['create_before'],
$input['calendars_id']);
}
return $input;
}
/**
* Return Additional Fileds for this type
**/
function getAdditionalFields() {
return [['name' => 'is_active',
'label' => __('Active'),
'type' => 'bool',
'list' => false],
['name' => 'tickettemplates_id',
'label' => _n('Ticket template', 'Ticket templates', 1),
'type' => 'dropdownValue',
'list' => true],
['name' => 'begin_date',
'label' => __('Start date'),
'type' => 'datetime',
'list' => false],
['name' => 'end_date',
'label' => __('End date'),
'type' => 'datetime',
'list' => false],
['name' => 'periodicity',
'label' => __('Periodicity'),
'type' => 'specific_timestamp',
'min' => DAY_TIMESTAMP,
'step' => DAY_TIMESTAMP,
'max' => 2*MONTH_TIMESTAMP],
['name' => 'create_before',
'label' => __('Preliminary creation'),
'type' => 'timestamp',
'max' => 7*DAY_TIMESTAMP,
'step' => HOUR_TIMESTAMP],
['name' => 'calendars_id',
'label' => _n('Calendar', 'Calendars', 1),
'type' => 'dropdownValue',
'list' => true],
];
}
/**
* @since 0.83.1
*
* @see CommonDropdown::displaySpecificTypeField()
**/
function displaySpecificTypeField($ID, $field = []) {
switch ($field['name']) {
case 'periodicity' :
$possible_values = [];
for ($i=1; $i<24; $i++) {
$possible_values[$i*HOUR_TIMESTAMP] = sprintf(_n('%d hour', '%d hours', $i), $i);
}
for ($i=1; $i<=30; $i++) {
$possible_values[$i*DAY_TIMESTAMP] = sprintf(_n('%d day', '%d days', $i), $i);
}
for ($i=1; $i<12; $i++) {
$possible_values[$i.'MONTH'] = sprintf(_n('%d month', '%d months', $i), $i);
}
for ($i=1; $i<11; $i++) {
$possible_values[$i.'YEAR'] = sprintf(_n('%d year', '%d years', $i), $i);
}
Dropdown::showFromArray($field['name'], $possible_values,
['value' => $this->fields[$field['name']]]);
break;
}
}
/**
* @since 0.84
*
* @param $field
* @param $values
* @param $options array
**/
static function getSpecificValueToDisplay($field, $values, array $options = []) {
if (!is_array($values)) {
$values = [$field => $values];
}
switch ($field) {
case 'periodicity' :
if (preg_match('/([0-9]+)MONTH/', $values[$field], $matches)) {
return sprintf(_n('%d month', '%d months', $matches[1]), $matches[1]);
}
if (preg_match('/([0-9]+)YEAR/', $values[$field], $matches)) {
return sprintf(_n('%d year', '%d years', $matches[1]), $matches[1]);
}
return Html::timestampToString($values[$field], false);
break;
}
return parent::getSpecificValueToDisplay($field, $values, $options);
}
function rawSearchOptions() {
$tab = parent::rawSearchOptions();
$tab[] = [
'id' => '11',
'table' => $this->getTable(),
'field' => 'is_active',
'name' => __('Active'),
'datatype' => 'bool'
];
$tab[] = [
'id' => '12',
'table' => 'glpi_tickettemplates',
'field' => 'name',
'name' => _n('Ticket template', 'Ticket templates', 1),
'datatype' => 'itemlink'
];
$tab[] = [
'id' => '13',
'table' => $this->getTable(),
'field' => 'begin_date',
'name' => __('Start date'),
'datatype' => 'datetime'
];
$tab[] = [
'id' => '17',
'table' => $this->getTable(),
'field' => 'end_date',
'name' => __('End date'),
'datatype' => 'datetime'
];
$tab[] = [
'id' => '15',
'table' => $this->getTable(),
'field' => 'periodicity',
'name' => __('Periodicity'),
'datatype' => 'specific'
];
$tab[] = [
'id' => '14',
'table' => $this->getTable(),
'field' => 'create_before',
'name' => __('Preliminary creation'),
'datatype' => 'timestamp'
];
$tab[] = [
'id' => '18',
'table' => 'glpi_calendars',
'field' => 'name',
'name' => _n('Calendar', 'Calendars', 1),
'datatype' => 'itemlink'
];
return $tab;
}
/**
* Show next creation date
*
* @return void
**/
function showInfos() {
if (!is_null($this->fields['next_creation_date'])) {
echo "<div class='center'>";
//TRANS: %s is the date of next creation
echo sprintf(__('Next creation on %s'),
Html::convDateTime($this->fields['next_creation_date']));
echo "</div>";
}
}
/**
* Compute next creation date of a ticket.
*
* @param string $begin_date Begin date of the recurrent ticket in 'Y-m-d H:i:s' format.
* @param string $end_date End date of the recurrent ticket in 'Y-m-d H:i:s' format,
* or 'NULL' or empty value.
* @param string|integer $periodicity Periodicity of creation, could be:
* - an integer corresponding to seconds,
* - a string using "/([0-9]+)(MONTH|YEAR)/" pattern.
* @param integer $create_before Anticipated creation delay in seconds.
* @param integer|null $calendars_id ID of the calendar to use to restrict creation to working hours,
* or 0 / null for no calendar.
*
* @return string Next creation date in 'Y-m-d H:i:s' format.
*
* @since 0.84 $calendars_id parameter added
*/
function computeNextCreationDate($begin_date, $end_date, $periodicity, $create_before,
$calendars_id) {
$now = time();
$periodicity_pattern = '/([0-9]+)(MONTH|YEAR)/';
if (false === DateTime::createFromFormat('Y-m-d H:i:s', $begin_date)) {
// Invalid begin date.
return 'NULL';
}
$has_end_date = false !== DateTime::createFromFormat('Y-m-d H:i:s', $end_date);
if ($has_end_date && strtotime($end_date) < $now) {
// End date is in past.
return 'NULL';
}
if (!is_int($periodicity) && !preg_match('/^\d+$/', $periodicity)
&& !preg_match($periodicity_pattern, $periodicity)) {
// Invalid periodicity.
return 'NULL';
}
// Compute periodicity values
$periodicity_as_interval = null;
$periodicity_in_seconds = $periodicity;
$matches = [];
if (preg_match($periodicity_pattern, $periodicity, $matches)) {
$periodicity_as_interval = "{$matches[1]} {$matches[2]}";
$periodicity_in_seconds = $matches[1]
* MONTH_TIMESTAMP
* ('YEAR' === $matches[2] ? 12 : 1);
} else if ($periodicity % DAY_TIMESTAMP == 0) {
$periodicity_as_interval = ($periodicity / DAY_TIMESTAMP) . ' DAY';
} else {
$periodicity_as_interval = ($periodicity / HOUR_TIMESTAMP) . ' HOUR';
}
// Check that anticipated creation delay is greater than periodicity.
if ($create_before > $periodicity_in_seconds) {
Session::addMessageAfterRedirect(
__('Invalid frequency. It must be greater than the preliminary creation.'),
false,
ERROR
);
return 'NULL';
}
$calendar = new Calendar();
$is_calendar_valid = $calendars_id && $calendar->getFromDB($calendars_id) && $calendar->hasAWorkingDay();
if (!$is_calendar_valid || $periodicity_in_seconds >= DAY_TIMESTAMP) {
// Compute next occurence without using the calendar if calendar is not valid
// or if periodicity is at least one day.
// First occurence of creation
$occurence_time = strtotime($begin_date);
$creation_time = $occurence_time - $create_before;
// Add steps while creation time is in past
while ($creation_time < $now) {
$creation_time = strtotime("+ $periodicity_as_interval", $creation_time);
$occurence_time = $creation_time + $create_before;
// Stop if end date reached
if ($has_end_date && $occurence_time > strtotime($end_date)) {
return 'NULL';
}
}
if ($is_calendar_valid) {
// Jump to next working day if occurence is outside working days.
while ($calendar->isHoliday(date('Y-m-d', $occurence_time))
|| !$calendar->isAWorkingDay($occurence_time)) {
$occurence_time = strtotime('+ 1 day', $occurence_time);
}
// Jump to next working hour if occurence is outside working hours.
if (!$calendar->isAWorkingHour($occurence_time)) {
$occurence_date = $calendar->computeEndDate(
date('Y-m-d', $occurence_time),
0 // 0 second delay to get the first working "second"
);
$occurence_time = strtotime($occurence_date);
}
$creation_time = $occurence_time - $create_before;
}
} else {
// Base computation on calendar if calendar is valid
$occurence_date = $calendar->computeEndDate(
$begin_date,
0 // 0 second delay to get the first working "second"
);
$occurence_time = strtotime($occurence_date);
$creation_time = $occurence_time - $create_before;
while ($creation_time < $now) {
$occurence_date = $calendar->computeEndDate(
date('Y-m-d H:i:s', $occurence_time),
$periodicity_in_seconds,
0,
$periodicity_in_seconds >= DAY_TIMESTAMP
);
$occurence_time = strtotime($occurence_date);
$creation_time = $occurence_time - $create_before;
// Stop if end date reached
if ($has_end_date && $occurence_time > strtotime($end_date)) {
return 'NULL';
}
};
}
return date("Y-m-d H:i:s", $creation_time);
}
/**
* Give cron information
*
* @param $name : task's name
*
* @return array of information
**/
static function cronInfo($name) {
switch ($name) {
case 'ticketrecurrent' :
return ['description' => self::getTypeName(Session::getPluralNumber())];
}
return [];
}
/**
* Cron for ticket's automatic close
*
* @param $task : crontask object
*
* @return integer (0 : nothing done - 1 : done)
**/
static function cronTicketRecurrent($task) {
global $DB;
$tot = 0;
$iterator = $DB->request([
'FROM' => 'glpi_ticketrecurrents',
'WHERE' => [
'next_creation_date' => ['<', new \QueryExpression('NOW()')],
'is_active' => 1,
'OR' => [
['end_date' => null],
['end_date' => ['>', new \QueryExpression('NOW()')]]
]
]
]);
while ($data = $iterator->next()) {
if (self::createTicket($data)) {
$tot++;
} else {
//TRANS: %s is a name
$task->log(sprintf(__('Failed to create recurrent ticket %s'),
$data['name']));
}
}
$task->setVolume($tot);
return ($tot > 0 ? 1 : 0);
}
/**
* Create a ticket based on ticket recurrent infos
*
* @param $data array data of a entry of glpi_ticketrecurrents
*
* @return boolean
**/
static function createTicket($data) {
$result = false;
$tt = new TicketTemplate();
// Create ticket based on ticket template and entity information of ticketrecurrent
if ($tt->getFromDB($data['tickettemplates_id'])) {
// Get default values for ticket
$input = Ticket::getDefaultValues($data['entities_id']);
// Apply itiltemplates predefined values
$ttp = new TicketTemplatePredefinedField();
$predefined = $ttp->getPredefinedFields($data['tickettemplates_id'], true);
if (count($predefined)) {
foreach ($predefined as $predeffield => $predefvalue) {
$input[$predeffield] = $predefvalue;
}
}
// Set date to creation date
$createtime = strtotime($data['next_creation_date'])+$data['create_before'];
$input['date'] = date('Y-m-d H:i:s', $createtime);
if (isset($predefined['date'])) {
$input['date'] = Html::computeGenericDateTimeSearch($predefined['date'], false,
$createtime);
}
// Compute time_to_resolve if predefined based on create date
if (isset($predefined['time_to_resolve'])) {
$input['time_to_resolve'] = Html::computeGenericDateTimeSearch($predefined['time_to_resolve'], false,
$createtime);
}
// Compute internal_time_to_resolve if predefined based on create date
if (isset($predefined['internal_time_to_resolve'])) {
$input['internal_time_to_resolve'] = Html::computeGenericDateTimeSearch($predefined['internal_time_to_resolve'], false,
$createtime);
}
// Set entity
$input['entities_id'] = $data['entities_id'];
$input['_auto_import'] = true;
$ticket = new Ticket();
$input = Toolbox::addslashes_deep($input);
if ($tid = $ticket->add($input)) {
$msg = sprintf(__('Ticket %d successfully created'), $tid);
$result = true;
} else {
$msg = __('Ticket creation failed (check mandatory fields)');
}
} else {
$msg = __('Ticket creation failed (no template)');
}
$changes[0] = 0;
$changes[1] = '';
$changes[2] = addslashes($msg);
Log::history($data['id'], __CLASS__, $changes, '', Log::HISTORY_LOG_SIMPLE_MESSAGE);
// Compute next creation date
$tr = new self();
if ($tr->getFromDB($data['id'])) {
$input = [];
$input['id'] = $data['id'];
$input['next_creation_date'] = $tr->computeNextCreationDate($data['begin_date'],
$data['end_date'],
$data['periodicity'],
$data['create_before'],
$data['calendars_id']);
$tr->update($input);
}
return $result;
}
static function getIcon() {
return "fas fa-stopwatch";
}
}