Ganteng Doang Upload Shell Gak Bisa


Linux server.jmdstrack.com 3.10.0-1160.119.1.el7.tuxcare.els10.x86_64 #1 SMP Fri Oct 11 21:40:41 UTC 2024 x86_64
/ home/ jmdstrac/ public_html/ devices/ src/

/home/jmdstrac/public_html/devices/src/CommonITILObject.php

<?php

/**
 * ---------------------------------------------------------------------
 *
 * GLPI - Gestionnaire Libre de Parc Informatique
 *
 * http://glpi-project.org
 *
 * @copyright 2015-2023 Teclib' and contributors.
 * @copyright 2003-2014 by the INDEPNET Development Team.
 * @licence   https://www.gnu.org/licenses/gpl-3.0.html
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * ---------------------------------------------------------------------
 */

use Glpi\Application\View\TemplateRenderer;
use Glpi\Event;
use Glpi\Plugin\Hooks;
use Glpi\RichText\RichText;
use Glpi\Team\Team;
use Glpi\Toolbox\Sanitizer;

/**
 * CommonITILObject Class
 *
 * @property-read array $users
 * @property-read array $groups
 * @property-read array $suppliers
 **/
abstract class CommonITILObject extends CommonDBTM
{
    use \Glpi\Features\Clonable;
    use \Glpi\Features\Timeline;
    use \Glpi\Features\Kanban;
    use Glpi\Features\Teamwork;

   /// Users by type
    protected $lazy_loaded_users = null;
    public $userlinkclass  = '';
   /// Groups by type
    protected $lazy_loaded_groups = null;
    public $grouplinkclass = '';

   /// Suppliers by type
    protected $lazy_loaded_suppliers = null;
    public $supplierlinkclass = '';

   /// Use user entity to select entity of the object
    protected $userentity_oncreate = false;

    public $deduplicate_queued_notifications = false;

    protected static $showTitleInNavigationHeader = true;

    const MATRIX_FIELD         = '';
    const URGENCY_MASK_FIELD   = '';
    const IMPACT_MASK_FIELD    = '';
    const STATUS_MATRIX_FIELD  = '';


   // STATUS
    const INCOMING      = 1; // new
    const ASSIGNED      = 2; // assign
    const PLANNED       = 3; // plan
    const WAITING       = 4; // waiting
    const SOLVED        = 5; // solved
    const CLOSED        = 6; // closed
    const ACCEPTED      = 7; // accepted
    const OBSERVED      = 8; // observe
    const EVALUATION    = 9; // evaluation
    const APPROVAL      = 10; // approbation
    const TEST          = 11; // test
    const QUALIFICATION = 12; // qualification

    const NO_TIMELINE       = -1;
    const TIMELINE_NOTSET   = 0;
    const TIMELINE_LEFT     = 1;
    const TIMELINE_MIDLEFT  = 2;
    const TIMELINE_MIDRIGHT = 3;
    const TIMELINE_RIGHT    = 4;

    const TIMELINE_ORDER_NATURAL = 'natural';
    const TIMELINE_ORDER_REVERSE = 'reverse';

    abstract public static function getTaskClass();

    public function post_getFromDB()
    {
        // Object may be reused to load multiples tickets thus we must clear all
        // cached data when a new mysql row is loaded
        $this->clearLazyLoadedActors();
    }

    /**
     * Load linked users
     *
     * @return void
     */
    public function loadUsers(): void
    {
        if (!empty($this->userlinkclass) && isset($this->fields['id'])) {
            $class = new $this->userlinkclass();
            $this->lazy_loaded_users = $class->getActors($this->fields['id']);
        } else {
            $this->lazy_loaded_users = [];
        }
    }

    /**
     * Load linked groups
     *
     * @return void
     */
    protected function loadGroups(): void
    {
        if (!empty($this->grouplinkclass) && isset($this->fields['id'])) {
            $class = new $this->grouplinkclass();
            $this->lazy_loaded_groups = $class->getActors($this->fields['id']);
        } else {
            $this->lazy_loaded_groups = [];
        }
    }

    /**
     * Load linked suppliers
     *
     * @return void
     */
    public function loadSuppliers(): void
    {
        if (!empty($this->supplierlinkclass) && isset($this->fields['id'])) {
            $class = new $this->supplierlinkclass();
            $this->lazy_loaded_suppliers = $class->getActors($this->fields['id']);
        } else {
            $this->lazy_loaded_suppliers = [];
        }
    }

    /**
     * @since 0.84
     **/
    public function loadActors()
    {
        // TODO 10.1 (breaking change): method should be protected instead of public

        // Might not be 100% needed to clear cache here but lets be safe
        // This way, any direct call to loadActors is assured to return accurate data
        $this->clearLazyLoadedActors();

        // Load each actors type
        $this->loadUsers();
        $this->loadGroups();
        $this->loadSuppliers();
    }

    /**
     * Clear lazy loaded actor data so it can be recomputed again next time its
     * accessed
     *
     * @return void
     */
    protected function clearLazyLoadedActors(): void
    {
        $this->lazy_loaded_users = null;
        $this->lazy_loaded_groups = null;
        $this->lazy_loaded_suppliers = null;
    }

    /**
     * Magic getter for lazy loaded properties
     *
     * @param string $property_name
     */
    public function __get(string $property_name)
    {
        switch ($property_name) {
            case 'users':
                if ($this->lazy_loaded_users === null) {
                    $this->loadUsers();
                }
                return $this->lazy_loaded_users;

            case 'groups':
                if ($this->lazy_loaded_groups === null) {
                    $this->loadGroups();
                }
                return $this->lazy_loaded_groups;

            case 'suppliers':
                if ($this->lazy_loaded_suppliers === null) {
                    $this->loadSuppliers();
                }
                return $this->lazy_loaded_suppliers;

            default:
                // Log error and keep running
                // TODO 10.1: throw exception instead
                trigger_error("Unknown field: '$property_name'", E_USER_WARNING);
                return null;
        }
    }

    /**
     * Magic setter for lazy loaded properties
     *
     * @param string $property_name
     * @param mixed $value
     */
    public function __set(string $property_name, $value)
    {
        switch ($property_name) {
            case 'users':
            case 'groups':
            case 'suppliers':
                // Log error and keep running
                // TODO 10.1: throw exception instead
                trigger_error("Readonly field: '$property_name'", E_USER_WARNING);
                break;

            default:
                if (version_compare(PHP_VERSION, '8.2.0', '<')) {
                    // Trigger same deprecation notice as the one triggered by PHP 8.2+
                    trigger_error(
                        sprintf('Creation of dynamic property %s::$%s is deprecated', get_called_class(), $property_name),
                        E_USER_DEPRECATED
                    );
                }
                $this->$property_name = $value;
                break;
        }
    }

    /**
     * Magic handler for isset() calls on lazy loaded properties
     *
     * @param string $property_name
     */
    public function __isset(string $property_name)
    {
        switch ($property_name) {
            case 'users':
            case 'groups':
            case 'suppliers':
                return true;

            default:
                // Log error and keep running
                // TODO 10.1: throw exception instead
                trigger_error("Unknown field: '$property_name'", E_USER_WARNING);
                return false;
        }
    }

    /**
     * Magic handler for unset() calls on lazy loaded properties
     *
     * @param string $property_name
     */
    public function __unset(string $property_name)
    {
        switch ($property_name) {
            case 'users':
                $this->lazy_loaded_users = null;
                break;

            case 'groups':
                $this->lazy_loaded_groups = null;
                break;

            case 'suppliers':
                $this->lazy_loaded_suppliers = null;
                break;

            default:
                // Log error and keep running
                // TODO 10.1: throw exception instead
                trigger_error("Unknown field: '$property_name'", E_USER_WARNING);
                break;
        }
    }

    /**
     * Return the number of actors currently assigned to the object
     *
     * @since 10.0
     *
     * @return int
     */
    public function countActors(): int
    {
        return $this->countGroups() + $this->countUsers() + $this->countSuppliers();
    }


    /**
     * Return the list of actors for a given actor type
     * We try to retrieve them by:
     * - in case new ticket
     *  - from virtual _actor field (present after a reload)
     *  - from template (predefined actor field)
     *  - from default actor if setting is defined in user preference
     * - for existing ticket (with an id > 0), directly from saved actors
     *
     * @since 10.0
     *
     * @param int $actortype 1=requester, 2=assign, 3=observer
     * @param array $params posted data of itil object
     *
     * @return array of actors
     */
    public function getActorsForType(int $actortype = 1, array $params = []): array
    {
        $actors = [];

        $actortypestring = self::getActorFieldNameType($actortype);

        if ($this->isNewItem()) {
            $entities_id = $params['entities_id'] ?? $_SESSION['glpiactive_entity'];
            $default_use_notif = Entity::getUsedConfig('is_notif_enable_default', $entities_id, '', 1);

            // load default user from preference only at the first load of new ticket form
            // we don't want to trigger it on form reload
            // at first load, the key _skip_default_actor is not present (can only be present after a submit)
            if (!isset($params['_skip_default_actor'])) {
                // $params['_users_id_' . $actortypestring] corresponds to value defined by static::getDefaultValues()
                // fallback to static::getDefaultActor() value if empty
                $users_id_default = array_key_exists('_users_id_' . $actortypestring, $params) && $params['_users_id_' . $actortypestring] > 0
                    ? $params['_users_id_' . $actortypestring]
                    : $this->getDefaultActor($actortype);
                if ($users_id_default > 0) {
                    $userobj  = new User();
                    if ($userobj->getFromDB($users_id_default)) {
                        $name = formatUserName(
                            $userobj->fields["id"],
                            $userobj->fields["name"],
                            $userobj->fields["realname"],
                            $userobj->fields["firstname"]
                        );
                        $email = UserEmail::getDefaultForUser($users_id_default);
                        $actors[] = [
                            'items_id'          => $users_id_default,
                            'itemtype'          => 'User',
                            'text'              => $name,
                            'title'             => $name,
                            'use_notification'  => $email === '' ? false : $default_use_notif,
                            'default_email'     => $email,
                            'alternative_email' => '',
                        ];
                    }
                }
            }

            // load default actors from itiltemplate passed from showForm in `params` var
            // we find this key on the first load of template (when opening form)
            // or when the template change (by category loading)
            if (isset($params['_template_changed'])) {
                $users_id = (int) ($params['_predefined_fields']['_users_id_' . $actortypestring] ?? 0);
                if ($users_id > 0) {
                    $userobj  = new User();
                    if ($userobj->getFromDB($users_id)) {
                        $name = formatUserName(
                            $userobj->fields["id"],
                            $userobj->fields["name"],
                            $userobj->fields["realname"],
                            $userobj->fields["firstname"]
                        );
                        $email = UserEmail::getDefaultForUser($users_id);
                        $actors[] = [
                            'items_id'          => $users_id,
                            'itemtype'          => 'User',
                            'text'              => $name,
                            'title'             => $name,
                            'use_notification'  => $email === '' ? false : $default_use_notif,
                            'default_email'     => $email,
                            'alternative_email' => '',
                        ];
                    }
                }

                $groups_id = (int) ($params['_predefined_fields']['_groups_id_' . $actortypestring] ?? 0);
                if ($groups_id > 0) {
                    $group_obj = new Group();
                    if ($group_obj->getFromDB($groups_id)) {
                        $actors[] = [
                            'items_id' => $group_obj->fields['id'],
                            'itemtype' => 'Group',
                            'text'     => CommonTreeDropdown::sanitizeSeparatorInCompletename($group_obj->getName()),
                            'title'    => CommonTreeDropdown::sanitizeSeparatorInCompletename($group_obj->getRawCompleteName()),
                        ];
                    }
                }

                $suppliers_id = (int) ($params['_predefined_fields']['_suppliers_id_' . $actortypestring] ?? 0);
                if ($suppliers_id > 0) {
                    $supplier_obj = new Supplier();
                    if ($supplier_obj->getFromDB($suppliers_id)) {
                        $actors[] = [
                            'items_id'          => $supplier_obj->fields['id'],
                            'itemtype'          => 'Supplier',
                            'text'              => $supplier_obj->fields['name'],
                            'title'             => $supplier_obj->fields['name'],
                            'use_notification'  => $supplier_obj->fields['email'] === '' ? false : $default_use_notif,
                            'default_email'     => $supplier_obj->fields['email'],
                            'alternative_email' => '',
                        ];
                    }
                }
            }

            // if we load any actor from _itemtype_actortype_id, we are loading template,
            // and so we don't want more actors.
            // if any actor exists and was absent in a field from template, it will be loaded by the POST data.
            // we choose to erase existing actors for any defined in the template.
            if (count($actors)) {
                return $actors;
            }

            // existing actors (from a form reload)
            if (isset($params['_actors'])) {
                foreach ($params['_actors'] as $existing_actortype => $existing_actors) {
                    if ($existing_actortype != $actortypestring) {
                        continue;
                    }
                    foreach ($existing_actors as &$existing_actor) {
                        $actor_obj = new $existing_actor['itemtype']();
                        if ($actor_obj->getFromDB($existing_actor['items_id'])) {
                            if ($actor_obj instanceof User) {
                                $name = formatUserName(
                                    $actor_obj->fields["id"],
                                    $actor_obj->fields["name"],
                                    $actor_obj->fields["realname"],
                                    $actor_obj->fields["firstname"]
                                );
                                $actors[] = $existing_actor + [
                                    'text'          => $name,
                                    'title'         => $name,
                                    'default_email' => UserEmail::getDefaultForUser($actor_obj->fields["id"]),
                                ];
                            } elseif ($actor_obj instanceof Supplier) {
                                $actors[] = $existing_actor + [
                                    'text'          => $actor_obj->fields['name'],
                                    'title'         => $actor_obj->fields['name'],
                                    'default_email' => $actor_obj->fields['email'],
                                ];
                            } elseif ($actor_obj instanceof CommonTreeDropdown) {
                                // Group
                                $actors[] = $existing_actor + [
                                    'text'  => CommonTreeDropdown::sanitizeSeparatorInCompletename($actor_obj->getName()),
                                    'title' => CommonTreeDropdown::sanitizeSeparatorInCompletename($actor_obj->getRawCompleteName()),
                                ];
                            } else {
                                $actors[] = $existing_actor + [
                                    'text'  => $actor_obj->getName(),
                                    'title' => $actor_obj->getRawCompleteName(),
                                ];
                            }
                        } elseif (
                            $actor_obj instanceof User
                            && $existing_actor['items_id'] == 0
                            && strlen($existing_actor['alternative_email']) > 0
                        ) {
                            // direct mail actor
                            $actors[] = $existing_actor + [
                                'text'  => $existing_actor['alternative_email'],
                                'title' => $existing_actor['alternative_email'],
                            ];
                        }
                    }
                }
                return $actors;
            }
        }

       // load existing actors (from existing itilobject)
        if (isset($this->users[$actortype])) {
            foreach ($this->users[$actortype] as $user) {
                $name = getUserName($user['users_id']);
                $actors[] = [
                    'id'                => $user['id'],
                    'items_id'          => $user['users_id'],
                    'itemtype'          => 'User',
                    'text'              => $name,
                    'title'             => $name,
                    'use_notification'  => $user['use_notification'],
                    'default_email'     => UserEmail::getDefaultForUser($user['users_id']),
                    'alternative_email' => $user['alternative_email'],
                ];
            }
        }
        if (isset($this->groups[$actortype])) {
            foreach ($this->groups[$actortype] as $group) {
                $group_obj = new Group();
                if ($group_obj->getFromDB($group['groups_id'])) {
                    $actors[] = [
                        'id'       => $group['id'],
                        'items_id' => $group['groups_id'],
                        'itemtype' => 'Group',
                        'text'     => CommonTreeDropdown::sanitizeSeparatorInCompletename($group_obj->getName()),
                        'title'    => CommonTreeDropdown::sanitizeSeparatorInCompletename($group_obj->getRawCompleteName()),
                    ];
                }
            }
        }
        if (isset($this->suppliers[$actortype])) {
            foreach ($this->suppliers[$actortype] as $supplier) {
                $supplier_obj = new Supplier();
                if ($supplier_obj->getFromDB($supplier['suppliers_id'])) {
                    $actors[] = [
                        'id'                => $supplier['id'],
                        'items_id'          => $supplier['suppliers_id'],
                        'itemtype'          => 'Supplier',
                        'text'              => $supplier_obj->fields['name'],
                        'title'             => $supplier_obj->fields['name'],
                        'use_notification'  => $supplier['use_notification'],
                        'default_email'     => $supplier_obj->fields['email'],
                        'alternative_email' => $supplier['alternative_email'],
                    ];
                }
            }
        }

        return $actors;
    }

    /**
     * Restores input, restores saved values, then sets the default options for any that are missing.
     * @param integer $ID The item ID
     * @param array $options ITIL Object options array passed to showFormXXXX functions. This is passed by reference and will be modified by this function.
     * @param ?array $overriden_defaults If specified, these values will be used as the defaults instead of the ones from the {@link getDefaultValues()} function.
     * @param bool $force_set_defaults If true, the defaults are set for missing options even if the item is not new.
     * @return void
     * @see getDefaultOptions()
     * @see restoreInput()
     * @see restoreSavedValues()
     */
    protected function restoreInputAndDefaults($ID, array &$options, ?array $overriden_defaults = null, bool $force_set_defaults = false): void
    {
        $default_values = $overriden_defaults ?? static::getDefaultValues();

        // Restore saved value or override with page parameter
        $options['_saved'] = $this->restoreInput();

        // Restore saved values and override $this->fields
        $this->restoreSavedValues($options['_saved']);

        // Set default options
        if ($force_set_defaults || static::isNewID($ID)) {
            foreach ($default_values as $key => $val) {
                if (!isset($options[$key])) {
                    if (isset($options['_saved'][$key])) {
                        $options[$key] = $options['_saved'][$key];
                    } else {
                        $options[$key] = $val;
                    }
                }
            }
        }
    }

    /**
     * @param $ID
     * @param $options   array
     **/
    public function showForm($ID, array $options = [])
    {
        if (!static::canView()) {
            return false;
        }

        $this->restoreInputAndDefaults($ID, $options);

        $canupdate = !$ID || (Session::getCurrentInterface() == "central" && $this->canUpdateItem());

        if ($ID && in_array($this->fields['status'], $this->getClosedStatusArray())) {
            $canupdate = false;
            // No update for actors
            $options['_noupdate'] = true;
        }

        if (!$this->isNewItem()) {
            $options['formtitle'] = sprintf(
                __('%1$s - ID %2$d'),
                $this->getTypeName(1),
                $ID
            );
            //set ID as already defined
            $options['noid'] = true;
        }

        $type = null;
        if (is_a($this, Ticket::class)) {
            $type = ($ID ? $this->fields['type'] : $options['type']);
        }
        // Load template if available
        $tt = $this->getITILTemplateToUse(
            $options['template_preview'] ?? 0,
            $type,
            ($ID ? $this->fields['itilcategories_id'] : $options['itilcategories_id']),
            ($ID ? $this->fields['entities_id'] : $options['entities_id'])
        );

        $predefined_fields = $this->setPredefinedFields($tt, $options, static::getDefaultValues());
        $this->initForm($this->fields['id'], $options);

        TemplateRenderer::getInstance()->display('components/itilobject/layout.html.twig', [
            'item'                    => $this,
            'timeline_itemtypes'      => $this->getTimelineItemtypes(),
            'legacy_timeline_actions' => $this->getLegacyTimelineActionsHTML(),
            'params'                  => $options,
            'entities_id'             => $ID ? $this->fields['entities_id'] : $options['entities_id'],
            'timeline'                => $this->getTimelineItems(),
            'itiltemplate_key'        => static::getTemplateFormFieldName(),
            'itiltemplate'            => $tt,
            'predefined_fields'       => Toolbox::prepareArrayForInput($predefined_fields),
            'canupdate'               => $canupdate,
            'canpriority'             => $canupdate,
            'canassign'               => $canupdate,
            'has_pending_reason'      => PendingReason_Item::getForItem($this) !== false,
        ]);

        return true;
    }

    /**
     * Return an array of predefined fields from provided template
     * Append also data to $options param (passed by reference) :
     *  - if we transform a ticket (form change and problem) or a problem (for change) override with its field
     *  - override form fields from template (ex: if content field is set in template, content field in option will be overriden)
     *  - if template changed (provided template doesn't match the one found in options), append a key _template_changed in $options
     *  - finally, append templates_id in options
     *
     * @param ITILTemplate $tt The ticket template to use
     * @param array $options The current options array (PASSED BY REFERENCE)
     * @param array $default_values The default values to use in case they are not predefined
     * @return array An array of the predefined values
     */
    protected function setPredefinedFields(ITILTemplate $tt, array &$options, array $default_values): array
    {
        // Predefined fields from template : reset them
        if (isset($options['_predefined_fields'])) {
            $options['_predefined_fields'] = Toolbox::decodeArrayFromInput($options['_predefined_fields']);
        } else {
            $options['_predefined_fields'] = [];
        }
        if (!isset($options['_hidden_fields'])) {
            $options['_hidden_fields'] = [];
        }

        // check original ticket for change and problem
        $tickets_id = $options['tickets_id'] ?? $options['_tickets_id'] ?? null;
        $ticket = new Ticket();
        $ticket->getEmpty();
        if (in_array($this->getType(), ['Change', 'Problem']) && $tickets_id) {
            $ticket->getFromDB($tickets_id);

            // copy fields from original ticket, only when fields are not already set by the user (contained in _saved array)
            $fields = [
                'content',
                'name',
                'impact',
                'urgency',
                'priority',
                'time_to_resolve',
                'entities_id',
            ];
            foreach ($fields as $field) {
                if (!isset($options['_saved'][$field])) {
                    $options[$field] = $ticket->fields[$field];
                }
            }

            if (!isset($options['_saved']['itilcategories_id'])) {
                //page is reloaded on category change, we only want category on the very first load
                $category = new ITILCategory();
                $options['itilcategories_id'] = 0;
                if (
                    $category->getFromDB($ticket->fields['itilcategories_id'])
                    && (
                        ($this->getType() === Change::class && $category->fields['is_change'])
                        || ($this->getType() === Problem::class && $category->fields['is_problem'])
                    )
                ) {
                    $options['itilcategories_id'] = $ticket->fields['itilcategories_id'];
                }
            }
        }

        // check original problem for change
        $problems_id = $options['problems_id'] ?? $options['_problems_id'] ?? null;
        $problem = new Problem();
        $problem->getEmpty();
        if ($this->getType() == "Change" && $problems_id) {
            $problem->getFromDB($problems_id);

            $options['content']             = $problem->fields['content'];
            $options['name']                = $problem->fields['name'];
            $options['impact']              = $problem->fields['impact'];
            $options['urgency']             = $problem->fields['urgency'];
            $options['priority']            = $problem->fields['priority'];
            if (isset($options['problems_id'])) {
                //page is reloaded on category change, we only want category on the very first load
                $options['itilcategories_id'] = $problem->fields['itilcategories_id'];
            }
            $options['time_to_resolve']     = $problem->fields['time_to_resolve'];
            $options['entities_id']         = $problem->fields['entities_id'];
        }

        // Store predefined fields to be able not to take into account on change template
        $predefined_fields = [];
        $tpl_key = static::getTemplateFormFieldName();

        if ($this->isNewItem()) {
            if (isset($tt->predefined) && count($tt->predefined)) {
                foreach ($tt->predefined as $predeffield => $predefvalue) {
                    if (isset($options[$predeffield]) && isset($default_values[$predeffield])) {
                        // Is always default value : not set
                        // Set if already predefined field
                        // Set if ticket template change
                        if (
                            ((count($options['_predefined_fields']) == 0)
                                && ($options[$predeffield] == $default_values[$predeffield]))
                            || (isset($options['_predefined_fields'][$predeffield])
                                && ($options[$predeffield] == $options['_predefined_fields'][$predeffield]))
                            || (isset($options[$tpl_key])
                                && ($options[$tpl_key] != $tt->getID()))

                            // user pref for requestype can't overwrite requestype from template
                            // when change category
                            || ($predeffield == 'requesttypes_id'
                                && empty(($options['_saved'] ?? [])))

                            // tests specificic for change & problem
                            || ($tickets_id != null
                                && $options[$predeffield] == $ticket->fields[$predeffield])
                            || ($problems_id != null
                                && $options[$predeffield] == $problem->fields[$predeffield])
                        ) {
                            $options[$predeffield]           = $predefvalue;
                            $predefined_fields[$predeffield] = $predefvalue;
                            $this->fields[$predeffield]      = $predefvalue;
                        }
                    } else { // Not defined options set as hidden field
                        $options['_hidden_fields'][$predeffield] = $predefvalue;
                    }
                }
                // All predefined override : add option to say predifined exists
                if (count($predefined_fields) == 0) {
                    $predefined_fields['_all_predefined_override'] = 1;
                }
            } else { // No template load : reset predefined values
                if (count($options['_predefined_fields'])) {
                    foreach ($options['_predefined_fields'] as $predeffield => $predefvalue) {
                        if ($options[$predeffield] == $predefvalue) {
                            $options[$predeffield] = $default_values[$predeffield];
                        }
                    }
                }
            }
        }
        // append to options to know later we added predefined values
        // we may need this especially for actors
        if (count($predefined_fields)) {
            $options['_predefined_fields'] = $predefined_fields;
        }

        // check if we load the default template (when openning form for example) or the template changed
        if (!isset($options[$tpl_key]) || $options[$tpl_key] != $tt->getId()) {
            $options['_template_changed'] = true;
        }

        // Put ticket template id on $options for actors
        $options[str_replace('s_id', '', $tpl_key)] = $tt->getId();

        // Add all values to fields of tickets for template preview
        if (($options['template_preview'] ?? false)) {
            foreach ($options as $key => $val) {
                if (!isset($this->fields[$key])) {
                    $this->fields[$key] = $val;
                }
            }
        }

        // Recompute priority if not predefined and impact/urgency was changed
        if (
            !isset($predefined_fields['priority'])
            && (
                isset($predefined_fields['urgency'])
                || isset($predefined_fields['impact'])
            )
        ) {
            $this->fields['priority'] = self::computePriority(
                $this->fields['urgency'] ?? 3,
                $this->fields['impact'] ?? 3
            );
        }

        return $predefined_fields;
    }


    /**
     * Retrieve all possible entities for an itilobject posted data.
     * We try to retrieve requesters in the data:
     * - from `_users_id_requester` (data from template or default actor)
     * - from `_actors` (virtual field when the form is reloaded)
     * By default, if none of these fields are present, entities are get from current active entity.
     *
     * @since 10.0
     *
     * @param array $params posted data by an itil object
     * @return array of possible entities_id
     */
    public function getEntitiesForRequesters(array $params = [])
    {
        $requesters = [];
        if (array_key_exists('_users_id_requester', $params) && !empty($params["_users_id_requester"])) {
            $requesters = !is_array($params["_users_id_requester"])
                ? [$params["_users_id_requester"]]
                : $params["_users_id_requester"];
        }
        if (isset($params['_actors']['requester'])) {
            foreach ($params['_actors']['requester'] as $actor) {
                if ($actor['itemtype'] == "User") {
                    $requesters[] = $actor['items_id'];
                }
            }
        }

        $entities = $_SESSION['glpiactiveentities'] ?? [];
        foreach ($requesters as $users_id) {
            $user_entities = Profile_User::getUserEntities($users_id, true, true);
            $entities = array_intersect($user_entities, $entities);
        }

        $entities = array_values($entities); // Ensure keys are starting at 0

        return $entities;
    }


    /**
     * Retrieve an item from the database with datas associated (hardwares)
     *
     * @param integer $ID ID of the item to get
     *
     * @return boolean true if succeed else false
     **/
    public function getFromDBwithData($ID)
    {

        if ($this->getFromDB($ID)) {
            $this->getAdditionalDatas();
            return true;
        }
        return false;
    }


    public function getAdditionalDatas()
    {
    }


    /**
     * Can manage actors
     *
     * @return boolean
     */
    public function canAdminActors()
    {
        if (isset($this->fields['is_deleted']) && $this->fields['is_deleted'] == 1) {
            return false;
        }
        return Session::haveRight(static::$rightname, UPDATE);
    }


    /**
     * Can assign object
     *
     * @return boolean
     */
    public 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, UPDATE);
    }


    /**
     * Can be assigned to me
     *
     * @return boolean
     */
    public 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(static::$rightname, UPDATE);
    }


    /**
     * Is the current user have right to approve solution of the current ITIL object.
     *
     * @since 9.4.0
     *
     * @return boolean
     */
    public function canApprove()
    {

        return (($this->fields["users_id_recipient"] === Session::getLoginUserID())
              || $this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID())
              || (isset($_SESSION["glpigroups"])
                  && $this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION["glpigroups"])));
    }

    /**
     * Is the current user have right to add followups to the current ITIL Object ?
     *
     * @since 9.4.0
     *
     * @return boolean
     */
    public function canAddFollowups()
    {
        return (
         (
            Session::haveRight("followup", ITILFollowup::ADDMYTICKET)
            && (
               $this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID())
               || (
                  isset($this->fields["users_id_recipient"])
                  && ($this->fields["users_id_recipient"] == Session::getLoginUserID())
               )
            )
         )
         || (
            Session::haveRight("followup", ITILFollowup::ADD_AS_OBSERVER)
            && $this->isUser(CommonITILActor::OBSERVER, Session::getLoginUserID())
         )
         || Session::haveRight('followup', ITILFollowup::ADDALLTICKET)
         || (
            Session::haveRight('followup', ITILFollowup::ADDGROUPTICKET)
            && isset($_SESSION["glpigroups"])
            && $this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION['glpigroups'])
         )
         || $this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID())
         || (
            isset($_SESSION["glpigroups"])
            && $this->haveAGroup(CommonITILActor::ASSIGN, $_SESSION['glpigroups'])
         )
         || $this->isValidator(Session::getLoginUserID())
        );
    }


    public function canMassiveAction($action, $field, $value)
    {

        switch ($action) {
            case 'update':
                switch ($field) {
                    case 'status':
                        if (!static::isAllowedStatus($this->fields['status'], $value)) {
                            return false;
                        }
                        break;
                }
                break;
        }
        return true;
    }


    /**
     * Do the current ItilObject need to be reopened by a requester answer
     *
     * @since 10.0.1
     *
     * @return boolean
     */
    public function needReopen(): bool
    {
        $my_id    = Session::getLoginUserID();
        $my_groups = $_SESSION["glpigroups"] ?? [];

        // Compute requester groups
        $requester_groups = array_filter(
            $my_groups,
            fn($group) => $this->isGroup(CommonITILActor::REQUESTER, $group)
        );

        // Compute assigned groups
        $assigned_groups = array_filter(
            $my_groups,
            fn($group) => $this->isGroup(CommonITILActor::ASSIGN, $group)
        );

        $ami_requester       = $this->isUser(CommonITILActor::REQUESTER, $my_id);
        $ami_requester_group = count($requester_groups) > 0;

        $ami_assignee        = $this->isUser(CommonITILActor::ASSIGN, $my_id);
        $ami_assignee_group  = count($assigned_groups) > 0;

        return in_array($this->fields["status"], static::getReopenableStatusArray())
            && ($ami_requester || $ami_requester_group)
            && !($ami_assignee || $ami_assignee_group);
    }

    /**
     * Check if the given users is a validator
     * @param int $users_id
     * @return bool
     */
    public function isValidator($users_id): bool
    {
        if (!$users_id) {
           // Invalid parameter
            return false;
        }

        if (!$this instanceof Ticket && !$this instanceof Change) {
           // Not a valid validation target
            return false;
        }

        $validation_class = static::class . "Validation";
        $valitation_obj = new $validation_class();
        $validation_requests = $valitation_obj->find([
            getForeignKeyFieldForItemType(static::class) => $this->getID(),
            'users_id_validate' => $users_id,
        ]);

        return count($validation_requests) > 0;
    }


    /**
     * Does current user have right to solve the current item?
     *
     * @return boolean
     **/
    public function canSolve()
    {

        return ((Session::haveRight(static::$rightname, UPDATE)
               || $this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID())
               || (isset($_SESSION["glpigroups"])
                   && $this->haveAGroup(CommonITILActor::ASSIGN, $_SESSION["glpigroups"])))
              && static::isAllowedStatus($this->fields['status'], self::SOLVED)
              // No edition on closed status
              && !in_array($this->fields['status'], $this->getClosedStatusArray()));
    }

    /**
     * Does current user have right to solve the current item; if it was not closed?
     *
     * @return boolean
     **/
    public function maySolve()
    {

        return ((Session::haveRight(static::$rightname, UPDATE)
               || $this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID())
               || (isset($_SESSION["glpigroups"])
                   && $this->haveAGroup(CommonITILActor::ASSIGN, $_SESSION["glpigroups"])))
              && static::isAllowedStatus($this->fields['status'], self::SOLVED));
    }


    /**
     * Get the ITIL object closed, solved or waiting status list
     *
     * @since 9.4.0
     *
     * @return array
     */
    public static function getReopenableStatusArray()
    {
        return [self::CLOSED, self::SOLVED, self::WAITING];
    }


    /**
     * Is a user linked to the object ?
     *
     * @param integer $type     type to search (see constants)
     * @param integer $users_id user ID
     *
     * @return boolean
     **/
    public function isUser($type, $users_id)
    {

        if (isset($this->users[$type])) {
            foreach ($this->users[$type] as $data) {
                if ($data['users_id'] == $users_id) {
                    return true;
                }
            }
        }

        return false;
    }


    /**
     * Is a group linked to the object ?
     *
     * @param int $type      type to search (see constants)
     * @param int $groups_id group ID
     *
     * @return bool
     **/
    // FIXME add params typehint in GLPI 10.1
    public function isGroup($type, $groups_id): bool
    {
        if (isset($this->groups[$type])) {
            foreach ($this->groups[$type] as $data) {
                if ($data['groups_id'] == $groups_id) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * Is a supplier linked to the object ?
     *
     * @since 0.84
     *
     * @param integer $type         type to search (see constants)
     * @param integer $suppliers_id supplier ID
     *
     * @return boolean
     **/
    public function isSupplier($type, $suppliers_id)
    {

        if (isset($this->suppliers[$type])) {
            foreach ($this->suppliers[$type] as $data) {
                if ($data['suppliers_id'] == $suppliers_id) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * get users linked to a object
     *
     * @param integer $type type to search (see constants)
     *
     * @return array
     **/
    public function getUsers($type)
    {

        if (isset($this->users[$type])) {
            return $this->users[$type];
        }

        return [];
    }


    /**
     * get groups linked to a object
     *
     * @param integer $type type to search (see constants)
     *
     * @return array
     **/
    public function getGroups($type)
    {

        if (isset($this->groups[$type])) {
            return $this->groups[$type];
        }

        return [];
    }


    /**
     * get users linked to an object including groups ones
     *
     * @since 0.85
     *
     * @param integer $type type to search (see constants)
     *
     * @return array
     **/
    public function getAllUsers($type)
    {

        $users = [];
        foreach ($this->getUsers($type) as $link) {
            $users[$link['users_id']] = $link['users_id'];
        }

        foreach ($this->getGroups($type) as $link) {
            $gusers = Group_User::getGroupUsers($link['groups_id']);
            foreach ($gusers as $user) {
                $users[$user['id']] = $user['id'];
            }
        }

        return $users;
    }


    /**
     * get suppliers linked to a object
     *
     * @since 0.84
     *
     * @param integer $type type to search (see constants)
     *
     * @return array
     **/
    public function getSuppliers($type)
    {

        if (isset($this->suppliers[$type])) {
            return $this->suppliers[$type];
        }

        return [];
    }


    /**
     * count users linked to object by type or global
     *
     * @param integer $type type to search (see constants) / 0 for all (default 0)
     *
     * @return integer
     **/
    public function countUsers($type = 0)
    {

        if ($type > 0) {
            if (isset($this->users[$type])) {
                return count($this->users[$type]);
            }
        } else {
            if (count($this->users)) {
                $count = 0;
                foreach ($this->users as $u) {
                    $count += count($u);
                }
                return $count;
            }
        }
        return 0;
    }


    /**
     * count groups linked to object by type or global
     *
     * @param integer $type type to search (see constants) / 0 for all (default 0)
     *
     * @return integer
     **/
    public function countGroups($type = 0)
    {

        if ($type > 0) {
            if (isset($this->groups[$type])) {
                return count($this->groups[$type]);
            }
        } else {
            if (count($this->groups)) {
                $count = 0;
                foreach ($this->groups as $u) {
                    $count += count($u);
                }
                return $count;
            }
        }
        return 0;
    }


    /**
     * count suppliers linked to object by type or global
     *
     * @since 0.84
     *
     * @param integer $type type to search (see constants) / 0 for all (default 0)
     *
     * @return integer
     **/
    public function countSuppliers($type = 0)
    {

        if ($type > 0) {
            if (isset($this->suppliers[$type])) {
                return count($this->suppliers[$type]);
            }
        } else {
            if (count($this->suppliers)) {
                $count = 0;
                foreach ($this->suppliers as $u) {
                    $count += count($u);
                }
                return $count;
            }
        }
        return 0;
    }


    /**
     * Is one of groups linked to the object ?
     *
     * @param integer $type   type to search (see constants)
     * @param array   $groups groups IDs
     *
     * @return boolean
     **/
    public function haveAGroup($type, array $groups)
    {

        if (
            is_array($groups) && count($groups)
            && isset($this->groups[$type])
        ) {
            foreach ($groups as $groups_id) {
                foreach ($this->groups[$type] as $data) {
                    if ($data['groups_id'] == $groups_id) {
                        return true;
                    }
                }
            }
        }
        return false;
    }


    /**
     * Get Default actor when creating the object
     *
     * @param integer $type type to search (see constants)
     *
     * @return boolean
     **/
    public function getDefaultActor($type)
    {

       /// TODO own_ticket -> own_itilobject
        if ($type == CommonITILActor::ASSIGN) {
            if (Session::haveRight("ticket", Ticket::OWN)) {
                return Session::getLoginUserID();
            }
        }
        return 0;
    }


    /**
     * Get Default actor when creating the object
     *
     * @param integer $type type to search (see constants)
     *
     * @return boolean
     **/
    public function getDefaultActorRightSearch($type)
    {

        if ($type == CommonITILActor::ASSIGN) {
            return "own_ticket";
        }
        return "all";
    }


    /**
     * Count active ITIL Objects
     *
     * @since 9.3.1
     *
     * @param CommonITILActor $linkclass Link class instance
     * @param integer         $id        Item ID
     * @param integer         $role      ITIL role
     *
     * @return integer
     **/
    private function countActiveObjectsFor(CommonITILActor $linkclass, $id, $role)
    {

        $itemtable = $this->getTable();
        $itemfk    = $this->getForeignKeyField();
        $linktable = $linkclass->getTable();
        $field     = $linkclass::$items_id_2;

        return countElementsInTable(
            [$itemtable, $linktable],
            [
                "$linktable.$itemfk"    => new \QueryExpression(DBmysql::quoteName("$itemtable.id")),
                "$linktable.$field"     => $id,
                "$linktable.type"       => $role,
                "$itemtable.is_deleted" => 0,
                "NOT"                   => [
                    "$itemtable.status" => array_merge(
                        $this->getSolvedStatusArray(),
                        $this->getClosedStatusArray()
                    )
                ]
            ] + getEntitiesRestrictCriteria($itemtable)
        );
    }




    /**
     * Count active ITIL Objects requested by a user
     *
     * @since 0.83
     *
     * @param integer $users_id ID of the User
     *
     * @return integer
     **/
    public function countActiveObjectsForUser($users_id)
    {
        $linkclass = new $this->userlinkclass();
        return $this->countActiveObjectsFor(
            $linkclass,
            $users_id,
            CommonITILActor::REQUESTER
        );
    }


    /**
     * Count active ITIL Objects having given user as observer.
     *
     * @param int $users_id
     *
     * @return int
     */
    final public function countActiveObjectsForObserverUser(int $user_id): int
    {
        $linkclass = new $this->userlinkclass();
        return $this->countActiveObjectsFor(
            $linkclass,
            $user_id,
            CommonITILActor::OBSERVER
        );
    }


    /**
     * Count active ITIL Objects assigned to a user
     *
     * @since 0.83
     *
     * @param integer $users_id ID of the User
     *
     * @return integer
     **/
    public function countActiveObjectsForTech($users_id)
    {
        $linkclass = new $this->userlinkclass();
        return $this->countActiveObjectsFor(
            $linkclass,
            $users_id,
            CommonITILActor::ASSIGN
        );
    }


    /**
     * Count active ITIL Objects having given group as requester.
     *
     * @param int $group_id
     *
     * @return int
     */
    final public function countActiveObjectsForRequesterGroup(int $group_id): int
    {
        $linkclass = new $this->grouplinkclass();
        return $this->countActiveObjectsFor(
            $linkclass,
            $group_id,
            CommonITILActor::REQUESTER
        );
    }


    /**
     * Count active ITIL Objects having given group as observer.
     *
     * @param int $group_id
     *
     * @return int
     */
    final public function countActiveObjectsForObserverGroup(int $group_id): int
    {
        $linkclass = new $this->grouplinkclass();
        return $this->countActiveObjectsFor(
            $linkclass,
            $group_id,
            CommonITILActor::OBSERVER
        );
    }


    /**
     * Count active ITIL Objects assigned to a group
     *
     * @since 0.84
     *
     * @param integer $groups_id ID of the User
     *
     * @return integer
     **/
    public function countActiveObjectsForTechGroup($groups_id)
    {
        $linkclass = new $this->grouplinkclass();
        return $this->countActiveObjectsFor(
            $linkclass,
            $groups_id,
            CommonITILActor::ASSIGN
        );
    }


    /**
     * Count active ITIL Objects assigned to a supplier
     *
     * @since 0.85
     *
     * @param integer $suppliers_id ID of the Supplier
     *
     * @return integer
     **/
    public function countActiveObjectsForSupplier($suppliers_id)
    {
        $linkclass = new $this->supplierlinkclass();
        return $this->countActiveObjectsFor(
            $linkclass,
            $suppliers_id,
            CommonITILActor::ASSIGN
        );
    }


    public function cleanDBonPurge()
    {

        $link_classes = [
            Itil_Project::class,
            ITILFollowup::class,
            ITILSolution::class
        ];

        if (is_a($this->grouplinkclass, CommonDBConnexity::class, true)) {
            $link_classes[] = $this->grouplinkclass;
        }

        if (is_a($this->userlinkclass, CommonDBConnexity::class, true)) {
            $link_classes[] = $this->userlinkclass;
        }

        if (is_a($this->supplierlinkclass, CommonDBConnexity::class, true)) {
            $link_classes[] = $this->supplierlinkclass;
        }

        $this->deleteChildrenAndRelationsFromDb($link_classes);
    }

    /**
     * Handle template mandatory fields on update
     *
     * @param array $input Input
     *
     * @return array
     */
    protected function handleTemplateFields(array $input)
    {
       //// check mandatory fields
       // First get ticket template associated : entity and type/category
        if (isset($input['entities_id'])) {
            $entid = $input['entities_id'];
        } else {
            $entid = $this->fields['entities_id'];
        }

        $type = null;
        if (isset($input['type'])) {
            $type = $input['type'];
        } else if (isset($this->fields['type'])) {
            $type = $this->fields['type'];
        }

        if (isset($input['itilcategories_id'])) {
            $categid = $input['itilcategories_id'];
        } else {
            $categid = $this->fields['itilcategories_id'];
        }

        $check_allowed_fields_for_template = false;
        $allowed_fields                    = [];
        if (
            !Session::isCron()
            && (!Session::haveRight(static::$rightname, UPDATE)
            // Closed tickets
            || in_array($this->fields['status'], $this->getClosedStatusArray()))
        ) {
            $allowed_fields                    = ['id'];
            $check_allowed_fields_for_template = true;

            if (in_array($this->fields['status'], $this->getClosedStatusArray())) {
                $allowed_fields[] = 'status';

               // probably transfer
                $allowed_fields[] = 'entities_id';
                $allowed_fields[] = 'itilcategories_id';
            } else {
                if (
                    $this->canApprove()
                    || $this->canAssign()
                    || $this->canAssignToMe()
                    || isset($input['_from_assignment'])
                ) {
                    $allowed_fields[] = 'status';
                    $allowed_fields[] = '_accepted';
                }
                $validation_class = static::getType() . 'Validation';
                if (
                    class_exists($validation_class)
                    && (
                        // for validation created by rules
                        // FIXME Use a more precise input name to ensure that 'global_validation' has been defined by rules
                        // e.g. $input['_validation_from_rule']
                        isset($input["_rule_process"])
                        // for validation status updated after CommonITILValidation add/update/delete
                        || (array_key_exists('_from_itilvalidation', $input) && $input['_from_itilvalidation'])
                    )
                ) {
                    $allowed_fields[] = 'global_validation';
                }
               // Manage assign and steal right
                if (static::getType() === Ticket::getType() && Session::haveRightsOr(static::$rightname, [Ticket::ASSIGN, Ticket::STEAL])) {
                    $allowed_fields[] = '_itil_assign';
                    $allowed_fields[] = '_users_id_assign';
                    $allowed_fields[] = '_groups_id_assign';
                    $allowed_fields[] = '_suppliers_id_assign';
                }

               // Can only update initial fields if no followup or task already added
                if ($this->canUpdateItem()) {
                    $allowed_fields[] = 'content';
                    $allowed_fields[] = 'urgency';
                    $allowed_fields[] = 'priority'; // automatic recalculate if user changes urgence
                    $allowed_fields[] = 'itilcategories_id';
                    $allowed_fields[] = 'name';
                    $allowed_fields[] = 'items_id';
                    $allowed_fields[] = '_filename';
                    $allowed_fields[] = '_tag_filename';
                    $allowed_fields[] = '_prefix_filename';
                    $allowed_fields[] = '_content';
                    $allowed_fields[] = '_tag_content';
                    $allowed_fields[] = '_prefix_content';
                    $allowed_fields[] = 'takeintoaccount_delay_stat';
                    $allowed_fields[] = 'takeintoaccountdate';
                }
            }

            $ret = [];

            foreach ($allowed_fields as $field) {
                if (isset($input[$field])) {
                    $ret[$field] = $input[$field];
                }
            }

            $input = $ret;

           // Only ID return false
            if (count($input) == 1) {
                return false;
            }
        }

        $tt = $this->getITILTemplateToUse(0, $type, $categid, $entid);

        if (count($tt->mandatory)) {
            $mandatory_missing = [];
            $fieldsname        = $tt->getAllowedFieldsNames(true);
            foreach ($tt->mandatory as $key => $val) {
                if (
                    (!$check_allowed_fields_for_template || in_array($key, $allowed_fields))
                    && (isset($input[$key])
                    && (empty($input[$key]) || ($input[$key] == 'NULL'))
                    )
                ) {
                    $mandatory_missing[$key] = $fieldsname[$val];
                }
            }
            if (count($mandatory_missing)) {
               //TRANS: %s are the fields concerned
                $message = sprintf(
                    __('Mandatory fields are not filled. Please correct: %s'),
                    implode(", ", $mandatory_missing)
                );
                Session::addMessageAfterRedirect($message, false, ERROR);
                return false;
            }
        }

        return $input;
    }

    public function prepareInputForUpdate($input)
    {

        if (!$this->checkFieldsConsistency($input)) {
            return false;
        }

       // Add document if needed
        $this->getFromDB($input["id"]); // entities_id field required

        if ($this->getType() !== Ticket::getType()) {
           //cannot be handled here for tickets. @see Ticket::prepareInputForUpdate()
            $input = $this->handleTemplateFields($input);
            if ($input === false) {
                return false;
            }
        }

        if (isset($input["document"]) && ($input["document"] > 0)) {
            $doc = new Document();
            if ($doc->getFromDB($input["document"])) {
                $docitem = new Document_Item();
                if (
                    $docitem->add(['documents_id' => $input["document"],
                        'itemtype'     => $this->getType(),
                        'items_id'     => $input["id"]
                    ])
                ) {
                    // Force date_mod of tracking
                    $input["date_mod"]     = $_SESSION["glpi_currenttime"];
                    $input['_doc_added'][] = $doc->fields["name"];
                }
            }
            unset($input["document"]);
        }

        if (isset($input["date"]) && empty($input["date"])) {
            unset($input["date"]);
        }

        if (isset($input["closedate"]) && empty($input["closedate"])) {
            unset($input["closedate"]);
        }

        if (isset($input["solvedate"]) && empty($input["solvedate"])) {
            unset($input["solvedate"]);
        }

       // "do not compute" flag set by business rules for "takeintoaccount_delay_stat" field
        $do_not_compute_takeintoaccount = $this->isTakeIntoAccountComputationBlocked($input);

        if (isset($input['_itil_requester'])) {
            // FIXME Deprecate this input key in GLPI 10.1.
            if (isset($input['_itil_requester']['_type'])) {
                $input['_itil_requester'] = [
                    'type'                            => CommonITILActor::REQUESTER,
                    $this->getForeignKeyField()       => $input['id'],
                    '_do_not_compute_takeintoaccount' => $do_not_compute_takeintoaccount,
                    '_from_object'                    => true,
                ] + $input['_itil_requester'];

                switch ($input['_itil_requester']['_type']) {
                    case "user":
                        if (
                            isset($input['_itil_requester']['use_notification'])
                            && is_array($input['_itil_requester']['use_notification'])
                        ) {
                            $input['_itil_requester']['use_notification'] = $input['_itil_requester']['use_notification'][0];
                        }
                        if (
                            isset($input['_itil_requester']['alternative_email'])
                            && is_array($input['_itil_requester']['alternative_email'])
                        ) {
                            $input['_itil_requester']['alternative_email'] = $input['_itil_requester']['alternative_email'][0];
                        }

                        if (!empty($this->userlinkclass)) {
                            if (
                                isset($input['_itil_requester']['alternative_email'])
                                && $input['_itil_requester']['alternative_email']
                                && !NotificationMailing::isUserAddressValid($input['_itil_requester']['alternative_email'])
                            ) {
                                $input['_itil_requester']['alternative_email'] = '';
                                Session::addMessageAfterRedirect(__('Invalid email address'), false, ERROR);
                            }

                            if (
                                (isset($input['_itil_requester']['alternative_email'])
                                && $input['_itil_requester']['alternative_email'])
                                || ($input['_itil_requester']['users_id'] > 0)
                            ) {
                                $useractors = new $this->userlinkclass();
                                if (
                                    isset($input['_auto_update'])
                                    || $useractors->can(-1, CREATE, $input['_itil_requester'])
                                ) {
                                    $useractors->add($input['_itil_requester']);
                                    $input['_forcenotif']                     = true;
                                }
                            }
                        }
                        break;

                    case "group":
                        if (
                            !empty($this->grouplinkclass)
                            && ($input['_itil_requester']['groups_id'] > 0)
                        ) {
                            $groupactors = new $this->grouplinkclass();
                            if (
                                isset($input['_auto_update'])
                                || $groupactors->can(-1, CREATE, $input['_itil_requester'])
                            ) {
                                $groupactors->add($input['_itil_requester']);
                                $input['_forcenotif']                     = true;
                            }
                        }
                        break;
                }
            }
        }

        if (isset($input['_itil_observer'])) {
            // FIXME Deprecate this input key in GLPI 10.1.
            if (isset($input['_itil_observer']['_type'])) {
                $input['_itil_observer'] = [
                    'type'                            => CommonITILActor::OBSERVER,
                    $this->getForeignKeyField()       => $input['id'],
                    '_do_not_compute_takeintoaccount' => $do_not_compute_takeintoaccount,
                    '_from_object'                    => true,
                ] + $input['_itil_observer'];

                switch ($input['_itil_observer']['_type']) {
                    case "user":
                        if (
                            isset($input['_itil_observer']['use_notification'])
                            && is_array($input['_itil_observer']['use_notification'])
                        ) {
                            $input['_itil_observer']['use_notification'] = $input['_itil_observer']['use_notification'][0];
                        }
                        if (
                            isset($input['_itil_observer']['alternative_email'])
                            && is_array($input['_itil_observer']['alternative_email'])
                        ) {
                            $input['_itil_observer']['alternative_email'] = $input['_itil_observer']['alternative_email'][0];
                        }

                        if (!empty($this->userlinkclass)) {
                            if (
                                isset($input['_itil_observer']['alternative_email'])
                                && $input['_itil_observer']['alternative_email']
                                && !NotificationMailing::isUserAddressValid($input['_itil_observer']['alternative_email'])
                            ) {
                                $input['_itil_observer']['alternative_email'] = '';
                                Session::addMessageAfterRedirect(__('Invalid email address'), false, ERROR);
                            }
                            if (
                                 (isset($input['_itil_observer']['alternative_email'])
                                 && $input['_itil_observer']['alternative_email'])
                                 || ($input['_itil_observer']['users_id'] > 0)
                            ) {
                                $useractors = new $this->userlinkclass();
                                if (
                                    isset($input['_auto_update'])
                                    || $useractors->can(-1, CREATE, $input['_itil_observer'])
                                ) {
                                    $useractors->add($input['_itil_observer']);
                                    $input['_forcenotif']                    = true;
                                }
                            }
                        }
                        break;

                    case "group":
                        if (
                            !empty($this->grouplinkclass)
                            && ($input['_itil_observer']['groups_id'] > 0)
                        ) {
                            $groupactors = new $this->grouplinkclass();
                            if (
                                isset($input['_auto_update'])
                                || $groupactors->can(-1, CREATE, $input['_itil_observer'])
                            ) {
                                $groupactors->add($input['_itil_observer']);
                                $input['_forcenotif']                    = true;
                            }
                        }
                        break;
                }
            }
        }

        if (isset($input['_itil_assign'])) {
            // FIXME Deprecate this input key in GLPI 10.1.
            if (isset($input['_itil_assign']['_type'])) {
                $input['_itil_assign'] = [
                    'type'                            => CommonITILActor::ASSIGN,
                    $this->getForeignKeyField()       => $input['id'],
                    '_do_not_compute_takeintoaccount' => $do_not_compute_takeintoaccount,
                    '_from_object'                    => true,
                ] + $input['_itil_assign'];

                if (
                    isset($input['_itil_assign']['use_notification'])
                    && is_array($input['_itil_assign']['use_notification'])
                ) {
                    $input['_itil_assign']['use_notification'] = $input['_itil_assign']['use_notification'][0];
                }
                if (
                    isset($input['_itil_assign']['alternative_email'])
                    && is_array($input['_itil_assign']['alternative_email'])
                ) {
                    $input['_itil_assign']['alternative_email'] = $input['_itil_assign']['alternative_email'][0];
                }

                switch ($input['_itil_assign']['_type']) {
                    case "user":
                        if (
                            !empty($this->userlinkclass)
                            && ((isset($input['_itil_assign']['alternative_email'])
                            && $input['_itil_assign']['alternative_email'])
                            || $input['_itil_assign']['users_id'] > 0)
                        ) {
                            $useractors = new $this->userlinkclass();
                            if (
                                isset($input['_auto_update'])
                                || $useractors->can(-1, CREATE, $input['_itil_assign'])
                            ) {
                                $useractors->add($input['_itil_assign']);
                                $input['_forcenotif']                  = true;
                                if (
                                    ((!isset($input['status'])
                                    && in_array($this->fields['status'], $this->getNewStatusArray()))
                                    || (isset($input['status'])
                                    && in_array($input['status'], $this->getNewStatusArray())))
                                    && !$this->isStatusComputationBlocked($input)
                                ) {
                                    if (in_array(self::ASSIGNED, array_keys($this->getAllStatusArray()))) {
                                        $input['status'] = self::ASSIGNED;
                                    }
                                }
                            }
                        }
                        break;

                    case "group":
                        if (
                            !empty($this->grouplinkclass)
                            && ($input['_itil_assign']['groups_id'] > 0)
                        ) {
                            $groupactors = new $this->grouplinkclass();

                            if (
                                isset($input['_auto_update'])
                                || $groupactors->can(-1, CREATE, $input['_itil_assign'])
                            ) {
                                $groupactors->add($input['_itil_assign']);
                                $input['_forcenotif']                  = true;
                                if (
                                    ((!isset($input['status'])
                                    && (in_array($this->fields['status'], $this->getNewStatusArray())))
                                    || (isset($input['status'])
                                    && (in_array($input['status'], $this->getNewStatusArray()))))
                                    && !$this->isStatusComputationBlocked($input)
                                ) {
                                    if (in_array(self::ASSIGNED, array_keys($this->getAllStatusArray()))) {
                                        $input['status'] = self::ASSIGNED;
                                    }
                                }
                            }
                        }
                        break;

                    case "supplier":
                        if (
                            !empty($this->supplierlinkclass)
                            && ((isset($input['_itil_assign']['alternative_email'])
                            && $input['_itil_assign']['alternative_email'])
                            || $input['_itil_assign']['suppliers_id'] > 0)
                        ) {
                            $supplieractors = new $this->supplierlinkclass();
                            if (
                                isset($input['_auto_update'])
                                || $supplieractors->can(-1, CREATE, $input['_itil_assign'])
                            ) {
                                $supplieractors->add($input['_itil_assign']);
                                $input['_forcenotif']                  = true;
                                if (
                                    ((!isset($input['status'])
                                    && (in_array($this->fields['status'], $this->getNewStatusArray())))
                                    || (isset($input['status'])
                                    && (in_array($input['status'], $this->getNewStatusArray()))))
                                    && !$this->isStatusComputationBlocked($input)
                                ) {
                                    if (in_array(self::ASSIGNED, array_keys($this->getAllStatusArray()))) {
                                        $input['status'] = self::ASSIGNED;
                                    }
                                }
                            }
                        }
                        break;
                }
            }
        }

       // set last updater if interactive user
        if (!Session::isCron()) {
            $input['users_id_lastupdater'] = Session::getLoginUserID();
        }

        $solvedclosed = array_merge(
            $this->getSolvedStatusArray(),
            $this->getClosedStatusArray()
        );

        if (
            isset($input["status"])
            && !in_array($input["status"], $solvedclosed)
        ) {
            $input['solvedate'] = 'NULL';
        }

        if (isset($input["status"]) && !in_array($input["status"], $this->getClosedStatusArray())) {
            $input['closedate'] = 'NULL';
        }

       // Setting a solution type means the ticket is solved
        if (
            isset($input["solutiontypes_id"])
            && (!isset($input['status']) || !in_array($input["status"], $solvedclosed))
        ) {
            $solution = new ITILSolution();
            $soltype = new SolutionType();
            $soltype->getFromDB($input['solutiontypes_id']);
            $solution->add([
                'itemtype'           => $this->getType(),
                'items_id'           => $this->getID(),
                'solutiontypes_id'   => $input['solutiontypes_id'],
                'content'            => 'Solved using type ' . $soltype->getName()
            ]);
        }

       // If status changed from pending to anything else, remove pending reason
        if (
            isset($this->input["status"])
            && $this->input["status"] != self::WAITING
        ) {
            PendingReason_Item::deleteForItem($this);
        }

        return $input;
    }

    public function post_updateItem($history = 1)
    {
        // Handle rich-text images and uploaded documents
        $this->input = $this->addFiles($this->input, ['force_update' => true]);

        // handle actors changes
        $this->updateActors();

        // Handle "_tasktemplates_id" special input
        $this->handleTaskTemplateInput();

        // Handle "_itilfollowuptemplates_id" special input
        $this->handleITILFollowupTemplateInput();

        // Handle "_solutiontemplates_id" special input
        $this->handleSolutionTemplateInput();

        // Send validation requests
        $this->manageValidationAdd($this->input);

        parent::post_updateItem();
    }


    public function pre_updateInDB()
    {
        global $DB;

       // get again object to reload actors
        $this->loadActors();

       // Check dates change interval due to the fact that second are not displayed in form
        if (
            (($key = array_search('date', $this->updates)) !== false)
            && (substr($this->fields["date"], 0, 16) == substr($this->oldvalues['date'], 0, 16))
        ) {
            unset($this->updates[$key]);
            unset($this->oldvalues['date']);
        }

        if (
            (($key = array_search('closedate', $this->updates)) !== false)
            && isset($this->oldvalues['closedate'])
            && (substr($this->fields["closedate"], 0, 16) == substr($this->oldvalues['closedate'], 0, 16))
        ) {
            unset($this->updates[$key]);
            unset($this->oldvalues['closedate']);
        }

        if (
            (($key = array_search('time_to_resolve', $this->updates)) !== false)
            && isset($this->oldvalues['time_to_resolve'])
            && (substr($this->fields["time_to_resolve"], 0, 16) == substr($this->oldvalues['time_to_resolve'], 0, 16))
        ) {
            unset($this->updates[$key]);
            unset($this->oldvalues['time_to_resolve']);
        }

        if (
            (($key = array_search('solvedate', $this->updates)) !== false)
            && isset($this->oldvalues['solvedate'])
            && (substr($this->fields["solvedate"], 0, 16) == substr($this->oldvalues['solvedate'], 0, 16))
        ) {
            unset($this->updates[$key]);
            unset($this->oldvalues['solvedate']);
        }

        if (isset($this->input["status"])) {
            if (
                in_array("status", $this->updates)
                && in_array($this->input["status"], $this->getSolvedStatusArray())
            ) {
                $this->updates[]              = "solvedate";
                $this->oldvalues['solvedate'] = $this->fields["solvedate"];
                $this->fields["solvedate"]    = $_SESSION["glpi_currenttime"];
               // If invalid date : set open date
                if ($this->fields["solvedate"] < $this->fields["date"]) {
                    $this->fields["solvedate"] = $this->fields["date"];
                }
            }

            if (
                in_array("status", $this->updates)
                && in_array($this->input["status"], $this->getClosedStatusArray())
            ) {
                $this->updates[]              = "closedate";
                $this->oldvalues['closedate'] = $this->fields["closedate"];
                $this->fields["closedate"]    = $_SESSION["glpi_currenttime"];
               // If invalid date : set open date
                if ($this->fields["closedate"] < $this->fields["date"]) {
                    $this->fields["closedate"] = $this->fields["date"];
                }
               // Set solvedate to closedate
                if (empty($this->fields["solvedate"])) {
                    $this->updates[]              = "solvedate";
                    $this->oldvalues['solvedate'] = $this->fields["solvedate"];
                    $this->fields["solvedate"]    = $this->fields["closedate"];
                }
            }
        }

       // check dates

       // check time_to_resolve (SLA)
        if (
            (in_array("date", $this->updates) || in_array("time_to_resolve", $this->updates))
            && !is_null($this->fields["time_to_resolve"])
        ) { // Date set
            if ($this->fields["time_to_resolve"] < $this->fields["date"]) {
                Session::addMessageAfterRedirect(__('Invalid dates. Update cancelled.'), false, ERROR);

                if (($key = array_search('date', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['date']);
                }
                if (($key = array_search('time_to_resolve', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['time_to_resolve']);
                }
            }
        }

       // check internal_time_to_resolve (OLA)
        if (
            (in_array("date", $this->updates) || in_array("internal_time_to_resolve", $this->updates))
            && !is_null($this->fields["internal_time_to_resolve"])
        ) { // Date set
            if ($this->fields["internal_time_to_resolve"] < $this->fields["date"]) {
                Session::addMessageAfterRedirect(__('Invalid dates. Update cancelled.'), false, ERROR);

                if (($key = array_search('date', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['date']);
                }
                if (($key = array_search('internal_time_to_resolve', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['internal_time_to_resolve']);
                }
            }
        }

       // Status close : check dates
        if (
            in_array($this->fields["status"], $this->getClosedStatusArray())
            && (in_array("date", $this->updates) || in_array("closedate", $this->updates))
        ) {
           // Invalid dates : no change
           // closedate must be > solvedate
            if ($this->fields["closedate"] < $this->fields["solvedate"]) {
                Session::addMessageAfterRedirect(__('Invalid dates. Update cancelled.'), false, ERROR);

                if (($key = array_search('closedate', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['closedate']);
                }
            }

           // closedate must be > create date
            if ($this->fields["closedate"] < $this->fields["date"]) {
                Session::addMessageAfterRedirect(__('Invalid dates. Update cancelled.'), false, ERROR);
                if (($key = array_search('date', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['date']);
                }
                if (($key = array_search('closedate', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['closedate']);
                }
            }
        }

        if (
            (($key = array_search('status', $this->updates)) !== false)
            && $this->oldvalues['status'] == $this->fields['status']
        ) {
            unset($this->updates[$key]);
            unset($this->oldvalues['status']);
        }

       // Status solved : check dates
        if (
            in_array($this->fields["status"], $this->getSolvedStatusArray())
            && (in_array("date", $this->updates) || in_array("solvedate", $this->updates))
        ) {
           // Invalid dates : no change
           // solvedate must be > create date
            if ($this->fields["solvedate"] < $this->fields["date"]) {
                Session::addMessageAfterRedirect(__('Invalid dates. Update cancelled.'), false, ERROR);

                if (($key = array_search('date', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['date']);
                }
                if (($key = array_search('solvedate', $this->updates)) !== false) {
                    unset($this->updates[$key]);
                    unset($this->oldvalues['solvedate']);
                }
            }
        }

       // Manage come back to waiting state
        if (
            !is_null($this->fields['begin_waiting_date'])
            && ($key = array_search('status', $this->updates)) !== false
            && (
            $this->oldvalues['status'] == self::WAITING
            // From solved to another state than closed
            || (
               in_array($this->oldvalues["status"], $this->getSolvedStatusArray())
               && !in_array($this->fields["status"], $this->getClosedStatusArray())
            )
            // From closed to any open state
            || (
               in_array($this->oldvalues["status"], $this->getClosedStatusArray())
               && in_array($this->fields["status"], $this->getNotSolvedStatusArray())
            )
            )
        ) {
           // Compute ticket waiting time use calendar if exists
            $calendar     = new Calendar();
            $calendars_id = $this->getCalendar();
            $delay_time   = 0;

           // Compute ticket waiting time use calendar if exists
           // Using calendar
            if (
                ($calendars_id > 0)
                && $calendar->getFromDB($calendars_id)
            ) {
                $delay_time = $calendar->getActiveTimeBetween(
                    $this->fields['begin_waiting_date'],
                    $_SESSION["glpi_currenttime"]
                );
            } else { // Not calendar defined
                $delay_time = strtotime($_SESSION["glpi_currenttime"])
                           - strtotime($this->fields['begin_waiting_date']);
            }

           // SLA case : compute sla_ttr duration
            if (isset($this->fields['slas_id_ttr']) && ($this->fields['slas_id_ttr'] > 0)) {
                $sla = new SLA();
                if ($sla->getFromDB($this->fields['slas_id_ttr'])) {
                    $sla->setTicketCalendar($calendars_id);
                    $delay_time_sla  = $sla->getActiveTimeBetween(
                        $this->fields['begin_waiting_date'],
                        $_SESSION["glpi_currenttime"]
                    );
                    $this->updates[] = "sla_waiting_duration";
                    $this->fields["sla_waiting_duration"] += $delay_time_sla;
                }

               // Compute new time_to_resolve
                $this->updates[]                 = "time_to_resolve";
                $this->fields['time_to_resolve'] = $sla->computeDate(
                    $this->fields['date'],
                    $this->fields["sla_waiting_duration"]
                );
               // Add current level to do
                $sla->addLevelToDo($this);
            } else {
               // Using calendar
                if (
                    ($calendars_id > 0)
                    && $calendar->getFromDB($calendars_id)
                    && $calendar->hasAWorkingDay()
                ) {
                    if ((int)$this->fields['time_to_resolve'] > 0) {
                       // compute new due date using calendar
                        $this->updates[]                 = "time_to_resolve";
                        $this->fields['time_to_resolve'] = $calendar->computeEndDate(
                            $this->fields['time_to_resolve'],
                            $delay_time
                        );
                    }
                } else { // Not calendar defined
                    if ((int)$this->fields['time_to_resolve'] > 0) {
                       // compute new due date : no calendar so add computed delay_time
                        $this->updates[]                 = "time_to_resolve";
                        $this->fields['time_to_resolve'] = date(
                            'Y-m-d H:i:s',
                            $delay_time + strtotime($this->fields['time_to_resolve'])
                        );
                    }
                }
            }

           // OLA case : compute ola_ttr duration
            if (isset($this->fields['olas_id_ttr']) && ($this->fields['olas_id_ttr'] > 0)) {
                $ola = new OLA();
                if ($ola->getFromDB($this->fields['olas_id_ttr'])) {
                    $ola->setTicketCalendar($calendars_id);
                    $delay_time_ola  = $ola->getActiveTimeBetween(
                        $this->fields['begin_waiting_date'],
                        $_SESSION["glpi_currenttime"]
                    );
                    $this->updates[]                      = "ola_waiting_duration";
                    $this->fields["ola_waiting_duration"] += $delay_time_ola;
                }

               // Compute new internal_time_to_resolve
                $this->updates[]                          = "internal_time_to_resolve";
                $this->fields['internal_time_to_resolve'] = $ola->computeDate(
                    $this->fields['ola_ttr_begin_date'],
                    $this->fields["ola_waiting_duration"]
                );
               // Add current level to do
                $ola->addLevelToDo($this, $this->fields["olalevels_id_ttr"]);
            } else if (array_key_exists("internal_time_to_resolve", $this->fields)) {
               // Change doesn't have internal_time_to_resolve
               // Using calendar
                if (
                    ($calendars_id > 0)
                    && $calendar->getFromDB($calendars_id)
                    && $calendar->hasAWorkingDay()
                ) {
                    if ((int)$this->fields['internal_time_to_resolve'] > 0) {
                       // compute new internal_time_to_resolve using calendar
                        $this->updates[]                          = "internal_time_to_resolve";
                        $this->fields['internal_time_to_resolve'] = $calendar->computeEndDate(
                            $this->fields['internal_time_to_resolve'],
                            $delay_time
                        );
                    }
                } else { // Not calendar defined
                    if ((int)$this->fields['internal_time_to_resolve'] > 0) {
                       // compute new internal_time_to_resolve : no calendar so add computed delay_time
                        $this->updates[]                          = "internal_time_to_resolve";
                        $this->fields['internal_time_to_resolve'] = date(
                            'Y-m-d H:i:s',
                            $delay_time +
                            strtotime($this->fields['internal_time_to_resolve'])
                        );
                    }
                }
            }

            $this->updates[]                   = "waiting_duration";
            $this->fields["waiting_duration"] += $delay_time;

           // Reset begin_waiting_date
            $this->updates[]                    = "begin_waiting_date";
            $this->fields["begin_waiting_date"] = 'NULL';
        }

       // Set begin waiting date if needed
        if (
            (($key = array_search('status', $this->updates)) !== false)
            && (($this->fields['status'] == self::WAITING)
              || in_array($this->fields["status"], $this->getSolvedStatusArray()))
        ) {
            $this->updates[]                    = "begin_waiting_date";
            $this->fields["begin_waiting_date"] = $_SESSION["glpi_currenttime"];

           // Specific for tickets
            if (isset($this->fields['slas_id_ttr']) && ($this->fields['slas_id_ttr'] > 0)) {
                SLA::deleteLevelsToDo($this);
            }

            if (isset($this->fields['olas_id_ttr']) && ($this->fields['olas_id_ttr'] > 0)) {
                OLA::deleteLevelsToDo($this);
            }
        }

       // solve_delay_stat : use delay between opendate and solvedate
        if (in_array("solvedate", $this->updates)) {
            $this->updates[]                  = "solve_delay_stat";
            $this->fields['solve_delay_stat'] = $this->computeSolveDelayStat();
        }
       // close_delay_stat : use delay between opendate and closedate
        if (in_array("closedate", $this->updates)) {
            $this->updates[]                  = "close_delay_stat";
            $this->fields['close_delay_stat'] = $this->computeCloseDelayStat();
        }

       //Look for reopening
        $statuses = array_merge(
            $this->getSolvedStatusArray(),
            $this->getClosedStatusArray()
        );
        if (
            ($key = array_search('status', $this->updates)) !== false
            && in_array($this->oldvalues['status'], $statuses)
            && !in_array($this->fields['status'], $statuses)
        ) {
            $users_id_reject = 0;
            // set last updater if interactive user
            if (!Session::isCron()) {
                $users_id_reject = Session::getLoginUserID();
            }

            //Mark existing solutions as refused
            $DB->update(
                ITILSolution::getTable(),
                [
                    'status'             => CommonITILValidation::REFUSED,
                    'users_id_approval'  => $users_id_reject,
                    'date_approval'      => date('Y-m-d H:i:s')
                ],
                [
                    'WHERE'  => [
                        'itemtype'  => static::getType(),
                        'items_id'  => $this->getID()
                    ],
                    'ORDER'  => [
                        'date_creation DESC',
                        'id DESC'
                    ],
                    'LIMIT'  => 1
                ]
            );

            //Delete existing survey
            $inquest = new TicketSatisfaction();
            $inquest->delete(['tickets_id' => $this->getID()]);
        }

        if (isset($this->input['_accepted'])) {
           //Mark last solution as approved
            $DB->update(
                ITILSolution::getTable(),
                [
                    'status'             => CommonITILValidation::ACCEPTED,
                    'users_id_approval'  => Session::getLoginUserID(),
                    'date_approval'      => date('Y-m-d H:i:s')
                ],
                [
                    'WHERE'  => [
                        'itemtype'  => static::getType(),
                        'items_id'  => $this->getID()
                    ],
                    'ORDER'  => [
                        'date_creation DESC',
                        'id DESC'
                    ],
                    'LIMIT'  => 1
                ]
            );
        }

       // Do not take into account date_mod if no update is done
        if (
            (count($this->updates) == 1)
            && (($key = array_search('date_mod', $this->updates)) !== false)
        ) {
            unset($this->updates[$key]);
        }
    }


    public function prepareInputForAdd($input)
    {
        global $CFG_GLPI;

        if (!$this->checkFieldsConsistency($input)) {
            return false;
        }

        $input = $this->transformActorsInput($input);

       // save value before clean;
        $title = ltrim($input['name']);

       // Set default status to avoid notice
        if (!isset($input["status"])) {
            $input["status"] = self::INCOMING;
        }

        if (
            !isset($input["urgency"])
            || !($CFG_GLPI['urgency_mask'] & (1 << $input["urgency"]))
        ) {
            $input["urgency"] = 3;
        }
        if (
            !isset($input["impact"])
            || !($CFG_GLPI['impact_mask'] & (1 << $input["impact"]))
        ) {
            $input["impact"] = 3;
        }

        $canpriority = true;
        if ($this->getType() == 'Ticket') {
            $canpriority = Session::haveRight(Ticket::$rightname, Ticket::CHANGEPRIORITY);
        }

        if ($canpriority && !isset($input["priority"]) || !$canpriority) {
            $input["priority"] = $this->computePriority($input["urgency"], $input["impact"]);
        }

       // set last updater if interactive user
        if (!Session::isCron() && ($last_updater = Session::getLoginUserID(true))) {
            $input['users_id_lastupdater'] = $last_updater;
        }

        if (!isset($input['_skip_auto_assign']) || $input['_skip_auto_assign'] === false) {
           // No Auto set Import for external source
            if (
                ($uid = Session::getLoginUserID())
                && !isset($input['_auto_import'])
            ) {
                $input["users_id_recipient"] = $uid;
            } else if (
                isset($input["_users_id_requester"])
                && !is_array($input['_users_id_requester'])
                && !empty($input["_users_id_requester"])
                && !isset($input["users_id_recipient"])
            ) {
                $input["users_id_recipient"] = $input["_users_id_requester"];
            }
        }

       // No name set name
        $input["name"]    = ltrim($input["name"]);
        $input['content'] = ltrim($input['content']);
        if (empty($input["name"])) {
           // Build name based on content

           // Unsanitize
            $content = Sanitizer::unsanitize($input['content']);

           // Get unformatted text
            $name = RichText::getTextFromHtml($content, false);

           // Shorten result
            $name = Toolbox::substr(preg_replace('/\s{2,}/', ' ', $name), 0, 70);

           // Sanitize result
            $input['name'] = Sanitizer::sanitize($name);
        }

       // Set default dropdown
        $dropdown_fields = ['entities_id', 'itilcategories_id'];
        foreach ($dropdown_fields as $field) {
            if (!isset($input[$field])) {
                $input[$field] = 0;
            }
        }

        $input = $this->computeDefaultValuesForAdd($input);

       // Do not check mandatory on auto import (mailgates)
        $key = $this->getTemplateFormFieldName();
        if (!isset($input['_auto_import'])) {
            if (isset($input[$key]) && $input[$key]) {
                $tt_class = $this->getType() . 'Template';
                $tt = new $tt_class();
                if ($tt->getFromDBWithData($input[$key])) {
                    if (count($tt->mandatory)) {
                        $mandatory_missing = [];
                        $fieldsname        = $tt->getAllowedFieldsNames(true);
                        foreach ($tt->mandatory as $key => $val) {
                             // for title if mandatory (restore initial value)
                            if ($key == 'name') {
                                $input['name']                     = $title;
                            }
                             // Check only defined values : Not defined not in form
                            if (isset($input[$key])) {
                             // If content is also predefined need to be different from predefined value
                                if (
                                    ($key == 'content')
                                    && isset($tt->predefined['content'])
                                ) {
                                    // Decode then re-encode predefine content to be sure to have it encoded using the same
                                    // entities.
                                    // Without this, predefined content created prior to GLPI 10.0 will never match
                                    // sanitized input, as entities used for encoding will be different in both.
                                    $predefined_content = Sanitizer::encodeHtmlSpecialChars(
                                        Sanitizer::decodeHtmlSpecialChars($tt->predefined['content'])
                                    );
                                 // Clean new lines to be fix encoding
                                    if (
                                        strcmp(
                                            preg_replace(
                                                "/\r?\n/",
                                                "",
                                                Html::cleanPostForTextArea($input[$key])
                                            ),
                                            preg_replace(
                                                "/\r?\n/",
                                                "",
                                                $predefined_content
                                            )
                                        ) == 0
                                    ) {
                                        Session::addMessageAfterRedirect(
                                            __('You cannot use predefined description verbatim'),
                                            false,
                                            ERROR
                                        );
                                           $mandatory_missing[$key] = $fieldsname[$val];
                                    }
                                }

                                if (
                                    empty($input[$key]) || ($input[$key] == 'NULL')
                                    || (is_array($input[$key])
                                    && ($input[$key] === [0 => "0"]))
                                ) {
                                    $mandatory_missing[$key] = $fieldsname[$val];
                                }
                            }

                            if (
                                ($key == '_add_validation')
                                && !empty($input['users_id_validate'])
                                && isset($input['users_id_validate'][0])
                                && ($input['users_id_validate'][0] > 0)
                            ) {
                                unset($mandatory_missing['_add_validation']);
                            }

                            if (static::getType() === Ticket::getType()) {
                               // For time_to_resolve and time_to_own : check also slas
                               // For internal_time_to_resolve and internal_time_to_own : check also olas
                                foreach ([SLM::TTR, SLM::TTO] as $slmType) {
                                    list($dateField, $slaField) = SLA::getFieldNames($slmType);
                                    if (
                                        ($key == $dateField)
                                        && isset($input[$slaField]) && ($input[$slaField] > 0)
                                        && isset($mandatory_missing[$dateField])
                                    ) {
                                          unset($mandatory_missing[$dateField]);
                                    }
                                    list($dateField, $olaField) = OLA::getFieldNames($slmType);
                                    if (
                                        ($key == $dateField)
                                        && isset($input[$olaField]) && ($input[$olaField] > 0)
                                        && isset($mandatory_missing[$dateField])
                                    ) {
                                          unset($mandatory_missing[$dateField]);
                                    }
                                }
                            }

                          // For document mandatory
                            if (
                                ($key == '_documents_id')
                                && !isset($input['_filename'])
                                && !isset($input['_tag_filename'])
                                && !isset($input['_content'])
                                && !isset($input['_tag_content'])
                                && !isset($input['_stock_image'])
                                && !isset($input['_tag_stock_image'])
                            ) {
                                $mandatory_missing[$key] = $fieldsname[$val];
                            }
                        }

                        if (count($mandatory_missing)) {
                           //TRANS: %s are the fields concerned
                            $message = sprintf(
                                __('Mandatory fields are not filled. Please correct: %s'),
                                implode(", ", $mandatory_missing)
                            );
                            Session::addMessageAfterRedirect($message, false, ERROR);
                            return false;
                        }
                    }
                }
            }
        }

        return $input;
    }

    /**
     * Check input fields consistency.
     *
     * @param array $input
     *
     * @return bool
     */
    private function checkFieldsConsistency(array $input): bool
    {
        if (
            array_key_exists('date', $input) && !empty($input['date']) && $input['date'] != 'NULL'
            && (!is_string($input['date']) || !preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $input['date']))
        ) {
            Session::addMessageAfterRedirect(__('Incorrect value for date field.'), false, ERROR);
            return false;
        }

        return true;
    }

    /**
     * Compute default values for Add
     * (to be passed in prepareInputForAdd before and after rules if needed)
     *
     * @since 0.84
     *
     * @param $input
     *
     * @return string
     **/
    public function computeDefaultValuesForAdd($input)
    {

        if (!isset($input["status"])) {
            $input["status"] = self::INCOMING;
        }

        if (!isset($input["date"]) || empty($input["date"]) || $input["date"] == 'NULL') {
            $input["date"] = $_SESSION["glpi_currenttime"];
        }

        if (isset($input["status"]) && in_array($input["status"], $this->getSolvedStatusArray())) {
            if (isset($input["date"])) {
                $input["solvedate"] = $input["date"];
            } else {
                $input["solvedate"] = $_SESSION["glpi_currenttime"];
            }
        }

        if (isset($input["status"]) && in_array($input["status"], $this->getClosedStatusArray())) {
            if (isset($input["date"])) {
                $input["closedate"] = $input["date"];
            } else {
                $input["closedate"] = $_SESSION["glpi_currenttime"];
            }
            $input['solvedate'] = $input["closedate"];
        }

       // Set begin waiting time if status is waiting
        if (isset($input["status"]) && ($input["status"] == self::WAITING)) {
            $input['begin_waiting_date'] = $input['date'];
        }

        return $input;
    }


    public function post_addItem()
    {
        // Handle rich-text images and uploaded documents
        $this->input = $this->addFiles($this->input, ['force_update' => true]);

       // Add default document if set in template
        if (
            isset($this->input['_documents_id'])
            && is_array($this->input['_documents_id'])
            && count($this->input['_documents_id'])
        ) {
            $docitem = new Document_Item();
            foreach ($this->input['_documents_id'] as $docID) {
                $docitem->add(['documents_id' => $docID,
                    '_do_notif'    => false,
                    'itemtype'     => $this->getType(),
                    'items_id'     => $this->fields['id']
                ]);
            }
        }

        // handle actors changes
        $this->updateActors(true);

        // Handle "_tasktemplates_id" special input
        $this->handleTaskTemplateInput();

        // Handle "_itilfollowuptemplates_id" special input
        $this->handleITILFollowupTemplateInput();

        // Handle "_solutiontemplates_id" special input
        $this->handleSolutionTemplateInput();

        // Send validation requests
        $this->manageValidationAdd($this->input);

        parent::post_addItem();
    }

    /**
     * @see Glpi\Features\Clonable::post_clone
     */
    public function post_clone($source, $history)
    {
        global $DB;
        $update = [];
        if (isset($source->fields['users_id_lastupdater'])) {
            $update['users_id_lastupdater'] = $source->fields['users_id_lastupdater'];
        }
        if (isset($source->fields['status'])) {
            $update['status'] = $source->fields['status'];
        }
        $DB->update(
            $this->getTable(),
            $update,
            ['id' => $this->getID()]
        );
    }

    public function getCloneRelations(): array
    {
        $relations = [];

        if (is_a($this->userlinkclass, CommonITILActor::class, true)) {
            $relations[] = $this->userlinkclass;
        }
        if (is_a($this->grouplinkclass, CommonITILActor::class, true)) {
            $relations[] = $this->grouplinkclass;
        }
        if (is_a($this->supplierlinkclass, CommonITILActor::class, true)) {
            $relations[] = $this->supplierlinkclass;
        }

        return $relations;
    }


    /**
     * Compute Priority
     *
     * @since 0.84
     *
     * @param $urgency   integer from 1 to 5
     * @param $impact    integer from 1 to 5
     *
     * @return integer from 1 to 5 (priority)
     **/
    public static function computePriority($urgency, $impact)
    {
        global $CFG_GLPI;

        if (isset($CFG_GLPI[static::MATRIX_FIELD][$urgency][$impact])) {
            return $CFG_GLPI[static::MATRIX_FIELD][$urgency][$impact];
        }
       // Failback to trivial
        return round(($urgency + $impact) / 2);
    }


    /**
     * Dropdown of ITIL object priority
     *
     * @since  version 0.84 new proto
     *
     * @param $options array of options
     *       - name     : select name (default is urgency)
     *       - value    : default value (default 0)
     *       - showtype : list proposed : normal, search (default normal)
     *       - wthmajor : boolean with major priority ?
     *       - display  : boolean if false get string
     *
     * @return string id of the select
     **/
    public static function dropdownPriority(array $options = [])
    {
        global $CFG_GLPI;

        $p = [
            'name'      => 'priority',
            'value'     => 0,
            'showtype'  => 'normal',
            'display'   => true,
            'withmajor' => false,
            'enable_filtering' => true,
            'templateResult'    => "templateItilPriority",
            'templateSelection' => "templateItilPriority",
        ];

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $p[$key] = $val;
            }
        }

        $values = [];

        if ($p['showtype'] == 'search') {
            $values[0]  = static::getPriorityName(0);
            $values[-5] = static::getPriorityName(-5);
            $values[-4] = static::getPriorityName(-4);
            $values[-3] = static::getPriorityName(-3);
            $values[-2] = static::getPriorityName(-2);
            $values[-1] = static::getPriorityName(-1);
        }

        if (
            ($p['showtype'] == 'search')
            || $p['withmajor']
        ) {
            $values[6] = static::getPriorityName(6);
        }

        $values[5] = static::getPriorityName(5);
        $values[4] = static::getPriorityName(4);
        $values[3] = static::getPriorityName(3);
        $values[2] = static::getPriorityName(2);
        $values[1] = static::getPriorityName(1);

        if ($p['enable_filtering']) {
            $urgencies = [];
            if (isset($CFG_GLPI[static::URGENCY_MASK_FIELD])) {
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 5)
                ) {
                    $urgencies[] = 5;
                }
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 4)
                ) {
                    $urgencies[] = 4;
                }
                $urgencies[] = 3;
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 2)
                ) {
                    $urgencies[] = 2;
                }
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 1)
                ) {
                    $urgencies[] = 1;
                }
            }
            $impacts = [];
            if (isset($CFG_GLPI[static::IMPACT_MASK_FIELD])) {
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 5)
                ) {
                    $impacts[] = 5;
                }
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 4)
                ) {
                    $impacts[] = 4;
                }
                $impacts[] = 3;
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 2)
                ) {
                    $impacts[] = 2;
                }
                if (
                    ($p['showtype'] == 'search')
                    || $CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 1)
                ) {
                    $impacts[] = 1;
                }
            }

            $active_priorities = [];
            foreach ($urgencies as $urgency) {
                foreach ($impacts as $impact) {
                    if (isset($CFG_GLPI["_matrix_{$urgency}_{$impact}"])) {
                        $active_priorities[] = $CFG_GLPI["_matrix_{$urgency}_{$impact}"];
                    }
                }
            }
            $active_priorities = array_unique($active_priorities);
            if (count($active_priorities) > 0) {
                foreach ($values as $priority => $name) {
                    if (!in_array($priority, $active_priorities)) {
                        if ($p['withmajor'] && $priority == 6) {
                            continue;
                        }

                        // don't unset current value (to avoid selecting major priority on existing item)
                        if ($priority != $p['value']) {
                            unset($values[$priority]);
                        }
                    }
                }
            }
        }

        return Dropdown::showFromArray($p['name'], $values, $p);
    }


    /**
     * Get ITIL object priority Name
     *
     * @param integer $value priority ID
     **/
    public static function getPriorityName($value)
    {

        switch ($value) {
            case 6:
                return _x('priority', 'Major');

            case 5:
                return _x('priority', 'Very high');

            case 4:
                return _x('priority', 'High');

            case 3:
                return _x('priority', 'Medium');

            case 2:
                return _x('priority', 'Low');

            case 1:
                return _x('priority', 'Very low');

           // No standard one :
            case 0:
                return _x('priority', 'All');
            case -1:
                return _x('priority', 'At least very low');
            case -2:
                return _x('priority', 'At least low');
            case -3:
                return _x('priority', 'At least medium');
            case -4:
                return _x('priority', 'At least high');
            case -5:
                return _x('priority', 'At least very high');

            default:
               // Return $value if not define
                return $value;
        }
    }


    /**
     * Dropdown of ITIL object Urgency
     *
     * @since 0.84 new proto
     *
     * @param $options array of options
     *       - name     : select name (default is urgency)
     *       - value    : default value (default 0)
     *       - showtype : list proposed : normal, search (default normal)
     *       - display  : boolean if false get string
     *
     * @return string id of the select
     **/
    public static function dropdownUrgency(array $options = [])
    {
        global $CFG_GLPI;

        $p = [
            'name'     => 'urgency',
            'value'    => 0,
            'showtype' => 'normal',
            'display'  => true,
        ];

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $p[$key] = $val;
            }
        }

        $values = [];

        if ($p['showtype'] == 'search') {
            $values[0]  = static::getUrgencyName(0);
            $values[-5] = static::getUrgencyName(-5);
            $values[-4] = static::getUrgencyName(-4);
            $values[-3] = static::getUrgencyName(-3);
            $values[-2] = static::getUrgencyName(-2);
            $values[-1] = static::getUrgencyName(-1);
        }

        if (isset($CFG_GLPI[static::URGENCY_MASK_FIELD])) {
            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 5))
            ) {
                $values[5]  = static::getUrgencyName(5);
            }

            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 4))
            ) {
                $values[4]  = static::getUrgencyName(4);
            }

            $values[3]  = static::getUrgencyName(3);

            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 2))
            ) {
                $values[2]  = static::getUrgencyName(2);
            }

            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::URGENCY_MASK_FIELD] & (1 << 1))
            ) {
                $values[1]  = static::getUrgencyName(1);
            }
        }

        return Dropdown::showFromArray($p['name'], $values, $p);
    }


    /**
     * Get ITIL object Urgency Name
     *
     * @param integer $value urgency ID
     **/
    public static function getUrgencyName($value)
    {

        switch ($value) {
            case 5:
                return _x('urgency', 'Very high');

            case 4:
                return _x('urgency', 'High');

            case 3:
                return _x('urgency', 'Medium');

            case 2:
                return _x('urgency', 'Low');

            case 1:
                return _x('urgency', 'Very low');

           // No standard one :
            case 0:
                return _x('urgency', 'All');
            case -1:
                return _x('urgency', 'At least very low');
            case -2:
                return _x('urgency', 'At least low');
            case -3:
                return _x('urgency', 'At least medium');
            case -4:
                return _x('urgency', 'At least high');
            case -5:
                return _x('urgency', 'At least very high');

            default:
               // Return $value if not define
                return $value;
        }
    }


    /**
     * Dropdown of ITIL object Impact
     *
     * @since 0.84 new proto
     *
     * @param $options   array of options
     *  - name     : select name (default is impact)
     *  - value    : default value (default 0)
     *  - showtype : list proposed : normal, search (default normal)
     *  - display  : boolean if false get string
     *
     * \
     * @return string id of the select
     **/
    public static function dropdownImpact(array $options = [])
    {
        global $CFG_GLPI;

        $p = [
            'name'     => 'impact',
            'value'    => 0,
            'showtype' => 'normal',
            'display'  => true,
        ];

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $p[$key] = $val;
            }
        }
        $values = [];

        if ($p['showtype'] == 'search') {
            $values[0]  = static::getImpactName(0);
            $values[-5] = static::getImpactName(-5);
            $values[-4] = static::getImpactName(-4);
            $values[-3] = static::getImpactName(-3);
            $values[-2] = static::getImpactName(-2);
            $values[-1] = static::getImpactName(-1);
        }

        if (isset($CFG_GLPI[static::IMPACT_MASK_FIELD])) {
            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 5))
            ) {
                $values[5]  = static::getImpactName(5);
            }

            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 4))
            ) {
                $values[4]  = static::getImpactName(4);
            }

            $values[3]  = static::getImpactName(3);

            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 2))
            ) {
                $values[2]  = static::getImpactName(2);
            }

            if (
                ($p['showtype'] == 'search')
                || ($CFG_GLPI[static::IMPACT_MASK_FIELD] & (1 << 1))
            ) {
                $values[1]  = static::getImpactName(1);
            }
        }

        return Dropdown::showFromArray($p['name'], $values, $p);
    }


    /**
     * Get ITIL object Impact Name
     *
     * @param integer $value impact ID
     **/
    public static function getImpactName($value)
    {

        switch ($value) {
            case 5:
                return _x('impact', 'Very high');

            case 4:
                return _x('impact', 'High');

            case 3:
                return _x('impact', 'Medium');

            case 2:
                return _x('impact', 'Low');

            case 1:
                return _x('impact', 'Very low');

           // No standard one :
            case 0:
                return _x('impact', 'All');
            case -1:
                return _x('impact', 'At least very low');
            case -2:
                return _x('impact', 'At least low');
            case -3:
                return _x('impact', 'At least medium');
            case -4:
                return _x('impact', 'At least high');
            case -5:
                return _x('impact', 'At least very high');

            default:
               // Return $value if not define
                return $value;
        }
    }


    /**
     * Get the ITIL object status list
     *
     * @param $withmetaforsearch boolean (false by default)
     *
     * @return array
     **/
    public static function getAllStatusArray($withmetaforsearch = false)
    {

       // To be overridden by class
        $tab = [];

        return $tab;
    }


    /**
     * Get the ITIL object closed status list
     *
     * @since 0.83
     *
     * @return array
     **/
    public static function getClosedStatusArray()
    {

       // To be overridden by class
        $tab = [];
        return $tab;
    }


    /**
     * Get the ITIL object solved status list
     *
     * @since 0.83
     *
     * @return array
     **/
    public static function getSolvedStatusArray()
    {

       // To be overridden by class
        $tab = [];
        return $tab;
    }

    /**
     * Get the ITIL object all status list without solved and closed status
     *
     * @since 9.2.1
     *
     * @return array
     **/
    public static function getNotSolvedStatusArray()
    {
        $all = static::getAllStatusArray();
        foreach (static::getSolvedStatusArray() as $status) {
            if (isset($all[$status])) {
                unset($all[$status]);
            }
        }
        foreach (static::getClosedStatusArray() as $status) {
            if (isset($all[$status])) {
                unset($all[$status]);
            }
        }
        $nosolved = array_keys($all);

        return $nosolved;
    }


    /**
     * Get the ITIL object new status list
     *
     * @since 0.83.8
     *
     * @return array
     **/
    public static function getNewStatusArray()
    {

       // To be overriden by class
        $tab = [];
        return $tab;
    }


    /**
     * Get the ITIL object process status list
     *
     * @since 0.83
     *
     * @return array
     **/
    public static function getProcessStatus()
    {

       // To be overridden by class
        $tab = [];
        return $tab;
    }


    /**
     * check is the user can change from / to a status
     *
     * @since 0.84
     *
     * @param integer $old value of old/current status
     * @param integer $new value of target status
     *
     * @return boolean
     **/
    public static function isAllowedStatus($old, $new)
    {

        if (
            isset($_SESSION['glpiactiveprofile'][static::STATUS_MATRIX_FIELD][$old][$new])
            && !$_SESSION['glpiactiveprofile'][static::STATUS_MATRIX_FIELD][$old][$new]
        ) {
            return false;
        }

        if (
            array_key_exists(
                static::STATUS_MATRIX_FIELD,
                $_SESSION['glpiactiveprofile']
            )
            && static::isStatusExists($new)
        ) { // maybe not set for post-only
            return true;
        }

        return false;
    }


    /**
     * Check if an itil object is still in an open status
     *
     * @since 10.0
     *
     * @return bool
     */
    public function isNotSolved()
    {
        return !in_array(
            $this->fields['status'],
            array_merge(
                $this->getSolvedStatusArray(),
                $this->getClosedStatusArray()
            )
        );
    }

    /**
     * Check if an itil object has a solved status
     *
     * @since 10.0
     *
     * @param bool $include_closed do we want ticket with closed status also ?
     *
     * @return bool
     */
    public function isSolved(bool $include_closed = false)
    {
        $status = $this->getSolvedStatusArray();
        if ($include_closed) {
            $status = array_merge($status, $this->getClosedStatusArray());
        }

        return in_array(
            $this->fields['status'],
            $status
        );
    }

    /**
     * Check if an itil object has a closed status
     *
     * @since 10.0
     *
     * @return bool
     */
    public function isClosed()
    {
        return in_array(
            $this->fields['status'],
            $this->getClosedStatusArray()
        );
    }


    /**
     * Get the ITIL object status allowed for a current status
     *
     * @since 0.84 new proto
     *
     * @param int|null $current   status
     *
     * @return array
     **/
    public static function getAllowedStatusArray($current)
    {

        $tab = static::getAllStatusArray();
        if (!static::isStatusExists($current)) {
            $current = self::INCOMING;
        }

        foreach (array_keys($tab) as $status) {
            if (
                ($status != $current)
                && !static::isAllowedStatus($current, $status)
            ) {
                unset($tab[$status]);
            }
        }
        return $tab;
    }

    /**
     * Is the ITIL object status exists for the object
     *
     * @since 0.85
     *
     * @param integer $status   status
     *
     * @return boolean
     **/
    public static function isStatusExists($status)
    {

        $tab = static::getAllStatusArray();

        return isset($tab[$status]);
    }

    /**
     * Dropdown of object status
     *
     * @since 0.84 new proto
     *
     * @param $options   array of options
     *  - name     : select name (default is status)
     *  - value    : default value (default self::INCOMING)
     *  - showtype : list proposed : normal, search or allowed (default normal)
     *  - display  : boolean if false get string
     *
     * @return string|integer Output string if display option is set to false,
     *                        otherwise random part of dropdown id
     **/
    public static function dropdownStatus(array $options = [])
    {

        $p = [
            'name'              => 'status',
            'showtype'          => 'normal',
            'display'           => true,
            'templateResult'    => "templateItilStatus",
            'templateSelection' => "templateItilStatus",
        ];

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $p[$key] = $val;
            }
        }

        if (!isset($p['value']) || empty($p['value'])) {
            $p['value']     = self::INCOMING;
        }

        switch ($p['showtype']) {
            case 'allowed':
                $current = isset($p['value_calculation']) && $p['value_calculation'] !== ''
                    ? $p['value_calculation']
                    : $p['value'];
                $tab = static::getAllowedStatusArray($current);
                break;

            case 'search':
                $tab = static::getAllStatusArray(true);
                break;

            default:
                $tab = static::getAllStatusArray(false);
                break;
        }

        return Dropdown::showFromArray($p['name'], $tab, $p);
    }


    /**
     * Get ITIL object status Name
     *
     * @since 0.84
     *
     * @param integer $value     status ID
     **/
    public static function getStatus($value)
    {

        $tab  = static::getAllStatusArray(true);
       // Return $value if not defined
        return (isset($tab[$value]) ? $tab[$value] : $value);
    }


    /**
     * get field part name corresponding to actor type
     *
     * @param $type      integer : user type
     *
     * @since 0.84.6
     *
     * @return string|boolean Field part or false if not applicable
     **/
    public static function getActorFieldNameType($type)
    {

        switch ($type) {
            case CommonITILActor::REQUESTER:
                return 'requester';

            case CommonITILActor::OBSERVER:
                return 'observer';

            case CommonITILActor::ASSIGN:
                return 'assign';

            default:
                return false;
        }
    }

    /**
     * display a value according to a field
     *
     * @since 0.83
     *
     * @param $field     String         name of the field
     * @param $values    String / Array with the value to display
     * @param $options   Array          of option
     *
     * @return a string
     **/
    public static function getSpecificValueToDisplay($field, $values, array $options = [])
    {

        if (!is_array($values)) {
            $values = [$field => $values];
        }
        switch ($field) {
            case 'status':
                return static::getStatus($values[$field]);

            case 'urgency':
                return static::getUrgencyName($values[$field]);

            case 'impact':
                return static::getImpactName($values[$field]);

            case 'priority':
                return static::getPriorityName($values[$field]);

            case 'global_validation':
                return CommonITILValidation::getStatus($values[$field]);
        }
        return parent::getSpecificValueToDisplay($field, $values, $options);
    }


    /**
     * @since 0.84
     *
     * @param $field
     * @param $name            (default '')
     * @param $values          (default '')
     * @param $options   array
     **/
    public static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = [])
    {

        if (!is_array($values)) {
            $values = [$field => $values];
        }
        $options['display'] = false;

        switch ($field) {
            case 'status':
                $options['name']  = $name;
                $options['value'] = $values[$field];
                return static::dropdownStatus($options);

            case 'impact':
                $options['name']  = $name;
                $options['value'] = $values[$field];
                return static::dropdownImpact($options);

            case 'urgency':
                $options['name']  = $name;
                $options['value'] = $values[$field];
                return static::dropdownUrgency($options);

            case 'priority':
                $options['name']  = $name;
                $options['value'] = $values[$field];
                return static::dropdownPriority($options);

            case 'global_validation':
                $options['global'] = true;
                $options['value']  = $values[$field];
                return CommonITILValidation::dropdownStatus($name, $options);
        }
        return parent::getSpecificValueToSelect($field, $name, $values, $options);
    }


    /**
     * @since 0.85
     *
     * @see CommonDBTM::showMassiveActionsSubForm()
     **/
    public static function showMassiveActionsSubForm(MassiveAction $ma)
    {
        global $CFG_GLPI;

        switch ($ma->getAction()) {
            case 'add_task':
                $itemtype = $ma->getItemtype(true);
                $tasktype = $itemtype . 'Task';
                if ($ttype = getItemForItemtype($tasktype)) {
                    $ttype->showMassiveActionAddTaskForm();
                    return true;
                }
                return false;

            case 'add_actor':
                $types            = [0                          => Dropdown::EMPTY_VALUE,
                    CommonITILActor::REQUESTER => _n('Requester', 'Requesters', 1),
                    CommonITILActor::OBSERVER  => _n('Watcher', 'Watchers', 1),
                    CommonITILActor::ASSIGN    => __('Assigned to')
                ];
                $rand             = Dropdown::showFromArray('actortype', $types);

                $paramsmassaction = ['actortype' => '__VALUE__'];

                Ajax::updateItemOnSelectEvent(
                    "dropdown_actortype$rand",
                    "show_massiveaction_field",
                    $CFG_GLPI["root_doc"] .
                                             "/ajax/dropdownMassiveActionAddActor.php",
                    $paramsmassaction
                );
                echo "<span id='show_massiveaction_field'>&nbsp;</span>\n";
                return true;
            case 'update_notif':
                Dropdown::showYesNo('use_notification');
                echo "<br><br>";
                echo Html::submit(_x('button', 'Post'), ['name' => 'massiveaction']);
                return true;
            return true;
        }
        return parent::showMassiveActionsSubForm($ma);
    }


    /**
     * @since 0.85
     *
     * @see CommonDBTM::processMassiveActionsForOneItemtype()
     **/
    public static function processMassiveActionsForOneItemtype(
        MassiveAction $ma,
        CommonDBTM $item,
        array $ids
    ) {

        switch ($ma->getAction()) {
            case 'add_actor':
                $input = $ma->getInput();
                foreach ($ids as $id) {
                    $input2 = ['id' => $id];
                    if (isset($input['_itil_requester'])) {
                        $input2['_itil_requester'] = $input['_itil_requester'];
                    }
                    if (isset($input['_itil_observer'])) {
                        $input2['_itil_observer'] = $input['_itil_observer'];
                    }
                    if (isset($input['_itil_assign'])) {
                        $input2['_itil_assign'] = $input['_itil_assign'];
                    }
                    if ($item->can($id, UPDATE)) {
                        if ($item->update($input2)) {
                            $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                        } else {
                            $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                            $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                        }
                    } else {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT);
                        $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
                    }
                }
                return;

            case 'update_notif':
                $input = $ma->getInput();
                foreach ($ids as $id) {
                    if ($item->can($id, UPDATE)) {
                        $linkclass = new $item->userlinkclass();
                        foreach ($linkclass->getActors($id) as $users) {
                            foreach ($users as $data) {
                                $data['use_notification'] = $input['use_notification'];
                                $linkclass->update($data);
                            }
                        }
                        $linkclass = new $item->supplierlinkclass();
                        foreach ($linkclass->getActors($id) as $users) {
                            foreach ($users as $data) {
                                 $data['use_notification'] = $input['use_notification'];
                                 $linkclass->update($data);
                            }
                        }

                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                    } else {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT);
                        $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
                    }
                }
                return;

            case 'add_task':
                if (!($task = getItemForItemtype($item->getType() . 'Task'))) {
                    $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
                    break;
                }
                $field = $item->getForeignKeyField();

                $input = $ma->getInput();

                foreach ($ids as $id) {
                    if ($item->getFromDB($id)) {
                        $input2 = [
                            $field              => $id,
                            'taskcategories_id' => $input['taskcategories_id'],
                            'actiontime'        => $input['actiontime'],
                            'state'             => $input['state'],
                            'content'           => $input['content']
                        ];
                        if ($task->can(-1, CREATE, $input2) && !in_array($item->fields['status'], array_merge($item->getSolvedStatusArray(), $item->getClosedStatusArray()))) {
                            if ($task->add($input2)) {
                                $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                            } else {
                                $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                                $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                            }
                        } else {
                            $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT);
                            $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
                        }
                    } else {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                        $ma->addMessage($item->getErrorMessage(ERROR_NOT_FOUND));
                    }
                }
                return;
        }
        parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
    }


    /**
     * @since 0.85
     **/
    public function getSearchOptionsMain()
    {
        global $DB;

        $tab = [];

        $tab[] = [
            'id'                 => 'common',
            'name'               => __('Characteristics')
        ];

        $tab[] = [
            'id'                 => '1',
            'table'              => $this->getTable(),
            'field'              => 'name',
            'name'               => __('Title'),
            'datatype'           => 'itemlink',
            'searchtype'         => 'contains',
            'massiveaction'      => false,
            'additionalfields'   => ['id', 'status']
        ];

        $tab[] = [
            'id'                 => '21',
            'table'              => $this->getTable(),
            'field'              => 'content',
            'name'               => __('Description'),
            'massiveaction'      => false,
            'datatype'           => 'text',
            'htmltext'           => true
        ];

        $tab[] = [
            'id'                 => '2',
            'table'              => $this->getTable(),
            'field'              => 'id',
            'name'               => __('ID'),
            'massiveaction'      => false,
            'datatype'           => 'number'
        ];

        $tab[] = [
            'id'                 => '12',
            'table'              => $this->getTable(),
            'field'              => 'status',
            'name'               => __('Status'),
            'searchtype'         => 'equals',
            'datatype'           => 'specific'
        ];

        $tab[] = [
            'id'                 => '10',
            'table'              => $this->getTable(),
            'field'              => 'urgency',
            'name'               => __('Urgency'),
            'searchtype'         => 'equals',
            'datatype'           => 'specific'
        ];

        $tab[] = [
            'id'                 => '11',
            'table'              => $this->getTable(),
            'field'              => 'impact',
            'name'               => __('Impact'),
            'searchtype'         => 'equals',
            'datatype'           => 'specific'
        ];

        $tab[] = [
            'id'                 => '3',
            'table'              => $this->getTable(),
            'field'              => 'priority',
            'name'               => __('Priority'),
            'searchtype'         => 'equals',
            'datatype'           => 'specific'
        ];

        $tab[] = [
            'id'                 => '15',
            'table'              => $this->getTable(),
            'field'              => 'date',
            'name'               => __('Opening date'),
            'datatype'           => 'datetime',
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '16',
            'table'              => $this->getTable(),
            'field'              => 'closedate',
            'name'               => __('Closing date'),
            'datatype'           => 'datetime',
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '18',
            'table'              => $this->getTable(),
            'field'              => 'time_to_resolve',
            'name'               => __('Time to resolve'),
            'datatype'           => 'datetime',
            'maybefuture'        => true,
            'massiveaction'      => false,
            'additionalfields'   => ['solvedate', 'status']
        ];

        $tab[] = [
            'id'                 => '151',
            'table'              => $this->getTable(),
            'field'              => 'time_to_resolve',
            'name'               => __('Time to resolve + Progress'),
            'massiveaction'      => false,
            'nosearch'           => true,
            'additionalfields'   => ['status'],
        ];

        $tab[] = [
            'id'                 => '82',
            'table'              => $this->getTable(),
            'field'              => 'is_late',
            'name'               => __('Time to resolve exceeded'),
            'datatype'           => 'bool',
            'massiveaction'      => false,
            'computation'        => self::generateSLAOLAComputation('time_to_resolve')
        ];

        $tab[] = [
            'id'                 => '17',
            'table'              => $this->getTable(),
            'field'              => 'solvedate',
            'name'               => __('Resolution date'),
            'datatype'           => 'datetime',
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '19',
            'table'              => $this->getTable(),
            'field'              => 'date_mod',
            'name'               => __('Last update'),
            'datatype'           => 'datetime',
            'massiveaction'      => false
        ];

        $newtab = [
            'id'                 => '7',
            'table'              => 'glpi_itilcategories',
            'field'              => 'completename',
            'name'               => _n('Category', 'Categories', 1),
            'datatype'           => 'dropdown'
        ];

        if (
            !Session::isCron() // no filter for cron
            && Session::getCurrentInterface() == 'helpdesk'
        ) {
            $newtab['condition']         = ['is_helpdeskvisible' => 1];
        }
        $tab[] = $newtab;

        $tab[] = [
            'id'                 => '80',
            'table'              => 'glpi_entities',
            'field'              => 'completename',
            'name'               => Entity::getTypeName(1),
            'massiveaction'      => false,
            'datatype'           => 'dropdown'
        ];

        $tab[] = [
            'id'                 => '45',
            'table'              => $this->getTable(),
            'field'              => 'actiontime',
            'name'               => __('Total duration'),
            'datatype'           => 'timestamp',
            'massiveaction'      => false,
            'nosearch'           => true
        ];

        $newtab = [
            'id'                 => '64',
            'table'              => 'glpi_users',
            'field'              => 'name',
            'linkfield'          => 'users_id_lastupdater',
            'name'               => __('Last edit by'),
            'massiveaction'      => false,
            'datatype'           => 'dropdown',
            'right'              => 'all'
        ];

       // Filter search fields for helpdesk
        if (
            !Session::isCron() // no filter for cron
            && Session::getCurrentInterface() != 'central'
        ) {
           // last updater no search
            $newtab['nosearch'] = true;
        }
        $tab[] = $newtab;

       // add objectlock search options
        $tab = array_merge($tab, ObjectLock::rawSearchOptionsToAdd(get_class($this)));

       // For ITIL template
        $tab[] = [
            'id'                 => '142',
            'table'              => 'glpi_documents',
            'field'              => 'name',
            'name'               => Document::getTypeName(Session::getPluralNumber()),
            'forcegroupby'       => true,
            'usehaving'          => true,
            'nosearch'           => true,
            'nodisplay'          => true,
            'datatype'           => 'dropdown',
            'massiveaction'      => false,
            'joinparams'         => [
                'jointype'           => 'items_id',
                'beforejoin'         => [
                    'table'              => 'glpi_documents_items',
                    'joinparams'         => [
                        'jointype'           => 'itemtype_item'
                    ]
                ]
            ]
        ];

        $tab[] = [
            'id'                 => '400',
            'table'              => PendingReason::getTable(),
            'field'              => 'name',
            'name'               => PendingReason::getTypeName(1),
            'massiveaction'      => false,
            'searchtype'         => ['equals', 'notequals'],
            'datatype'           => 'dropdown',
            'joinparams'         => [
                'jointype'           => 'items_id',
                'beforejoin'         => [
                    'table'              => PendingReason_Item::getTable(),
                    'joinparams'         => [
                        'jointype'           => 'itemtype_item'
                    ]
                ]
            ]
        ];

        $location_so = Location::rawSearchOptionsToAdd();
        foreach ($location_so as &$so) {
           //duplicated search options :(
            switch ($so['id']) {
                case 3:
                    $so['id'] = 83;
                    break;
                case 91:
                    $so['id'] = 84;
                    break;
                case 92:
                    $so['id'] = 85;
                    break;
                case 93:
                    $so['id'] = 86;
                    break;
            }
        }

        $tab = array_merge($tab, $location_so);

        $tab = array_merge($tab, Project::rawSearchOptionsToAdd(static::class));

        return $tab;
    }


    /**
     * @since 0.85
     **/
    public function getSearchOptionsSolution()
    {
        global $DB;
        $tab = [];

        $tab[] = [
            'id'                 => 'solution',
            'name'               => ITILSolution::getTypeName(1)
        ];

        $tab[] = [
            'id'                 => '23',
            'table'              => 'glpi_solutiontypes',
            'field'              => 'name',
            'name'               => SolutionType::getTypeName(1),
            'datatype'           => 'dropdown',
            'massiveaction'      => false,
            'forcegroupby'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => ITILSolution::getTable(),
                    'joinparams'         => [
                        'jointype'           => 'itemtype_item',
                    ]
                ]
            ]
        ];

        $tab[] = [
            'id'                 => '24',
            'table'              => ITILSolution::getTable(),
            'field'              => 'content',
            'name'               => ITILSolution::getTypeName(1),
            'datatype'           => 'text',
            'htmltext'           => true,
            'massiveaction'      => false,
            'forcegroupby'       => true,
            'joinparams'         => [
                'jointype'           => 'itemtype_item'
            ]
        ];

        $tab[] = [
            'id'                  => '38',
            'table'               => ITILSolution::getTable(),
            'field'               => 'status',
            'name'                => __('Any solution status'),
            'datatype'            => 'specific',
            'searchtype'          => ['equals', 'notequals'],
            'searchequalsonfield' => true,
            'massiveaction'       => false,
            'forcegroupby'        => true,
            'joinparams'          => [
                'jointype' => 'itemtype_item'
            ]
        ];

        $tab[] = [
            'id'                  => '39',
            'table'               => ITILSolution::getTable(),
            'field'               => 'status',
            'name'                => __('Last solution status'),
            'datatype'            => 'specific',
            'searchtype'          => ['equals', 'notequals'],
            'searchequalsonfield' => true,
            'massiveaction'       => false,
            'forcegroupby'        => true,
            'joinparams'          => [
                'jointype'  => 'itemtype_item',
            // Get only last created solution
                'condition' => [
                    'NEWTABLE.id'  => ['=', new QuerySubQuery([
                        'SELECT' => 'id',
                        'FROM'   => ITILSolution::getTable(),
                        'WHERE'  => [
                            ITILSolution::getTable() . '.items_id' => new QueryExpression($DB->quoteName('REFTABLE.id')),
                            ITILSolution::getTable() . '.itemtype' => static::getType()
                        ],
                        'ORDER'  => ITILSolution::getTable() . '.id DESC',
                        'LIMIT'  => 1
                    ])
                    ]
                ]
            ]
        ];

        return $tab;
    }


    public function getSearchOptionsStats()
    {
        $tab = [];

        $tab[] = [
            'id'                 => 'stats',
            'name'               => __('Statistics')
        ];

        $tab[] = [
            'id'                 => '154',
            'table'              => $this->getTable(),
            'field'              => 'solve_delay_stat',
            'name'               => __('Resolution time'),
            'datatype'           => 'timestamp',
            'forcegroupby'       => true,
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '152',
            'table'              => $this->getTable(),
            'field'              => 'close_delay_stat',
            'name'               => __('Closing time'),
            'datatype'           => 'timestamp',
            'forcegroupby'       => true,
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '153',
            'table'              => $this->getTable(),
            'field'              => 'waiting_duration',
            'name'               => __('Waiting time'),
            'datatype'           => 'timestamp',
            'forcegroupby'       => true,
            'massiveaction'      => false
        ];

        return $tab;
    }


    public function getSearchOptionsActors()
    {
        $tab = [];

        $tab[] = [
            'id'                 => 'requester',
            'name'               => _n('Requester', 'Requesters', 1)
        ];

        $newtab = [
            'id'                 => '4', // Also in Ticket_User::post_addItem() and Log::getHistoryData()
            'table'              => 'glpi_users',
            'field'              => 'name',
            'datatype'           => 'dropdown',
            'right'              => 'all',
            'name'               => _n('Requester', 'Requesters', 1),
            'forcegroupby'       => true,
            'massiveaction'      => false,
            'use_subquery'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => getTableForItemType($this->userlinkclass),
                    'joinparams'         => [
                        'jointype'           => 'child',
                        'condition'          => ['NEWTABLE.type' => CommonITILActor::REQUESTER]
                    ]
                ]
            ]
        ];

        if (
            !Session::isCron() // no filter for cron
            && Session::getCurrentInterface() == 'helpdesk'
        ) {
            $newtab['right']       = 'id';
        }
        $tab[] = $newtab;

        $newtab = [
            'id'                 => '71',  // Also in Group_Ticket::post_addItem() and Log::getHistoryData()
            'table'              => 'glpi_groups',
            'field'              => 'completename',
            'datatype'           => 'dropdown',
            'name'               => _n('Requester group', 'Requester groups', 1),
            'forcegroupby'       => true,
            'massiveaction'      => false,
            'condition'          => ['is_requester' => 1],
            'use_subquery'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => getTableForItemType($this->grouplinkclass),
                    'joinparams'         => [
                        'jointype'           => 'child',
                        'condition'          => ['NEWTABLE.type' => CommonITILActor::REQUESTER]
                    ]
                ]
            ]
        ];

        if (
            !Session::isCron() // no filter for cron
            && Session::getCurrentInterface() == 'helpdesk'
        ) {
            $newtab['condition'] = array_merge(
                $newtab['condition'],
                ['id' => $_SESSION['glpigroups']]
            );
        }
        $tab[] = $newtab;

        $newtab = [
            'id'                 => '22',
            'table'              => 'glpi_users',
            'field'              => 'name',
            'datatype'           => 'dropdown',
            'right'              => 'all',
            'linkfield'          => 'users_id_recipient',
            'name'               => __('Writer')
        ];

        if (
            !Session::isCron() // no filter for cron
            && Session::getCurrentInterface() == 'helpdesk'
        ) {
            $newtab['right']       = 'id';
        }
        $tab[] = $newtab;

        $tab[] = [
            'id'                 => 'observer',
            'name'               => _n('Watcher', 'Watchers', 1)
        ];

        $tab[] = [
            'id'                 => '66', // Also in Ticket_User::post_addItem() and Log::getHistoryData()
            'table'              => 'glpi_users',
            'field'              => 'name',
            'datatype'           => 'dropdown',
            'right'              => 'all',
            'name'               => _n('Watcher', 'Watchers', 1),
            'forcegroupby'       => true,
            'massiveaction'      => false,
            'use_subquery'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => getTableForItemType($this->userlinkclass),
                    'joinparams'         => [
                        'jointype'           => 'child',
                        'condition'          => ['NEWTABLE.type' => CommonITILActor::OBSERVER]
                    ]
                ]
            ]
        ];

        $tab[] = [
            'id'                 => '65', // Also in Group_Ticket::post_addItem() and Log::getHistoryData()
            'table'              => 'glpi_groups',
            'field'              => 'completename',
            'datatype'           => 'dropdown',
            'name'               => _n('Watcher group', 'Watcher groups', 1),
            'forcegroupby'       => true,
            'massiveaction'      => false,
            'condition'          => ['is_watcher' => 1],
            'use_subquery'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => getTableForItemType($this->grouplinkclass),
                    'joinparams'         => [
                        'jointype'           => 'child',
                        'condition'          => ['NEWTABLE.type' => CommonITILActor::OBSERVER]
                    ]
                ]
            ]
        ];

        $tab[] = [
            'id'                 => 'assign',
            'name'               => __('Assigned to')
        ];

        $tab[] = [
            'id'                 => '5', // Also in Ticket_User::post_addItem() and Log::getHistoryData()
            'table'              => 'glpi_users',
            'field'              => 'name',
            'datatype'           => 'dropdown',
            'right'              => 'own_ticket',
            'name'               => __('Technician'),
            'forcegroupby'       => true,
            'massiveaction'      => false,
            'use_subquery'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => getTableForItemType($this->userlinkclass),
                    'joinparams'         => [
                        'jointype'           => 'child',
                        'condition'          => ['NEWTABLE.type' => CommonITILActor::ASSIGN]
                    ]
                ]
            ]
        ];

        $tab[] = [
            'id'                 => '6', // Also in Supplier_Ticket::post_addItem() and Log::getHistoryData()
            'table'              => 'glpi_suppliers',
            'field'              => 'name',
            'datatype'           => 'dropdown',
            'name'               => __('Assigned to a supplier'),
            'forcegroupby'       => true,
            'massiveaction'      => false,
            'use_subquery'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => getTableForItemType($this->supplierlinkclass),
                    'joinparams'         => [
                        'jointype'           => 'child',
                        'condition'          => ['NEWTABLE.type' => CommonITILActor::ASSIGN]
                    ]
                ]
            ]
        ];

        $tab[] = [
            'id'                 => '8', // Also in Group_Ticket::post_addItem() and Log::getHistoryData()
            'table'              => 'glpi_groups',
            'field'              => 'completename',
            'datatype'           => 'dropdown',
            'name'               => __('Technician group'),
            'forcegroupby'       => true,
            'massiveaction'      => false,
            'condition'          => ['is_assign' => 1],
            'use_subquery'       => true,
            'joinparams'         => [
                'beforejoin'         => [
                    'table'              => getTableForItemType($this->grouplinkclass),
                    'joinparams'         => [
                        'jointype'           => 'child',
                        'condition'          => ['NEWTABLE.type' => CommonITILActor::ASSIGN]
                    ]
                ]
            ]
        ];

        $tab[] = [
            'id'                 => 'notification',
            'name'               => _n('Notification', 'Notifications', Session::getPluralNumber())
        ];

        $tab[] = [
            'id'                 => '35',
            'table'              => getTableForItemType($this->userlinkclass),
            'field'              => 'use_notification',
            'name'               => __('Email followup'),
            'datatype'           => 'bool',
            'massiveaction'      => false,
            'joinparams'         => [
                'jointype'           => 'child',
                'condition'          => ['NEWTABLE.type' => CommonITILActor::REQUESTER]
            ]
        ];

        $tab[] = [
            'id'                 => '34',
            'table'              => getTableForItemType($this->userlinkclass),
            'field'              => 'alternative_email',
            'name'               => __('Email for followup'),
            'datatype'           => 'email',
            'massiveaction'      => false,
            'joinparams'         => [
                'jointype'           => 'child',
                'condition'          => ['NEWTABLE.type' => CommonITILActor::REQUESTER]
            ]
        ];

        return $tab;
    }

    public static function generateSLAOLAComputation($type, $table = "TABLE")
    {
        global $DB;

        switch ($type) {
            case 'internal_time_to_own':
            case 'time_to_own':
                return 'IF(' . $DB->quoteName($table . '.' . $type) . ' IS NOT NULL
            AND ' . $DB->quoteName($table . '.status') . ' <> ' . self::WAITING . '
            AND ((' . $DB->quoteName($table . '.takeintoaccountdate') . ' IS NOT NULL AND
                 ' . $DB->quoteName($table . '.takeintoaccountdate') . ' > ' . $DB->quoteName($table . '.' . $type) . ')
                 OR (' . $DB->quoteName($table . '.takeintoaccountdate') . ' IS NULL AND
                 ' . $DB->quoteName($table . '.takeintoaccount_delay_stat') . '
                        > TIMESTAMPDIFF(SECOND,
                                        ' . $DB->quoteName($table . '.date') . ',
                                        ' . $DB->quoteName($table . '.' . $type) . '))
                 OR (' . $DB->quoteName($table . '.takeintoaccount_delay_stat') . ' = 0
                      AND ' . $DB->quoteName($table . '.' . $type) . ' < NOW())),
            1, 0)';
            break;

            case 'internal_time_to_resolve':
            case 'time_to_resolve':
                return 'IF(' . $DB->quoteName($table . '.' . $type) . ' IS NOT NULL
            AND ' . $DB->quoteName($table . '.status') . ' <> ' . self::WAITING . '
            AND (' . $DB->quoteName($table . '.solvedate') . ' > ' . $DB->quoteName($table . '.' . $type) . '
                  OR (' . $DB->quoteName($table . '.solvedate') . ' IS NULL
                     AND ' . $DB->quoteName($table . '.' . $type) . ' < NOW())),
            1, 0)';
            break;
        }
    }

    /**
     * Get status icon
     *
     * @since 9.3
     *
     * @return string
     */
    public static function getStatusIcon($status)
    {
        $class = static::getStatusClass($status);
        $label = static::getStatus($status);
        return "<i class='$class me-1' title='$label' data-bs-toggle='tooltip'></i>";
    }

    /**
     * Get status class
     *
     * @since 9.3
     *
     * @return string
     */
    public static function getStatusClass($status)
    {
        $class = null;
        $solid = true;

        switch ($status) {
            case self::INCOMING:
                $class = 'circle';
                break;
            case self::ASSIGNED:
                $class = 'circle';
                $solid = false;
                break;
            case self::PLANNED:
                $class = 'calendar';
                $solid = false;
                break;
            case self::WAITING:
                $class = 'circle';
                break;
            case self::SOLVED:
                $class = 'circle';
                $solid = false;
                break;
            case self::CLOSED:
                $class = 'circle';
                break;
            case self::ACCEPTED:
                $class = 'check-circle';
                break;
            case self::OBSERVED:
                $class = 'eye';
                break;
            case self::EVALUATION:
                $class = 'circle';
                $solid = false;
                break;
            case self::APPROVAL:
                $class = 'question-circle';
                break;
            case self::TEST:
                $class = 'question-circle';
                break;
            case self::QUALIFICATION:
                $class = 'circle';
                $solid = false;
                break;
            case Change::REFUSED:
                $class = 'times-circle';
                $solid = false;
                break;
            case Change::CANCELED:
                $class = 'ban';
                break;
        }

        return $class == null
         ? ''
         : 'itilstatus ' . ($solid ? 'fas fa-' : 'far fa-') . $class .
         " " . static::getStatusKey($status);
    }

    /**
     * Get status key
     *
     * @since 9.3
     *
     * @return string
     */
    public static function getStatusKey($status)
    {
        $key = '';
        switch ($status) {
            case self::INCOMING:
                $key = 'new';
                break;
            case self::ASSIGNED:
                $key = 'assigned';
                break;
            case self::PLANNED:
                $key = 'planned';
                break;
            case self::WAITING:
                $key = 'waiting';
                break;
            case self::SOLVED:
                $key = 'solved';
                break;
            case self::CLOSED:
                $key = 'closed';
                break;
            case self::ACCEPTED:
                $key = 'accepted';
                break;
            case self::OBSERVED:
                $key = 'observe';
                break;
            case self::EVALUATION:
                $key = 'eval';
                break;
            case self::APPROVAL:
                $key = 'approval';
                break;
            case self::TEST:
                $key = 'test';
                break;
            case self::QUALIFICATION:
                $key = 'qualif';
                break;
        }
        return $key;
    }


    /**
     * show actor add div
     *
     * @param $type         string   actor type
     * @param $rand_type    integer  rand value of div to use
     * @param $entities_id  integer  entity ID
     * @param $is_hidden    array    of hidden fields (if empty consider as not hidden)
     * @param $withgroup    boolean  allow adding a group (true by default)
     * @param $withsupplier boolean  allow adding a supplier (only one possible in ASSIGN case)
     *                               (false by default)
     * @param $inobject     boolean  display in ITIL object ? (true by default)
     *
     * @return void|boolean Nothing if displayed, false if not applicable
     **/
    public function showActorAddForm(
        $type,
        $rand_type,
        $entities_id,
        $is_hidden = [],
        $withgroup = true,
        $withsupplier = false,
        $inobject = true
    ) {
        global $CFG_GLPI;

        $types = ['user'  => User::getTypeName(1)];

        if ($withgroup) {
            $types['group'] = Group::getTypeName(1);
        }

        if (
            $withsupplier
            && ($type == CommonITILActor::ASSIGN)
        ) {
            $types['supplier'] = Supplier::getTypeName(1);
        }

        $typename = static::getActorFieldNameType($type);
        switch ($type) {
            case CommonITILActor::REQUESTER:
                if (isset($is_hidden['_users_id_requester']) && $is_hidden['_users_id_requester']) {
                    unset($types['user']);
                }
                if (isset($is_hidden['_groups_id_requester']) && $is_hidden['_groups_id_requester']) {
                    unset($types['group']);
                }
                break;

            case CommonITILActor::OBSERVER:
                if (isset($is_hidden['_users_id_observer']) && $is_hidden['_users_id_observer']) {
                    unset($types['user']);
                }
                if (isset($is_hidden['_groups_id_observer']) && $is_hidden['_groups_id_observer']) {
                    unset($types['group']);
                }
                break;

            case CommonITILActor::ASSIGN:
                if (isset($is_hidden['_users_id_assign']) && $is_hidden['_users_id_assign']) {
                    unset($types['user']);
                }
                if (isset($is_hidden['_groups_id_assign']) && $is_hidden['_groups_id_assign']) {
                    unset($types['group']);
                }
                if (
                    isset($types['supplier'])
                    && isset($is_hidden['_suppliers_id_assign']) && $is_hidden['_suppliers_id_assign']
                ) {
                    unset($types['supplier']);
                }
                break;

            default:
                return false;
        }

        echo "<div " . ($inobject ? "style='display:none'" : '') . " id='itilactor$rand_type' class='actor-dropdown'>";
        $rand   = Dropdown::showFromArray(
            "_itil_" . $typename . "[_type]",
            $types,
            ['display_emptychoice' => true]
        );
        $params = ['type'            => '__VALUE__',
            'actortype'       => $typename,
            'itemtype'        => $this->getType(),
            'allow_email'     => (($type == CommonITILActor::OBSERVER)
                                            || $type == CommonITILActor::REQUESTER),
            'entity_restrict' => $entities_id,
            'use_notif'       => Entity::getUsedConfig('is_notif_enable_default', $entities_id, '', 1)
        ];

        Ajax::updateItemOnSelectEvent(
            "dropdown__itil_" . $typename . "[_type]$rand",
            "showitilactor" . $typename . "_$rand",
            $CFG_GLPI["root_doc"] . "/ajax/dropdownItilActors.php",
            $params
        );
        echo "<span id='showitilactor" . $typename . "_$rand' class='actor-dropdown'>&nbsp;</span>";
        if ($inobject) {
            echo "<hr>";
        }
        echo "</div>";
    }


    /**
     * show user add div on creation
     *
     * @param $type      integer  actor type
     * @param $options   array    options for default values ($options of showForm)
     *
     * @return integer Random part of inputs ids
     **/
    public function showActorAddFormOnCreate($type, array $options)
    {
        global $CFG_GLPI;

        $typename = static::getActorFieldNameType($type);

        $itemtype = $this->getType();

       // For ticket templates : mandatories
        $key = $this->getTemplateFormFieldName();
        if (isset($options[$key])) {
            $tt = $options[$key];
            if (is_numeric($options[$key])) {
                $tt_id = $options[$key];
                $tt_classname = self::getTemplateClass();
                $tt = new $tt_classname();
                $tt->getFromDB($tt_id);
            }
            echo $tt->getMandatoryMark("_users_id_" . $typename);
        }

        $right = $options["_right"] ?? $this->getDefaultActorRightSearch($type);

        if ($options["_users_id_" . $typename] == 0 && !isset($_REQUEST["_users_id_$typename"]) && !isset($this->input["_users_id_$typename"])) {
            $options["_users_id_" . $typename] = $this->getDefaultActor($type);
        }
        $rand = $options['rand'] ?? mt_rand();
        $actor_name = '_users_id_' . $typename;
        if ($type == CommonITILActor::OBSERVER) {
            $actor_name = '_users_id_' . $typename . '[]';
        }
        $params = [
            'name'   => $actor_name,
            'value'  => $options["_users_id_" . $typename],
            'right'  => $right,
            'rand'   => $rand,
            'width'  => "95%",
            'entity' => $options['entities_id'] ?? $options['entity_restrict']
        ];

       //only for active ldap and corresponding right
        $ldap_methods = getAllDataFromTable('glpi_authldaps', ['is_active' => 1]);
        if (
            count($ldap_methods)
            && Session::haveRight('user', User::IMPORTEXTAUTHUSERS)
        ) {
            $params['ldap_import'] = true;
        }

        if (
            $this->userentity_oncreate
            && ($type == CommonITILActor::REQUESTER)
        ) {
            $params['on_change'] = 'this.form.submit()';
            unset($params['entity']);
        }

        $params['_user_index'] = 0;
        if (isset($options['_user_index'])) {
            $params['_user_index'] = $options['_user_index'];
        }

        $paramscomment = [];
        if ($CFG_GLPI['notifications_mailing']) {
            $paramscomment = [
                'value'            => '__VALUE__',
                'field'            => "_users_id_" . $typename . "_notif",
                '_user_index'      => $params['_user_index'],
                'allow_email'      => $type == CommonITILActor::REQUESTER
                               || $type == CommonITILActor::OBSERVER,
                'use_notification' => $options["_users_id_" . $typename . "_notif"]['use_notification']
            ];
            if (isset($options["_users_id_" . $typename . "_notif"]['alternative_email'])) {
                $paramscomment['alternative_email']
                = $options["_users_id_" . $typename . "_notif"]['alternative_email'];
            }
            $params['toupdate'] = [
                'value_fieldname' => 'value',
                'to_update'       => "notif_" . $typename . "_$rand",
                'url'             => $CFG_GLPI["root_doc"] . "/ajax/uemailUpdate.php",
                'moreparams'      => $paramscomment
            ];
        }

        if (
            ($itemtype == 'Ticket')
            && ($type == CommonITILActor::ASSIGN)
        ) {
            $toupdate = [];
            if (isset($params['toupdate']) && is_array($params['toupdate'])) {
                $toupdate[] = $params['toupdate'];
            }
            $toupdate[] = [
                'value_fieldname' => 'value',
                'to_update'       => "countassign_$rand",
                'url'             => $CFG_GLPI["root_doc"] . "/ajax/actorinformation.php",
                'moreparams'      => ['users_id_assign' => '__VALUE__']
            ];
            $params['toupdate'] = $toupdate;
        }

       // List all users in the active entities
        echo "<div class='text-nowrap'>";
        User::dropdown($params);

        if ($itemtype == 'Ticket') {
           // display opened tickets for user
            if (
                ($type == CommonITILActor::REQUESTER)
                && ($options["_users_id_" . $typename] > 0)
                && (Session::getCurrentInterface() != "helpdesk")
            ) {
                $options2 = [
                    'criteria' => [
                        [
                            'field'      => 4, // users_id
                            'searchtype' => 'equals',
                            'value'      => $options["_users_id_" . $typename],
                            'link'       => 'AND',
                        ],
                        [
                            'field'      => 12, // status
                            'searchtype' => 'equals',
                            'value'      => 'notold',
                            'link'       => 'AND',
                        ],
                    ],
                    'reset'    => 'reset',
                ];

                $url = $this->getSearchURL() . "?" . Toolbox::append_params($options2, '&amp;');

                echo "<a href='$url' title=\"" . __s('Processing') . "\" class='badge bg-secondary ms-1'>";
                echo $this->countActiveObjectsForUser($options["_users_id_" . $typename]);
                echo "</a>";
            }
        }
        echo "</div>";

        if ($itemtype == 'Ticket') {
           // Display active tickets for a tech
           // Need to update information on dropdown changes
            if ($type == CommonITILActor::ASSIGN) {
                echo "<span id='countassign_$rand'>";
                echo "</span>";

                echo "<script type='text/javascript'>";
                echo "$(function() {";
                Ajax::updateItemJsCode(
                    "countassign_$rand",
                    $CFG_GLPI["root_doc"] . "/ajax/actorinformation.php",
                    ['users_id_assign' => '__VALUE__'],
                    "dropdown__users_id_" . $typename . $rand
                );
                echo "});</script>";
            }
        }

        if ($CFG_GLPI['notifications_mailing']) {
            echo "<div id='notif_" . $typename . "_$rand' class='mt-2'>";
            echo "</div>";

            echo "<script type='text/javascript'>";
            echo "$(function() {";
            Ajax::updateItemJsCode(
                "notif_" . $typename . "_$rand",
                $CFG_GLPI["root_doc"] . "/ajax/uemailUpdate.php",
                $paramscomment,
                "dropdown_" . $actor_name . $rand
            );
            echo "});</script>";
        }

        return $rand;
    }


    /**
     * @param $actiontime
     **/
    public static function getActionTime($actiontime)
    {
        return Html::timestampToString($actiontime, false);
    }


    /**
     * @param $ID
     * @param $itemtype
     * @param $link      (default 0)
     **/
    public static function getAssignName($ID, $itemtype, $link = 0)
    {

        switch ($itemtype) {
            case 'User':
                if ($ID == 0) {
                    return "";
                }
                return getUserName($ID, $link);

            case 'Supplier':
            case 'Group':
                $item = new $itemtype();
                if ($item->getFromDB($ID)) {
                    if ($link) {
                        return $item->getLink(['comments' => true]);
                    }
                    return $item->getNameID();
                }
                return "";
        }
    }

    /**
     * Form to add a solution to an ITIL object
     *
     * @since 0.84
     * @since 9.2 Signature has changed
     *
     * @param CommonITILObject $item item instance
     *
     * @param $entities_id
     **/
    public static function showMassiveSolutionForm(CommonITILObject $item)
    {
        $solution = new ITILSolution();
        $solution->showForm(
            null,
            [
                'parent' => $item,
                'entity' => $item->getEntityID(),
                'noform' => true,
                'nokb'   => true
            ]
        );
    }


    /**
     * Update date mod of the ITIL object
     *
     * @param $ID                    integer  ID of the ITIL object
     * @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)
     **/
    public function updateDateMod($ID, $no_stat_computation = false, $users_id_lastupdater = 0)
    {
        global $DB;

        if ($this->getFromDB($ID)) {
           // Force date mod and lastupdater
            $update = ['date_mod' => $_SESSION['glpi_currenttime']];

           // set last updater if interactive user
            if (!Session::isCron()) {
                $update['users_id_lastupdater'] = Session::getLoginUserID();
            } else if ($users_id_lastupdater > 0) {
                $update['users_id_lastupdater'] = $users_id_lastupdater;
            }

            $DB->update(
                $this->getTable(),
                $update,
                ['id' => $ID]
            );
        }
    }


    /**
     * Update actiontime of the object based on actiontime of the tasks
     *
     * @param integer $ID ID of the object
     *
     * @return boolean : success
     **/
    public function updateActionTime($ID)
    {
        global $DB;

        $tot       = 0;
        $tasktable = getTableForItemType($this->getType() . 'Task');

        $result = $DB->request([
            'SELECT' => ['SUM' => 'actiontime as sumtime'],
            'FROM'   => $tasktable,
            'WHERE'  => [$this->getForeignKeyField() => $ID]
        ])->current();
        $sum = $result['sumtime'];
        if (!is_null($sum)) {
            $tot += $sum;
        }

        $result = $DB->update(
            $this->getTable(),
            [
                'actiontime' => $tot
            ],
            [
                'id' => $ID
            ]
        );
        return $result;
    }


    /**
     * Get all available types to which an ITIL object can be assigned
     **/
    public static function getAllTypesForHelpdesk()
    {
        global $PLUGIN_HOOKS, $CFG_GLPI;

       /// TODO ticket_types -> itil_types

        $types = [];
        $ptypes = [];
       //Types of the plugins (keep the plugin hook for right check)
        if (isset($PLUGIN_HOOKS['assign_to_ticket'])) {
            foreach (array_keys($PLUGIN_HOOKS['assign_to_ticket']) as $plugin) {
                if (!Plugin::isPluginActive($plugin)) {
                    continue;
                }
                $ptypes = Plugin::doOneHook($plugin, 'AssignToTicket', $ptypes);
            }
        }
        asort($ptypes);
       //Types of the core (after the plugin for robustness)
        foreach ($CFG_GLPI["ticket_types"] as $itemtype) {
            if ($item = getItemForItemtype($itemtype)) {
                if (
                    !isPluginItemType($itemtype) // No plugin here
                    && isset($_SESSION["glpiactiveprofile"]["helpdesk_item_type"])
                    && in_array($itemtype, $_SESSION["glpiactiveprofile"]["helpdesk_item_type"])
                ) {
                    $types[$itemtype] = $item->getTypeName(1);
                }
            }
        }
        asort($types); // core type first... asort could be better ?

       // Drop not available plugins
        foreach (array_keys($ptypes) as $itemtype) {
            if (
                !isset($_SESSION["glpiactiveprofile"]["helpdesk_item_type"])
                || !in_array($itemtype, $_SESSION["glpiactiveprofile"]["helpdesk_item_type"])
            ) {
                unset($ptypes[$itemtype]);
            }
        }

        $types = array_merge($types, $ptypes);
        return $types;
    }


    /**
     * Check if it's possible to assign ITIL object to a type (core or plugin)
     *
     * @param string $itemtype the object's type
     *
     * @return true if ticket can be assign to this type, false if not
     **/
    public static function isPossibleToAssignType($itemtype)
    {

        if (in_array($itemtype, $_SESSION["glpiactiveprofile"]["helpdesk_item_type"])) {
            return true;
        }
        return false;
    }


    /**
     * Compute solve delay stat of the current ticket
     **/
    public function computeSolveDelayStat()
    {

        if (
            isset($this->fields['id'])
            && !empty($this->fields['date'])
            && !empty($this->fields['solvedate'])
        ) {
            $calendars_id = $this->getCalendar();
            $calendar     = new Calendar();

           // Using calendar
            if (
                ($calendars_id > 0)
                && $calendar->getFromDB($calendars_id)
            ) {
                return max(0, $calendar->getActiveTimeBetween(
                    $this->fields['date'],
                    $this->fields['solvedate']
                )
                                                            - $this->fields["waiting_duration"]);
            }
           // Not calendar defined
            return max(0, strtotime($this->fields['solvedate']) - strtotime($this->fields['date'])
                       - $this->fields["waiting_duration"]);
        }
        return 0;
    }


    /**
     * Compute close delay stat of the current ticket
     **/
    public function computeCloseDelayStat()
    {

        if (
            isset($this->fields['id'])
            && !empty($this->fields['date'])
            && !empty($this->fields['closedate'])
        ) {
            $calendars_id = $this->getCalendar();
            $calendar     = new Calendar();

           // Using calendar
            if (
                ($calendars_id > 0)
                && $calendar->getFromDB($calendars_id)
            ) {
                return max(0, $calendar->getActiveTimeBetween(
                    $this->fields['date'],
                    $this->fields['closedate']
                )
                                                             - $this->fields["waiting_duration"]);
            }
           // Not calendar defined
            return max(0, strtotime($this->fields['closedate']) - strtotime($this->fields['date'])
                       - $this->fields["waiting_duration"]);
        }
        return 0;
    }


    public function showStats()
    {

        if (
            !$this->canView()
            || !isset($this->fields['id'])
        ) {
            return false;
        }

        $this->showStatsDates();
        Plugin::doHook(Hooks::SHOW_ITEM_STATS, $this);
        $this->showStatsTimes();
    }

    public function showStatsDates()
    {
        echo "<table class='tab_cadre_fixe'>";
        echo "<tr><th colspan='2'>" . _n('Date', 'Dates', Session::getPluralNumber()) . "</th></tr>";

        echo "<tr class='tab_bg_2'><td>" . __('Opening date') . "</td>";
        echo "<td>" . Html::convDateTime($this->fields['date']) . "</td></tr>";

        echo "<tr class='tab_bg_2'><td>" . __('Time to resolve') . "</td>";
        echo "<td>" . Html::convDateTime($this->fields['time_to_resolve']) . "</td></tr>";

        if (!$this->isNotSolved()) {
            echo "<tr class='tab_bg_2'><td>" . __('Resolution date') . "</td>";
            echo "<td>" . Html::convDateTime($this->fields['solvedate']) . "</td></tr>";
        }

        if (in_array($this->fields['status'], $this->getClosedStatusArray())) {
            echo "<tr class='tab_bg_2'><td>" . __('Closing date') . "</td>";
            echo "<td>" . Html::convDateTime($this->fields['closedate']) . "</td></tr>";
        }
        echo "</table>";
    }

    public function showStatsTimes()
    {
        echo "<div class='dates_timelines'>";
        echo "<table class='tab_cadre_fixe'>";
        echo "<tr><th colspan='2'>" . _n('Time', 'Times', Session::getPluralNumber()) . "</th></tr>";

        if (isset($this->fields['takeintoaccount_delay_stat'])) {
            echo "<tr class='tab_bg_2'><td>" . __('Take into account') . "</td><td>";
            if ($this->fields['takeintoaccount_delay_stat'] > 0) {
                echo Html::timestampToString($this->fields['takeintoaccount_delay_stat'], 0, false);
            } else {
                echo '&nbsp;';
            }
            echo "</td></tr>";
        }

        if (!$this->isNotSolved()) {
            echo "<tr class='tab_bg_2'><td>" . __('Resolution') . "</td><td>";

            if ($this->fields['solve_delay_stat'] > 0) {
                echo Html::timestampToString($this->fields['solve_delay_stat'], 0, false);
            } else {
                echo '&nbsp;';
            }
            echo "</td></tr>";
        }

        if (in_array($this->fields['status'], $this->getClosedStatusArray())) {
            echo "<tr class='tab_bg_2'><td>" . __('Closure') . "</td><td>";
            if ($this->fields['close_delay_stat'] > 0) {
                echo Html::timestampToString($this->fields['close_delay_stat'], true, false);
            } else {
                echo '&nbsp;';
            }
            echo "</td></tr>";
        }

        echo "<tr class='tab_bg_2'><td>" . __('Pending') . "</td><td>";
        if ($this->fields['waiting_duration'] > 0) {
            echo Html::timestampToString($this->fields['waiting_duration'], 0, false);
        } else {
            echo '&nbsp;';
        }
        echo "</td></tr>";

        echo "</table>";
        echo "</div>";
    }


    /** Get users_ids of itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct users_ids which have itil object
     **/
    public function getUsedAuthorBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $linkclass = new $this->userlinkclass();
        $linktable = $linkclass->getTable();

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => [
                'glpi_users.id AS users_id',
                'glpi_users.name AS name',
                'glpi_users.realname AS realname',
                'glpi_users.firstname AS firstname'
            ],
            'DISTINCT' => true,
            'FROM'            => $ctable,
            'LEFT JOIN'       => [
                $linktable  => [
                    'ON' => [
                        $linktable  => $this->getForeignKeyField(),
                        $ctable     => 'id', [
                            'AND' => [
                                "$linktable.type"    => CommonITILActor::REQUESTER
                            ]
                        ]
                    ]
                ]
            ],
            'INNER JOIN'      => [
                'glpi_users'   => [
                    'ON' => [
                        $linktable     => 'users_id',
                        'glpi_users'   => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                'realname',
                'firstname',
                'name'
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];
        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['users_id'],
                'link' => formatUserName(
                    $line['users_id'],
                    $line['name'],
                    $line['realname'],
                    $line['firstname'],
                    1
                )
            ];
        }
        return $tab;
    }


    /** Get recipient of itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct recipents which have itil object
     **/
    public function getUsedRecipientBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => [
                'glpi_users.id AS user_id',
                'glpi_users.name AS name',
                'glpi_users.realname AS realname',
                'glpi_users.firstname AS firstname'
            ],
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'LEFT JOIN'       => [
                'glpi_users'   => [
                    'ON' => [
                        $ctable        => 'users_id_recipient',
                        'glpi_users'   => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                'realname',
                'firstname',
                'name'
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];

        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['user_id'],
                'link' => formatUserName(
                    $line['user_id'],
                    $line['name'],
                    $line['realname'],
                    $line['firstname'],
                    1
                )
            ];
        }
        return $tab;
    }


    /** Get groups which have itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct groups of tickets
     **/
    public function getUsedGroupBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $linkclass = new $this->grouplinkclass();
        $linktable = $linkclass->getTable();

        $ctable = $this->getTable();
        $criteria = [
            'SELECT' => [
                'glpi_groups.id',
                'glpi_groups.completename'
            ],
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'LEFT JOIN'       => [
                $linktable  => [
                    'ON' => [
                        $linktable  => $this->getForeignKeyField(),
                        $ctable     => 'id', [
                            'AND' => [
                                "$linktable.type"    => CommonITILActor::REQUESTER
                            ]
                        ]
                    ]
                ]
            ],
            'INNER JOIN'      => [
                'glpi_groups'   => [
                    'ON' => [
                        $linktable     => 'groups_id',
                        'glpi_groups'   => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                'glpi_groups.completename'
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];

        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['id'],
                'link' => $line['completename'],
            ];
        }
        return $tab;
    }


    /** Get recipient of itil object between 2 dates
     *
     * @param string  $date1 begin date
     * @param string  $date2 end date
     * @param boolean $title indicates if stat if by title (true) or type (false)
     *
     * @return array contains the distinct recipents which have tickets
     **/
    public function getUsedUserTitleOrTypeBetween($date1 = '', $date2 = '', $title = true)
    {
        global $DB;

        $linkclass = new $this->userlinkclass();
        $linktable = $linkclass->getTable();

        if ($title) {
            $table = "glpi_usertitles";
            $field = "usertitles_id";
        } else {
            $table = "glpi_usercategories";
            $field = "usercategories_id";
        }

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => "glpi_users.$field",
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'INNER JOIN'      => [
                $linktable  => [
                    'ON' => [
                        $linktable  => $this->getForeignKeyField(),
                        $ctable     => 'id'
                    ]
                ],
                'glpi_users'   => [
                    'ON' => [
                        $linktable     => 'users_id',
                        'glpi_users'   => 'id'
                    ]
                ]
            ],
            'LEFT JOIN'       => [
                $table         => [
                    'ON' => [
                        'glpi_users'   => $field,
                        $table         => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                "glpi_users.$field"
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];
        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line[$field],
                'link' => Dropdown::getDropdownName($table, $line[$field]),
            ];
        }
        return $tab;
    }


    /**
     * Get priorities of itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct priorities of tickets
     **/
    public function getUsedPriorityBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => 'priority',
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => 'priority'
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];
        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['priority'],
                'link' => static::getPriorityName($line['priority']),
            ];
        }
        return $tab;
    }


    /**
     * Get urgencies of itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct priorities of tickets
     **/
    public function getUsedUrgencyBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => 'urgency',
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => 'urgency'
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];

        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['urgency'],
                'link' => static::getUrgencyName($line['urgency']),
            ];
        }
        return $tab;
    }


    /**
     * Get impacts of itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct priorities of tickets
     **/
    public function getUsedImpactBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => 'impact',
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => 'impact'
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];

        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['impact'],
                'link' => static::getImpactName($line['impact']),
            ];
        }
        return $tab;
    }


    /**
     * Get request types of itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct request types of tickets
     **/
    public function getUsedRequestTypeBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => 'requesttypes_id',
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => 'requesttypes_id'
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];
        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['requesttypes_id'],
                'link' => Dropdown::getDropdownName('glpi_requesttypes', $line['requesttypes_id']),
            ];
        }
        return $tab;
    }


    /**
     * Get solution types of itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct request types of tickets
     **/
    public function getUsedSolutionTypeBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => 'solutiontypes_id',
            'DISTINCT'        => true,
            'FROM'            => ITILSolution::getTable(),
            'INNER JOIN'      => [
                $ctable   => [
                    'ON' => [
                        ITILSolution::getTable()   => 'items_id',
                        $ctable                    => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                ITILSolution::getTable() . ".itemtype" => $this->getType(),
                "$ctable.is_deleted"                   => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => 'solutiontypes_id'
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];
        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['solutiontypes_id'],
                'link' => Dropdown::getDropdownName('glpi_solutiontypes', $line['solutiontypes_id']),
            ];
        }
        return $tab;
    }


    /** Get users which have intervention assigned to  between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct users which have any intervention assigned to.
     **/
    public function getUsedTechBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $linkclass = new $this->userlinkclass();
        $linktable = $linkclass->getTable();
        $showlink = User::canView();

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => [
                'glpi_users.id AS users_id',
                'glpi_users.name AS name',
                'glpi_users.realname AS realname',
                'glpi_users.firstname AS firstname'
            ],
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'LEFT JOIN'       => [
                $linktable  => [
                    'ON' => [
                        $linktable  => $this->getForeignKeyField(),
                        $ctable     => 'id', [
                            'AND' => [
                                "$linktable.type"    => CommonITILActor::ASSIGN
                            ]
                        ]
                    ]
                ],
                'glpi_users'   => [
                    'ON' => [
                        $linktable     => 'users_id',
                        'glpi_users'   => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                'realname',
                'firstname',
                'name'
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];

        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['users_id'],
                'link' => formatUserName($line['users_id'], $line['name'], $line['realname'], $line['firstname'], $showlink),
            ];
        }
        return $tab;
    }


    /** Get users which have followup assigned to  between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct users which have any followup assigned to.
     **/
    public function getUsedTechTaskBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $linktable = getTableForItemType($this->getType() . 'Task');
        $showlink = User::canView();

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => [
                'glpi_users.id AS users_id',
                'glpi_users.name AS name',
                'glpi_users.realname AS realname',
                'glpi_users.firstname AS firstname'
            ],
            'DISTINCT' => true,
            'FROM'            => $ctable,
            'LEFT JOIN'       => [
                $linktable  => [
                    'ON' => [
                        $linktable  => $this->getForeignKeyField(),
                        $ctable     => 'id'
                    ]
                ],
                'glpi_users'   => [
                    'ON' => [
                        $linktable     => 'users_id',
                        'glpi_users'   => 'id'
                    ]
                ],
                'glpi_profiles_users'   => [
                    'ON' => [
                        'glpi_users'            => 'id',
                        'glpi_profiles_users'   => 'users_id'
                    ]
                ],
                'glpi_profiles'         => [
                    'ON' => [
                        'glpi_profiles'         => 'id',
                        'glpi_profiles_users'   => 'profiles_id'
                    ]
                ],
                'glpi_profilerights'    => [
                    'ON' => [
                        'glpi_profiles'      => 'id',
                        'glpi_profilerights' => 'profiles_id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted"          => 0,
                'glpi_profilerights.name'     => 'ticket',
                'glpi_profilerights.rights'   => ['&', Ticket::OWN],
                "$linktable.users_id"         => ['<>', 0],
                ['NOT'                        => ["$linktable.users_id" => null]]
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                'realname',
                'firstname',
                'name'
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];

        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['users_id'],
                'link' => formatUserName($line['users_id'], $line['name'], $line['realname'], $line['firstname'], $showlink),
            ];
        }
        return $tab;
    }


    /** Get enterprises which have itil object assigned to between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct enterprises which have any tickets assigned to.
     **/
    public function getUsedSupplierBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $linkclass = new $this->supplierlinkclass();
        $linktable = $linkclass->getTable();

        $ctable = $this->getTable();
        $criteria = [
            'SELECT'          => [
                'glpi_suppliers.id AS suppliers_id_assign',
                'glpi_suppliers.name AS name'
            ],
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'LEFT JOIN'       => [
                $linktable        => [
                    'ON' => [
                        $linktable  => $this->getForeignKeyField(),
                        $ctable     => 'id', [
                            'AND' => [
                                "$linktable.type"    => CommonITILActor::ASSIGN
                            ]
                        ]
                    ]
                ],
                'glpi_suppliers'  => [
                    'ON' => [
                        $linktable        => 'suppliers_id',
                        'glpi_suppliers'  => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                'name'
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];
        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['suppliers_id_assign'],
                'link' => '<a href="' . Supplier::getFormURLWithID($line['suppliers_id_assign']) . '">' . $line['name'] . '</a>',
            ];
        }
        return $tab;
    }


    /** Get groups assigned to itil object between 2 dates
     *
     * @param string $date1 begin date
     * @param string $date2 end date
     *
     * @return array contains the distinct groups assigned to a tickets
     **/
    public function getUsedAssignGroupBetween($date1 = '', $date2 = '')
    {
        global $DB;

        $linkclass = new $this->grouplinkclass();
        $linktable = $linkclass->getTable();

        $ctable = $this->getTable();
        $criteria = [
            'SELECT' => [
                'glpi_groups.id',
                'glpi_groups.completename'
            ],
            'DISTINCT'        => true,
            'FROM'            => $ctable,
            'LEFT JOIN'       => [
                $linktable  => [
                    'ON' => [
                        $linktable  => $this->getForeignKeyField(),
                        $ctable     => 'id', [
                            'AND' => [
                                "$linktable.type"    => CommonITILActor::ASSIGN
                            ]
                        ]
                    ]
                ],
                'glpi_groups'   => [
                    'ON' => [
                        $linktable     => 'groups_id',
                        'glpi_groups'   => 'id'
                    ]
                ]
            ],
            'WHERE'           => [
                "$ctable.is_deleted" => 0
            ] + getEntitiesRestrictCriteria($ctable),
            'ORDERBY'         => [
                'glpi_groups.completename'
            ]
        ];

        if (!empty($date1) || !empty($date2)) {
            $criteria['WHERE'][] = [
                'OR' => [
                    getDateCriteria("$ctable.date", $date1, $date2),
                    getDateCriteria("$ctable.closedate", $date1, $date2),
                ]
            ];
        }

        $iterator = $DB->request($criteria);
        $tab    = [];
        foreach ($iterator as $line) {
            $tab[] = [
                'id'   => $line['id'],
                'link' => $line['completename'],
            ];
        }
        return $tab;
    }


    /**
     * Display a line for an object
     *
     * @since 0.85 (befor in each object with differents parameters)
     *
     * @param $id                 Integer  ID of the object
     * @param $options            array of options
     *      output_type            : Default output type (see Search class / default Search::HTML_OUTPUT)
     *      row_num                : row num used for display
     *      type_for_massiveaction : itemtype for massive action
     *      id_for_massaction      : default 0 means no massive action
     *
     * @since 10.0.0 "followups" option has been dropped
     */
    public static function showShort($id, $options = [])
    {
        global $DB;

        $p = [
            'output_type'            => Search::HTML_OUTPUT,
            'row_num'                => 0,
            'type_for_massiveaction' => 0,
            'id_for_massiveaction'   => 0,
            'followups'              => false,
            'ticket_stats'           => false,
        ];

        if (count($options)) {
            foreach ($options as $key => $val) {
                $p[$key] = $val;
            }
        }

        $rand = mt_rand();

       /// TODO to be cleaned. Get datas and clean display links

       // Prints a job in short form
       // Should be called in a <table>-segment
       // Print links or not in case of user view
       // Make new job object and fill it from database, if success, print it
        $item         = new static();

        $candelete   = static::canDelete();
        $canupdate   = Session::haveRight(static::$rightname, UPDATE);
        $showprivate = Session::haveRight('followup', ITILFollowup::SEEPRIVATE);
        $align       = "class='left'";
        $align_desc  = "class='left'";

        if ($item->getFromDB($id)) {
            $item_num = 1;
            $bgcolor  = $_SESSION["glpipriority_" . $item->fields["priority"]];
            echo Search::showNewLine($p['output_type'], $p['row_num'] % 2, $item->isDeleted());

            $check_col = '';
            if (
                ($candelete || $canupdate)
                && ($p['output_type'] == Search::HTML_OUTPUT)
                && $p['id_for_massiveaction']
            ) {
                $check_col = Html::getMassiveActionCheckBox($p['type_for_massiveaction'], $p['id_for_massiveaction']);
            }
            echo Search::showItem($p['output_type'], $check_col, $item_num, $p['row_num'], $align);

            // First column ID
            echo Search::showItem($p['output_type'], $item->fields["id"], $item_num, $p['row_num'], $align);

            // Second column TITLE
            $second_column = "<span class='b'>" . $item->getName() . "</span>&nbsp;";
            if ($p['output_type'] == Search::HTML_OUTPUT && $item->canViewItem()) {
                $second_column  = sprintf(
                    __('%1$s (%2$s)'),
                    "<a id='" . $item->getType() . $item->fields["id"] . "$rand' href=\"" . $item->getLinkURL() . "\">$second_column</a>",
                    sprintf(
                        __('%1$s - %2$s'),
                        $item->numberOfFollowups($showprivate),
                        $item->numberOfTasks($showprivate)
                    )
                );
                $second_column = sprintf(
                    __('%1$s %2$s'),
                    $second_column,
                    Html::showToolTip(
                        RichText::getEnhancedHtml($item->fields['content']),
                        ['display' => false,
                            'applyto' => $item->getType() . $item->fields["id"] .
                        $rand
                        ]
                    )
                );
            }
            echo Search::showItem($p['output_type'], $second_column, $item_num, $p['row_num'], $align);

            // third column
            if ($p['output_type'] == Search::HTML_OUTPUT) {
                $third_col = static::getStatusIcon($item->fields["status"]);
            } else {
                $third_col = static::getStatus($item->fields["status"]);
            }
            echo Search::showItem($p['output_type'], $third_col, $item_num, $p['row_num'], $align);


            // fourth column
            if ($item->fields['status'] == static::CLOSED) {
                $fourth_col = sprintf(
                    __('Closed on %s'),
                    ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : '') .
                    Html::convDateTime($item->fields['closedate'])
                );
            } else if ($item->fields['status'] == static::SOLVED) {
                $fourth_col = sprintf(
                    __('Solved on %s'),
                    ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : '') .
                    Html::convDateTime($item->fields['solvedate'])
                );
            } else if ($item->fields['begin_waiting_date']) {
                $fourth_col = sprintf(
                    __('Put on hold on %s'),
                    ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : '') .
                    Html::convDateTime($item->fields['begin_waiting_date'])
                );
            } else if ($item->fields['time_to_resolve']) {
                $fourth_col = sprintf(
                    __('%1$s: %2$s'),
                    __('Time to resolve'),
                    ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : '') .
                    Html::convDateTime($item->fields['time_to_resolve'])
                );
            } else {
                $fourth_col = sprintf(
                    __('Opened on %s'),
                    ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : '') .
                    Html::convDateTime($item->fields['date'])
                );
            }
            echo Search::showItem($p['output_type'], $fourth_col, $item_num, $p['row_num'], $align . " width=130");

           // fifth column
            $fifth_col = Html::convDateTime($item->fields["date_mod"]);
            echo Search::showItem($p['output_type'], $fifth_col, $item_num, $p['row_num'], $align . " width=90");

           // sixth column
            if (count($_SESSION["glpiactiveentities"]) > 1) {
                $sixth_col = Dropdown::getDropdownName('glpi_entities', $item->fields['entities_id']);
                echo Search::showItem(
                    $p['output_type'],
                    $sixth_col,
                    $item_num,
                    $p['row_num'],
                    $align . " width=100"
                );
            }

           // seventh Column
            echo Search::showItem(
                $p['output_type'],
                "<span class='b'>" . static::getPriorityName($item->fields["priority"]) . "</span>",
                $item_num,
                $p['row_num'],
                "$align bgcolor='$bgcolor'"
            );

           // eighth Column
            $eighth_col = "";
            foreach ($item->getUsers(CommonITILActor::REQUESTER) as $d) {
                $userdata    = getUserName($d["users_id"], 2);
                $eighth_col .= sprintf(
                    __('%1$s %2$s'),
                    "<span class='b'>" . $userdata['name'] . "</span>",
                    Html::showToolTip(
                        $userdata["comment"],
                        ['link'    => $userdata["link"],
                            'display' => false
                        ]
                    )
                );
                 $eighth_col .= "<br>";
            }

            foreach ($item->getGroups(CommonITILActor::REQUESTER) as $d) {
                $eighth_col .= Dropdown::getDropdownName("glpi_groups", $d["groups_id"]);
                $eighth_col .= "<br>";
            }

            echo Search::showItem($p['output_type'], $eighth_col, $item_num, $p['row_num'], $align);

            // ninth column
            $ninth_col = "";
            foreach ($item->getUsers(CommonITILActor::ASSIGN) as $d) {
                if (
                    Session::getCurrentInterface() == 'helpdesk'
                    && !empty($anon_name = User::getAnonymizedNameForUser(
                        $d['users_id'],
                        $item->getEntityID()
                    ))
                ) {
                    $ninth_col .= $anon_name;
                } else {
                    $userdata   = getUserName($d["users_id"], 2);
                    $ninth_col .= sprintf(
                        __('%1$s %2$s'),
                        "<span class='b'>" . $userdata['name'] . "</span>",
                        Html::showToolTip(
                            $userdata["comment"],
                            ['link'    => $userdata["link"],
                                'display' => false
                            ]
                        )
                    );
                }
                $ninth_col .= "<br>";
            }

            foreach ($item->getGroups(CommonITILActor::ASSIGN) as $d) {
                if (
                    Session::getCurrentInterface() == 'helpdesk'
                    && !empty($anon_name = Group::getAnonymizedName($item->getEntityID()))
                ) {
                    $ninth_col .= $anon_name;
                } else {
                    $ninth_col .= Dropdown::getDropdownName("glpi_groups", $d["groups_id"]);
                }
                $ninth_col .= "<br>";
            }

            foreach ($item->getSuppliers(CommonITILActor::ASSIGN) as $d) {
                $ninth_col .= Dropdown::getDropdownName("glpi_suppliers", $d["suppliers_id"]);
                $ninth_col .= "<br>";
            }
            echo Search::showItem($p['output_type'], $ninth_col, $item_num, $p['row_num'], $align);


            if (!$p['ticket_stats']) {
               // tenth Colum
               // Ticket : simple link to item
                $tenth_col  = "";
                $is_deleted = false;
                $item_ticket = new Item_Ticket();
                $data = $item_ticket->find(['tickets_id' => $item->fields['id']]);

                if ($item->getType() == 'Ticket') {
                    if (!empty($data)) {
                        foreach ($data as $val) {
                            if (!empty($val["itemtype"]) && ($val["items_id"] > 0)) {
                                if ($object = getItemForItemtype($val["itemtype"])) {
                                    if ($object->getFromDB($val["items_id"])) {
                                         $is_deleted = $object->isDeleted();

                                         $tenth_col .= $object->getTypeName();
                                         $tenth_col .= " - <span class='b'>";
                                        if ($item->canView()) {
                                            $tenth_col .= $object->getLink();
                                        } else {
                                            $tenth_col .= $object->getNameID();
                                        }
                                        $tenth_col .= "</span><br>";
                                    }
                                }
                            }
                        }
                    } else {
                        $tenth_col = __('General');
                    }

                    echo Search::showItem($p['output_type'], $tenth_col, $item_num, $p['row_num'], ($is_deleted ? " class='center deleted' " : $align));
                }

               // Seventh column
                echo Search::showItem(
                    $p['output_type'],
                    "<span class='b'>" .
                                    Dropdown::getDropdownName(
                                        'glpi_itilcategories',
                                        $item->fields["itilcategories_id"]
                                    ) .
                                 "</span>",
                    $item_num,
                    $p['row_num'],
                    $align
                );

               //eleventh column
                $eleventh_column  = '';
                $planned_infos = '';

                $tasktype      = $item->getType() . "Task";
                $plan          = new $tasktype();
                $items         = [];

                $result = $DB->request(
                    [
                        'FROM'  => $plan->getTable(),
                        'WHERE' => [
                            $item->getForeignKeyField() => $item->fields['id'],
                        ],
                    ]
                );
                foreach ($result as $plan) {
                    if (isset($plan['begin']) && $plan['begin']) {
                        $items[$plan['id']] = $plan['id'];
                        $planned_infos .= sprintf(
                            __('From %s') .
                                            ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : ''),
                            Html::convDateTime($plan['begin'])
                        );
                        $planned_infos .= sprintf(
                            __('To %s') .
                                            ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : ''),
                            Html::convDateTime($plan['end'])
                        );
                        if ($plan['users_id_tech']) {
                                  $planned_infos .= sprintf(
                                      __('By %s') .
                                                ($p['output_type'] == Search::HTML_OUTPUT ? '<br>' : ''),
                                      getUserName($plan['users_id_tech'])
                                  );
                        }
                        $planned_infos .= "<br>";
                    }
                }

                $eleventh_column = count($items);
                if ($eleventh_column) {
                    $eleventh_column = "<span class='pointer'
                                 id='" . $item->getType() . $item->fields["id"] . "planning$rand'>" .
                                 $eleventh_column . '</span>';
                    $eleventh_column = sprintf(
                        __('%1$s %2$s'),
                        $eleventh_column,
                        Html::showToolTip(
                            $planned_infos,
                            ['display' => false,
                                'applyto' => $item->getType() .
                                                                              $item->fields["id"] .
                            "planning" . $rand
                            ]
                        )
                    );
                }

                echo Search::showItem(
                    $p['output_type'],
                    $eleventh_column,
                    $item_num,
                    $p['row_num'],
                    $align_desc . " width='150'"
                );
            } else {
                echo Search::showItem($p['output_type'], $second_column, $item_num, $p['row_num'], $align_desc . " width='200'");

                $takeintoaccountdelay_column = "";
               // Show only for tickets taken into account
                if ($item->fields['takeintoaccount_delay_stat'] > 0) {
                    $takeintoaccountdelay_column = Html::timestampToString($item->fields['takeintoaccount_delay_stat']);
                }
                echo Search::showItem($p['output_type'], $takeintoaccountdelay_column, $item_num, $p['row_num'], $align_desc . " width='150'");

                $solvedelay_column = "";
               // Show only for solved tickets
                if ($item->fields['solve_delay_stat'] > 0) {
                    $solvedelay_column = Html::timestampToString($item->fields['solve_delay_stat']);
                }
                echo Search::showItem($p['output_type'], $solvedelay_column, $item_num, $p['row_num'], $align_desc . " width='150'");

                $waiting_duration_column = Html::timestampToString($item->fields['waiting_duration']);
                echo Search::showItem($p['output_type'], $waiting_duration_column, $item_num, $p['row_num'], $align_desc . " width='150'");
            }

           // Finish Line
            echo Search::showEndLine($p['output_type']);
        } else {
            echo "<tr class='tab_bg_2'>";
            echo "<td colspan='6' ><i>" . __('No item in progress.') . "</i></td></tr>";
        }
    }

    /**
     * @param integer $output_type Output type
     * @param string  $mass_id     id of the form to check all
     */
    public static function commonListHeader(
        $output_type = Search::HTML_OUTPUT,
        $mass_id = '',
        array $params = []
    ) {
        $ticket_stats = $params['ticket_stats'] ?? false;

       // New Line for Header Items Line
        echo Search::showNewLine($output_type);
       // $show_sort if
        $header_num                      = 1;

        $items                           = [];
        $items[(empty($mass_id) ? '&nbsp' : Html::getCheckAllAsCheckbox($mass_id))] = '';

        $items[__('ID')]           = "id";
        $items[__('Title')]        = "name";
        $items[__('Status')]             = "status";
        $items[_n('Date', 'Dates', 1)]               = "date";
        $items[__('Last update')]        = "date_mod";

        if (count($_SESSION["glpiactiveentities"]) > 1) {
            $items[Entity::getTypeName(Session::getPluralNumber())] = "glpi_entities.completename";
        }

        $items[__('Priority')]           = "priority";
        $items[_n('Requester', 'Requesters', 1)]          = "users_id";
        $items[__('Assigned')]           = "users_id_assign";

        if (!$ticket_stats) {
            if (static::getType() == 'Ticket') {
                $items[_n('Associated element', 'Associated elements', Session::getPluralNumber())] = "";
            }
            $items[_n('Category', 'Categories', 1)]           = "glpi_itilcategories.completename";
            $items[__('Planification')]      = "glpi_tickettasks.begin";
        } else {
            $items[__('Take into account')] = "takeintoaccount_delay_stat";
            $items[__('Resolution')]        = "solve_delay_stat";
            $items[__('Pending')]           = "waiting_duration";
        }

        foreach (array_keys($items) as $key) {
            $link   = "";
            echo Search::showHeaderItem($output_type, $key, $header_num, $link);
        }

       // End Line for column headers
        echo Search::showEndLine($output_type);
    }


    /**
     * Get correct Calendar: Entity or Sla
     *
     * @since 0.90.4
     *
     **/
    public function getCalendar()
    {
        return Entity::getUsedConfig(
            'calendars_strategy',
            $this->fields['entities_id'],
            'calendars_id',
            0
        );
    }


    /**
     * Summary of getTimelinePosition
     * Returns the position of the $sub_type for the $user_id in the timeline
     *
     * @param int $items_id is the id of the ITIL object
     * @param string $sub_type is ITILFollowup, Document_Item, TicketTask, TicketValidation or Solution
     * @param int $users_id
     * @since 9.2
     */
    public static function getTimelinePosition($items_id, $sub_type, $users_id)
    {
        $itilobject = new static();
        $itilobject->fields['id'] = $items_id;
        $actors = $itilobject->getITILActors();

       // 1) rule for followups, documents, tasks and validations:
       //    Matrix for position of timeline objects
       //    R O A (R=Requester, O=Observer, A=AssignedTo)
       //    0 0 0 -> depending on the interface: central -> right, helpdesk -> left
       //    0 0 1 -> Right
       //    0 1 0 -> Left
       //    0 1 1 -> R
       //    1 0 0 -> L
       //    1 0 1 -> L
       //    1 1 0 -> L
       //    1 1 1 -> L
       //    if users_id is not in the actor list, then pos is left
       // 2) rule for solutions: always on the right side

       // default position is left
        $pos = self::TIMELINE_LEFT;

        $pos_matrix = [];
        $pos_matrix[0][0][0] = Session::getCurrentInterface() == "central"
            ? self::TIMELINE_RIGHT
            : self::TIMELINE_LEFT;
        $pos_matrix[0][0][1] = self::TIMELINE_RIGHT;
        $pos_matrix[0][1][1] = self::TIMELINE_RIGHT;

        switch ($sub_type) {
            case 'ITILFollowup':
            case 'Document_Item':
            case static::class . 'Task':
            case static::class . 'Validation':
                if (isset($actors[$users_id])) {
                    $r = in_array(CommonITILActor::REQUESTER, $actors[$users_id]) ? 1 : 0;
                    $o = in_array(CommonITILActor::OBSERVER, $actors[$users_id]) ? 1 : 0;
                    $a = in_array(CommonITILActor::ASSIGN, $actors[$users_id]) ? 1 : 0;
                    if (isset($pos_matrix[$r][$o][$a])) {
                        $pos = $pos_matrix[$r][$o][$a];
                    }
                }
                break;
            case 'Solution': // FIXME Remove it in GLPI 10.1, it may be still used in some edge cases in GLPI 10.0
            case ITILSolution::class:
                $pos = self::TIMELINE_RIGHT;
                break;
        }

        return $pos;
    }


    public function getTimelineItemtypes(): array
    {
        global $PLUGIN_HOOKS;

        /** @var CommonITILObject $obj_type */
        $obj_type = static::getType();
        $foreign_key = static::getForeignKeyField();

       //check sub-items rights
        $tmp = [$foreign_key => $this->getID()];
        $fup = new ITILFollowup();
        $fup->getEmpty();
        $fup->fields['itemtype'] = $obj_type;
        $fup->fields['items_id'] = $this->getID();

        $task_class = $obj_type . "Task";
        $task = new $task_class();

        $solved_statuses = static::getSolvedStatusArray();
        $closed_statuses = static::getClosedStatusArray();
        $solved_closed_statuses = array_merge($solved_statuses, $closed_statuses);

        $canadd_fup = $fup->can(-1, CREATE, $tmp) && !in_array($this->fields["status"], $solved_closed_statuses, true) || isset($_GET['_openfollowup']);
        $canadd_task = $task->can(-1, CREATE, $tmp) && !in_array($this->fields["status"], $solved_closed_statuses, true);
        $canadd_document = $canadd_fup || ($this->canAddItem('Document') && !in_array($this->fields["status"], $solved_closed_statuses, true));
        $canadd_solution = $obj_type::canUpdate() && $this->canSolve() && !in_array($this->fields["status"], $solved_statuses, true);

        $validation = $this->getValidationClassInstance();
        $canadd_validation = $validation !== null
            && $validation->can(-1, CREATE, $tmp)
            && !in_array($this->fields["status"], $solved_closed_statuses, true);

        $itemtypes = [];

        $itemtypes['answer'] = [
            'type'          => 'ITILFollowup',
            'class'         => 'ITILFollowup',
            'icon'          => ITILFollowup::getIcon(),
            'label'         => _x('button', 'Answer'),
            'short_label'   => _x('button', 'Answer'),
            'template'      => 'components/itilobject/timeline/form_followup.html.twig',
            'item'          => $fup,
            'hide_in_menu'  => !$canadd_fup
        ];
        $itemtypes['task'] = [
            'type'          => 'ITILTask',
            'class'         => $task_class,
            'icon'          => CommonITILTask::getIcon(),
            'label'         => _x('button', 'Create a task'),
            'short_label'   => _x('button', 'Task'),
            'template'      => 'components/itilobject/timeline/form_task.html.twig',
            'item'          => $task,
            'hide_in_menu'  => !$canadd_task
        ];
        $itemtypes['solution'] = [
            'type'          => 'ITILSolution',
            'class'         => 'ITILSolution',
            'icon'          => ITILSolution::getIcon(),
            'label'         => _x('button', 'Add a solution'),
            'short_label'   => _x('button', 'Solution'),
            'template'      => 'components/itilobject/timeline/form_solution.html.twig',
            'item'          => new ITILSolution(),
            'hide_in_menu'  => !$canadd_solution
        ];
        $itemtypes['document'] = [
            'type'          => 'Document_Item',
            'class'         => Document_Item::class,
            'icon'          => Document_Item::getIcon(),
            'label'         => _x('button', 'Add a document'),
            'short_label'   => _x('button', 'Document'),
            'template'      => 'components/itilobject/timeline/form_document_item.html.twig',
            'item'          => new Document_Item(),
            'hide_in_menu'  => !$canadd_document
        ];
        if ($validation !== null) {
            $itemtypes['validation'] = [
                'type'          => 'ITILValidation',
                'class'         => $validation::getType(),
                'icon'          => CommonITILValidation::getIcon(),
                'label'         => _x('button', 'Ask for validation'),
                'short_label'   => _x('button', 'Validation'),
                'template'      => 'components/itilobject/timeline/form_validation.html.twig',
                'item'          => $validation,
                'hide_in_menu'  => !$canadd_validation
            ];
        }

        if (isset($PLUGIN_HOOKS[Hooks::TIMELINE_ANSWER_ACTIONS])) {
            /**
             * @var string $plugin
             * @var array|callable $hook_callable
             */
            foreach ($PLUGIN_HOOKS[Hooks::TIMELINE_ANSWER_ACTIONS] as $plugin => $hook_callable) {
                if (!Plugin::isPluginActive($plugin)) {
                    continue;
                }
                if (is_callable($hook_callable)) {
                    $hook_itemtypes = $hook_callable(['item' => $this]);
                } else {
                    $hook_itemtypes = $hook_callable;
                }
                if (is_array($hook_itemtypes)) {
                    $itemtypes = array_merge($itemtypes, $hook_itemtypes);
                } else {
                    trigger_error(
                        sprintf('"%s" hook callback result should be an array, "%s" returned.', Hooks::TIMELINE_ANSWER_ACTIONS, gettype($hook_itemtypes)),
                        E_USER_WARNING
                    );
                }
            }
        }

        return $itemtypes;
    }

    /**
     * Get an HTML string of all timeline actions/buttons provided by plugins via the {@link Hooks::TIMELINE_ACTIONS}  hook.
     * @return string
     * @since 10.0.0
     */
    public function getLegacyTimelineActionsHTML(): string
    {
        global $PLUGIN_HOOKS;

        $legacy_actions = '';

        ob_start();
        Plugin::doHook(Hooks::TIMELINE_ACTIONS, [
            'rand'   => mt_rand(),
            'item'   => $this
        ]);
        $legacy_actions .= ob_get_clean() ?? '';

        return $legacy_actions;
    }

    /**
     * Retrieves all timeline items for this ITILObject
     *
     * @param array $options Possible options:
     * - with_documents     : include documents elements
     * - with_logs          : include log entries
     * - with_validations   : include validation elements
     * - sort_by_date_desc  : sort timeline items by date
     * - check_view_rights  : indicates whether current session rights should be checked for view rights
     * - hide_private_items : force hiding private items (followup/tasks), even if session allow it
     * @since 9.4.0
     *
     * @param bool $with_logs include logs ?
     *
     * @return mixed[] Timeline items
     */
    public function getTimelineItems(array $options = [])
    {

        $params = [
            'with_documents'     => true,
            'with_logs'          => true,
            'with_validations'   => true,
            'sort_by_date_desc'  => $_SESSION['glpitimeline_order'] == CommonITILObject::TIMELINE_ORDER_REVERSE,

            // params used by notifications process (as session cannot be used there)
            'check_view_rights'  => true,
            'hide_private_items' => false,
        ];

        if (array_key_exists('bypass_rights', $options) && $options['bypass_rights']) {
            Toolbox::deprecated('Using `bypass_rights` parameter is deprecated.');
            $params['check_view_rights'] = false;
        }
        if (array_key_exists('expose_private', $options) && $options['expose_private']) {
            Toolbox::deprecated('Using `expose_private` parameter is deprecated.');
            $params['hide_private_items'] = false;
        }
        if (array_key_exists('is_self_service', $options) && $options['is_self_service']) {
            Toolbox::deprecated('Using `is_self_service` parameter is deprecated.');
            $params['hide_private_items'] = false;
        }

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $params[$key] = $val;
            }
        }

        if ($this->isNewItem()) {
            return [];
        }

        if ($params['check_view_rights'] && !$this->canViewItem()) {
            return [];
        }

        $objType    = static::getType();
        $foreignKey = static::getForeignKeyField();
        $timeline = [];

        $canupdate_parent = $this->canUpdateItem() && !in_array($this->fields['status'], $this->getClosedStatusArray());

       //checks rights
        $restrict_fup = $restrict_task = [];
        if (
            $params['hide_private_items']
            || ($params['check_view_rights'] && !Session::haveRight("followup", ITILFollowup::SEEPRIVATE))
        ) {
            if (!$params['check_view_rights']) {
                // notification case, we cannot rely on session
                $restrict_fup = [
                    'is_private' => 0,
                ];
            } else {
                $restrict_fup = [
                    'OR' => [
                        'is_private' => 0,
                        'users_id'   => Session::getCurrentInterface() === "central" ? (int)Session::getLoginUserID() : 0,
                    ]
                ];
            }
        }

        $restrict_fup['itemtype'] = static::getType();
        $restrict_fup['items_id'] = $this->getID();

        $taskClass = $objType . "Task";
        $task_obj  = new $taskClass();
        if (
            $task_obj->maybePrivate()
            && (
                $params['hide_private_items']
                || ($params['check_view_rights'] && !Session::haveRight($task_obj::$rightname, CommonITILTask::SEEPRIVATE))
            )
        ) {
            if (!$params['check_view_rights']) {
                // notification case, we cannot rely on session
                $restrict_task = [
                    'is_private' => 0,
                ];
            } else {
                $restrict_task = [
                    'OR' => [
                        'is_private' => 0,
                        'users_id'   => Session::getCurrentInterface() === "central" ? (int)Session::getLoginUserID() : 0,
                    ]
                ];
            }
        }

        // Add followups to timeline
        $followup_obj = new ITILFollowup();
        if (!$params['check_view_rights'] || $followup_obj->canview()) {
            $followups = $followup_obj->find(
                ['items_id'  => $this->getID()] + $restrict_fup,
                ['date_creation DESC', 'id DESC']
            );

            foreach ($followups as $followups_id => $followup_row) {
                // Safer to use a clean object to load our data
                $followup = new ITILFollowup();
                $followup->setParentItem($this);
                $followup->fields = $followup_row;
                $followup->post_getFromDB();

                if (!$params['check_view_rights'] || $followup->canViewItem()) {
                    $followup_row['can_edit'] = $followup->canUpdateItem();
                    $followup_row['can_promote'] =
                        Session::getCurrentInterface() === 'central'
                        && $this instanceof Ticket
                        && Ticket::canCreate()
                    ;
                    $timeline["ITILFollowup_" . $followups_id] = [
                        'type'     => ITILFollowup::class,
                        'item'     => $followup_row,
                        'object'   => $followup,
                        'itiltype' => 'Followup'
                    ];
                }
            }
        }

        // Add tasks to timeline
        if (!$params['check_view_rights'] || $task_obj->canview()) {
            $tasks = $task_obj->find(
                [$foreignKey => $this->getID()] + $restrict_task,
                'date_creation DESC'
            );

            foreach ($tasks as $tasks_id => $task_row) {
                // Safer to use a clean object to load our data
                $task = new $taskClass();
                $task->fields = $task_row;
                $task->post_getFromDB();

                if (!$params['check_view_rights'] || $task->canViewItem()) {
                    $task_row['can_edit'] = $task->canUpdateItem();
                    $task_row['can_promote'] =
                        Session::getCurrentInterface() === 'central'
                        && $this instanceof Ticket
                        && Ticket::canCreate()
                    ;
                    $timeline[$task::getType() . "_" . $tasks_id] = [
                        'type'     => $taskClass,
                        'item'     => $task_row,
                        'object'   => $task,
                        'itiltype' => 'Task'
                    ];
                }
            }
        }

        // Add solutions to timeline
        $solution_obj   = new ITILSolution();
        $solution_items = $solution_obj->find([
            'itemtype'  => static::getType(),
            'items_id'  => $this->getID()
        ]);

        foreach ($solution_items as $solution_item) {
            // Safer to use a clean object to load our data
            $solution = new ITILSolution();
            $solution->setParentItem($this);
            $solution->fields = $solution_item;
            $solution->post_getFromDB();

            $timeline["ITILSolution_" . $solution_item['id'] ] = [
                'type'     => ITILSolution::class,
                'itiltype' => 'Solution',
                'item'     => [
                    'id'                 => $solution_item['id'],
                    'content'            => $solution_item['content'],
                    'date'               => $solution_item['date_creation'],
                    'users_id'           => $solution_item['users_id'],
                    'solutiontypes_id'   => $solution_item['solutiontypes_id'],
                    'can_edit'           => $objType::canUpdate() && $this->canSolve(),
                    'timeline_position'  => self::TIMELINE_RIGHT,
                    'users_id_editor'    => $solution_item['users_id_editor'],
                    'date_creation'      => $solution_item['date_creation'],
                    'date_mod'           => $solution_item['date_mod'],
                    'users_id_approval'  => $solution_item['users_id_approval'],
                    'date_approval'      => $solution_item['date_approval'],
                    'status'             => $solution_item['status']
                ],
                'object' => $solution,
            ];
        }

        // Add validation to timeline
        $validation_class = $objType . "Validation";
        if (
            class_exists($validation_class) && $params['with_validations']
            && (!$params['check_view_rights'] || $validation_class::canView())
        ) {
            $valitation_obj = new $validation_class();
            $validations = $valitation_obj->find([
                $foreignKey => $this->getID()
            ]);

            foreach ($validations as $validations_id => $validation_row) {
                // Safer to use a clean object to load our data
                $validation = new $validation_class();
                $validation->fields = $validation_row;
                $validation->post_getFromDB();

                $canedit = $valitation_obj->can($validations_id, UPDATE);
                $cananswer = ($validation_row['users_id_validate'] === Session::getLoginUserID() &&
                $validation_row['status'] == CommonITILValidation::WAITING);
                $user = new User();
                $user->getFromDB($validation_row['users_id_validate']);

                $request_key = $valitation_obj::getType() . '_' . $validations_id
                    . (empty($validation_row['validation_date']) ? '' : '_request'); // If no answer, no suffix to see attached documents on request
                $timeline[$request_key] = [
                    'type' => $validation_class,
                    'item' => [
                        'id'        => $validations_id,
                        'date'      => $validation_row['submission_date'],
                        'content'   => __('Validation request') . " <i class='ti ti-arrow-right'></i><i class='ti ti-user text-muted me-1'></i>" . $user->getlink(),
                        'comment_submission' => $validation_row['comment_submission'],
                        'users_id'  => $validation_row['users_id'],
                        'can_edit'  => $canedit,
                        'can_answer'   => $cananswer,
                        'users_id_validate'  => $validation_row['users_id_validate'],
                        'timeline_position' => $validation_row['timeline_position']
                    ],
                    'itiltype' => 'Validation',
                    'class'    => 'validation-request ' .
                    ($validation_row['status'] == CommonITILValidation::WAITING  ? "validation-waiting"  : "") .
                    ($validation_row['status'] == CommonITILValidation::ACCEPTED ? "validation-accepted" : "") .
                    ($validation_row['status'] == CommonITILValidation::REFUSED  ? "validation-refused"  : ""),
                    'item_action' => 'validation-request',
                    'object'      => $validation,
                ];

                if (!empty($validation_row['validation_date'])) {
                    $timeline[$valitation_obj::getType() . "_" . $validations_id] = [
                        'type' => $validation_class,
                        'item' => [
                            'id'        => $validations_id,
                            'date'      => $validation_row['validation_date'],
                            'content'   => __('Validation request answer') . " : " .
                            _sx('status', ucfirst($validation_class::getStatus($validation_row['status']))),
                            'comment_validation' => $validation_row['comment_validation'],
                            'users_id'  => $validation_row['users_id_validate'],
                            'status'    => "status_" . $validation_row['status'],
                            'can_edit'  => $validation_row['users_id_validate'] === Session::getLoginUserID(),
                            'timeline_position' => $validation_row['timeline_position'],
                        ],
                        'class'       => 'validation-answer',
                        'itiltype'    => 'Validation',
                        'item_action' => 'validation-answer',
                        'object'      => $validation,
                    ];
                }
            }
        }

        // Add documents to timeline
        if ($params['with_documents']) {
            $document_item_obj = new Document_Item();
            $document_obj      = new Document();
            $document_items    = $document_item_obj->find([
                $this->getAssociatedDocumentsCriteria(!$params['check_view_rights']),
                'timeline_position'  => ['>', self::NO_TIMELINE]
            ]);
            foreach ($document_items as $document_item) {
                if (!$document_obj->getFromDB($document_item['documents_id'])) {
                    // Orphan `Document_Item`
                    continue;
                }

                $item = $document_obj->fields;
                $item['date'] = $document_item['date'] ?? $document_item['date_creation'];
                // #1476 - set date_creation, date_mod and owner to attachment ones
                $item['date_creation'] = $document_item['date_creation'];
                $item['date_mod'] = $document_item['date_mod'];
                $item['users_id'] = $document_item['users_id'];
                $item['documents_item_id'] = $document_item['id'];

                $item['timeline_position'] = $document_item['timeline_position'];
                $item['_can_edit'] = Document::canUpdate() && $document_obj->canUpdateItem();
                $item['_can_delete'] = Document::canDelete() && $document_obj->canDeleteItem() && $canupdate_parent;

                $timeline_key = $document_item['itemtype'] . "_" . $document_item['items_id'];
                if ($document_item['itemtype'] == static::getType()) {
                  // document associated directly to itilobject
                    $timeline["Document_" . $document_item['documents_id']] = [
                        'type' => 'Document_Item',
                        'item' => $item,
                        'object' => $document_obj,
                    ];
                } elseif (isset($timeline[$timeline_key])) {
                 // document associated to a sub item of itilobject
                    if (!isset($timeline[$timeline_key]['documents'])) {
                        $timeline[$timeline_key]['documents'] = [];
                    }

                    $docpath = GLPI_DOC_DIR . "/" . $item['filepath'];
                    $is_image = Document::isImage($docpath);
                    $sub_document = [
                        'type' => 'Document_Item',
                        'item' => $item,
                    ];
                    if ($is_image) {
                        $sub_document['_is_image'] = true;
                        $sub_document['_size'] = getimagesize($docpath);
                    }
                    $timeline[$timeline_key]['documents'][] = $sub_document;
                }
            }
        }

        // Add logs to timeline
        if ($params['with_logs'] && Session::getCurrentInterface() == "central") {
           //add logs to timeline
            $log_items = Log::getHistoryData($this, 0, 0, [
                'OR' => [
                    'id_search_option' => ['>', 0],
                    'itemtype_link'    => ['User', 'Group', 'Supplier'],
                ]
            ]);

            foreach ($log_items as $log_row) {
                // Safer to use a clean object to load our data
                $log = new Log();
                $log->fields = $log_row;
                $log->post_getFromDB();

                $content = $log_row['change'];
                if (strlen($log_row['field']) > 0) {
                    $content = sprintf(__("%s: %s"), $log_row['field'], $content);
                }
                $content = "<i class='fas fa-history me-1' title='" . __("Log entry") . "' data-bs-toggle='tooltip'></i>" . $content;
                $timeline["Log_" . $log_row['id'] ] = [
                    'type'     => 'Log',
                    'class'    => 'text-muted d-none',
                    'item'     => [
                        'id'                 => $log_row['id'],
                        'content'            => $content,
                        'date'               => $log_row['date_mod'],
                        'users_id'           => 0,
                        'can_edit'           => false,
                        'timeline_position'  => self::TIMELINE_LEFT,
                    ],
                    'object' => $log,
                ];
            }
        }

        Plugin::doHook(Hooks::SHOW_IN_TIMELINE, ['item' => $this, 'timeline' => &$timeline]);

       //sort timeline items by date
        $reverse = $params['sort_by_date_desc'];
        usort($timeline, function ($a, $b) use ($reverse) {
            $date_a = $a['item']['date_creation'] ?? $a['item']['date'];
            $date_b = $b['item']['date_creation'] ?? $b['item']['date'];
            $diff = strtotime($date_a) - strtotime($date_b);
            return $reverse ? 0 - $diff : $diff;
        });

        return $timeline;
    }


    /**
     * @since 9.4.0
     *
     * @param CommonDBTM $item The item whose form should be shown
     * @param integer $id ID of the item
     * @param mixed[] $params Array of extra parameters
     *
     * @return void
     */
    public static function showSubForm(CommonDBTM $item, $id, $params)
    {

        if ($item instanceof Document_Item) {
            Document_Item::showAddFormForItem($params['parent'], '');
        } else if ($item->getType() == $params['parent']->getType()) {
            return self::showEditDescriptionForm($params['parent']);
        } else if (
            method_exists($item, "showForm")
                 && $item->can(-1, CREATE, $params)
        ) {
            $item->showForm($id, $params);
        }
    }

    public static function showEditDescriptionForm(CommonITILObject $item)
    {
        $can_requester = true;
        if (method_exists($item, "canRequesterUpdateItem")) {
            $can_requester = $item->canRequesterUpdateItem();
        }
        TemplateRenderer::getInstance()->display('components/itilobject/timeline/simple_form.html.twig', [
            'item'          => $item,
            'canupdate'     => (Session::getCurrentInterface() == "central" && $item->canUpdateItem()),
            'can_requester' => $can_requester,
        ]);
    }

    /**
     * Summary of getITILActors
     * Get the list of actors for the current Change
     * will return an assoc array of users_id => array of roles.
     *
     * @since 9.4.0
     *
     * @return array[] of array[] of users and roles
     */
    public function getITILActors()
    {
        global $DB;

        $users_table = $this->getTable() . '_users';
        switch ($this->getType()) {
            case 'Ticket':
                $groups_table = 'glpi_groups_tickets';
                break;
            case 'Problem':
                $groups_table = 'glpi_groups_problems';
                break;
            default:
                $groups_table = $this->getTable() . '_groups';
                break;
        }
        $fk = $this->getForeignKeyField();

        $subquery1 = new \QuerySubQuery([
            'SELECT'    => [
                'usr.id AS users_id',
                'tu.type AS type'
            ],
            'FROM'      => "$users_table AS tu",
            'LEFT JOIN' => [
                User::getTable() . ' AS usr' => [
                    'ON' => [
                        'tu'  => 'users_id',
                        'usr' => 'id'
                    ]
                ]
            ],
            'WHERE'     => [
                "tu.$fk" => $this->getID()
            ]
        ]);

        $subquery2 = new \QuerySubQuery([
            'SELECT'    => [
                'usr.id AS users_id',
                'gt.type AS type'
            ],
            'FROM'      => "$groups_table AS gt",
            'LEFT JOIN' => [
                Group_User::getTable() . ' AS gu'   => [
                    'ON' => [
                        'gu'  => 'groups_id',
                        'gt'  => 'groups_id'
                    ]
                ],
                User::getTable() . ' AS usr'        => [
                    'ON' => [
                        'gu'  => 'users_id',
                        'usr' => 'id'
                    ]
                ]
            ],
            'WHERE'     => [
                "gt.$fk" => $this->getID()
            ]
        ]);

        $union = new \QueryUnion([$subquery1, $subquery2], false, 'allactors');
        $iterator = $DB->request([
            'SELECT'          => [
                'users_id',
                'type'
            ],
            'DISTINCT'        => true,
            'FROM'            => $union
        ]);

        $users_keys = [];
        foreach ($iterator as $current_tu) {
            $users_keys[$current_tu['users_id']][] = $current_tu['type'];
        }

        return $users_keys;
    }


    /**
     * Number of followups of the object
     *
     * @param boolean $with_private true : all followups / false : only public ones (default 1)
     *
     * @return integer followup count
     **/
    public function numberOfFollowups($with_private = true)
    {
        global $DB;

        $RESTRICT = [];
        if ($with_private !== true) {
            $RESTRICT['is_private'] = 0;
        }

       // Set number of followups
        $result = $DB->request([
            'COUNT'  => 'cpt',
            'FROM'   => 'glpi_itilfollowups',
            'WHERE'  => [
                'itemtype'  => $this->getType(),
                'items_id'  => $this->fields['id']
            ] + $RESTRICT
        ])->current();

        return $result['cpt'];
    }

    /**
     * Number of tasks of the object
     *
     * @param boolean $with_private true : all followups / false : only public ones (default 1)
     *
     * @return integer
     **/
    public function numberOfTasks($with_private = true)
    {
        global $DB;

        $table = 'glpi_' . strtolower($this->getType()) . 'tasks';

        $RESTRICT = [];
        if ($with_private !== true && $this->getType() == 'Ticket') {
           //No private tasks for Problems and Changes
            $RESTRICT['is_private'] = 0;
        }

       // Set number of tasks
        $row = $DB->request([
            'COUNT'  => 'cpt',
            'FROM'   => $table,
            'WHERE'  => [
                $this->getForeignKeyField()   => $this->fields['id']
            ] + $RESTRICT
        ])->current();
        return (int)$row['cpt'];
    }

    /**
     * Check if input contains a flag set to prevent 'takeintoaccount' delay computation.
     *
     * @param array $input
     *
     * @return boolean
     */
    public function isTakeIntoAccountComputationBlocked($input)
    {
        return array_key_exists('_do_not_compute_takeintoaccount', $input)
         && $input['_do_not_compute_takeintoaccount'];
    }


    /**
     * Check if input contains a flag set to prevent status computation.
     *
     * @param array $input
     *
     * @return boolean
     */
    public function isStatusComputationBlocked(array $input)
    {
        return array_key_exists('_do_not_compute_status', $input)
         && $input['_do_not_compute_status'];
    }


    public function addDefaultFormTab(array &$ong)
    {

        $timeline    = $this->getTimelineItems(['with_logs' => false]);
        $nb_elements = count($timeline);
        $label = $this->getTypeName(1);
        if ($nb_elements > 0) {
            $label .= " <span class='badge'>$nb_elements</span>";
        }

        $ong[$this->getType() . '$main'] = $label;
        return $this;
    }

    public static function getAdditionalMenuOptions()
    {
        $tplclass = self::getTemplateClass();
        if ($tplclass::canView()) {
            $menu = [
                $tplclass => [
                    'title' => $tplclass::getTypeName(Session::getPluralNumber()),
                    'page'  => $tplclass::getSearchURL(false),
                    'icon'  => $tplclass::getIcon(),
                    'links' => [
                        'search' => $tplclass::getSearchURL(false),
                    ],
                ],
            ];

            if ($tplclass::canCreate()) {
                $menu[$tplclass]['links']['add'] = $tplclass::getFormURL(false);
            }
            return $menu;
        }
        return false;
    }


    /**
     * @see CommonGLPI::getAdditionalMenuLinks()
     *
     * @since 9.5.0
     **/
    public static function getAdditionalMenuLinks()
    {
        $links = [];
        $tplclass = self::getTemplateClass();
        if ($tplclass::canView()) {
            $links['template'] = $tplclass::getSearchURL(false);
        }
        $links['summary_kanban'] = static::getFormURL(false) . '?showglobalkanban=1';

        return $links;
    }


    /**
     * Get template to use
     * Use force_template first, then try on template define for type and category
     * then use default template of active profile of connected user and then use default entity one
     *
     * @param integer      $force_template     itiltemplate_id to use (case of preview for example)
     * @param integer|null $type               type of the ticket
     *                                         (use Ticket::INCIDENT_TYPE or Ticket::DEMAND_TYPE constants value)
     * @param integer      $itilcategories_id  ticket category
     * @param integer      $entities_id
     *
     * @return ITILTemplate
     *
     * @since 9.5.0
     **/
    public function getITILTemplateToUse(
        $force_template = 0,
        $type = null,
        $itilcategories_id = 0,
        $entities_id = -1
    ) {
        if (!$type && $this->getType() != Ticket::getType()) {
            $type = true;
        }
       // Load template if available :
        $tplclass = static::getTemplateClass();
        $tt              = new $tplclass();
        $template_loaded = false;

        if ($force_template) {
           // with type and categ
            if ($tt->getFromDBWithData($force_template, true)) {
                $template_loaded = true;
            }
        }

        if (
            !$template_loaded
            && $type
            && $itilcategories_id
        ) {
            $categ = new ITILCategory();
            if ($categ->getFromDB($itilcategories_id)) {
                $field = $this->getTemplateFieldName($type);

                if (!empty($categ->fields[$field]) && $categ->fields[$field]) {
                    // without type and categ
                    if ($tt->getFromDBWithData($categ->fields[$field], false)) {
                        $template_loaded = true;
                    }
                }
            }
        }

       // If template loaded from type and category do not check after
        if ($template_loaded) {
            return $tt;
        }

       //Get template from profile
        if (!$template_loaded && $type) {
            $field = $this->getTemplateFieldName($type);
            $field = str_replace(['_incident', '_demand'], ['', ''], $field);
           // load default profile one if not already loaded
            if (
                isset($_SESSION['glpiactiveprofile'][$field])
                && $_SESSION['glpiactiveprofile'][$field]
            ) {
               // with type and categ
                if (
                    $tt->getFromDBWithData(
                        $_SESSION['glpiactiveprofile'][$field],
                        true
                    )
                ) {
                    $template_loaded = true;
                }
            }
        }

       //Get template from entity
        if (
            !$template_loaded
            && ($entities_id >= 0)
        ) {
           // load default entity one if not already loaded
            $template_id = Entity::getUsedConfig(
                strtolower($this->getType()) . 'templates_strategy',
                $entities_id,
                strtolower($this->getType()) . 'templates_id',
                0
            );
            if ($template_id > 0 && $tt->getFromDBWithData($template_id, true)) {
                $template_loaded = true;
            }
        }

       // Check if profile / entity set type and category and try to load template for these values
        if ($template_loaded) { // template loaded for profile or entity
            $newtype              = $type;
            $newitilcategories_id = $itilcategories_id;
           // Get predefined values for ticket template
            if (isset($tt->predefined['itilcategories_id']) && $tt->predefined['itilcategories_id']) {
                $newitilcategories_id = $tt->predefined['itilcategories_id'];
            }
            if (isset($tt->predefined['type']) && $tt->predefined['type']) {
                $newtype = $tt->predefined['type'];
            }
            if (
                $newtype
                && $newitilcategories_id
            ) {
                $categ = new ITILCategory();
                if ($categ->getFromDB($newitilcategories_id)) {
                    $field = $this->getTemplateFieldName($newtype);

                    if (isset($categ->fields[$field]) && $categ->fields[$field]) {
                        // without type and categ
                        if ($tt->getFromDBWithData($categ->fields[$field], false)) {
                            $template_loaded = true;
                        }
                    }
                }
            }
        }
        return $tt;
    }

    /**
     * Get template field name
     *
     * @param string $type Type, if any
     *
     * @return string
     */
    public function getTemplateFieldName($type = null): string
    {
        $field = strtolower(static::getType()) . 'templates_id';
        if (static::getType() === Ticket::getType()) {
            switch ($type) {
                case Ticket::INCIDENT_TYPE:
                    $field .= '_incident';
                    break;

                case Ticket::DEMAND_TYPE:
                    $field .= '_demand';
                    break;

                case true:
                   //for changes and problem, or from profiles
                    break;

                default:
                    $field = '';
                    trigger_error('Missing type for Ticket template!', E_USER_WARNING);
                    break;
            }
        }

        return $field;
    }

    /**
     * @since 9.5.0
     *
     * @param integer $entity entities_id usefull if function called by cron (default 0)
     **/
    abstract public static function getDefaultValues($entity = 0);

    /**
     * Get template class name.
     *
     * @since 9.5.0
     *
     * @return string
     */
    public static function getTemplateClass()
    {
        return static::getType() . 'Template';
    }

    /**
     * Get template form field name
     *
     * @since 9.5.0
     *
     * @return string
     */
    public static function getTemplateFormFieldName()
    {
        return '_' . strtolower(static::getType()) . 'template';
    }

    /**
     * Get common request criteria
     *
     * @since 9.5.0
     *
     * @return array
     */
    public static function getCommonCriteria()
    {
        $fk = self::getForeignKeyField();
        $gtable = str_replace('glpi_', 'glpi_groups_', static::getTable());
        $itable = str_replace('glpi_', 'glpi_items_', static::getTable());
        if (self::getType() == 'Change') {
            $gtable = 'glpi_changes_groups';
            $itable = 'glpi_changes_items';
        }
        $utable = static::getTable() . '_users';
        $stable = static::getTable() . '_suppliers';
        if (self::getType() == 'Ticket') {
            $stable = 'glpi_suppliers_tickets';
        }
        $table = static::getTable();
        $criteria = [
            'SELECT'          => [
                "$table.*",
                'glpi_itilcategories.completename AS catname'
            ],
            'DISTINCT'        => true,
            'FROM'            => $table,
            'LEFT JOIN'       => [
                $gtable  => [
                    'ON' => [
                        $table   => 'id',
                        $gtable  => $fk
                    ]
                ],
                $utable  => [
                    'ON' => [
                        $table   => 'id',
                        $utable  => $fk
                    ]
                ],
                $stable  => [
                    'ON' => [
                        $table   => 'id',
                        $stable  => $fk
                    ]
                ],
                'glpi_itilcategories'      => [
                    'ON' => [
                        $table                  => 'itilcategories_id',
                        'glpi_itilcategories'   => 'id'
                    ]
                ],
                $itable  => [
                    'ON' => [
                        $table   => 'id',
                        $itable  => $fk
                    ]
                ]
            ],
            'ORDERBY'            => "$table.date_mod DESC"
        ];
        if (count($_SESSION["glpiactiveentities"]) > 1) {
            $criteria['LEFT JOIN']['glpi_entities'] = [
                'ON' => [
                    'glpi_entities'   => 'id',
                    $table            => 'entities_id'
                ]
            ];
            $criteria['SELECT'] = array_merge(
                $criteria['SELECT'],
                [
                    'glpi_entities.completename AS entityname',
                    "$table.entities_id AS entityID"
                ]
            );
        }
        return $criteria;
    }

    public function getForbiddenSingleMassiveActions()
    {
        $excluded = parent::getForbiddenSingleMassiveActions();

        if (isset($this->fields['global_validation']) && $this->fields['global_validation'] != CommonITILValidation::NONE) {
           //a validation has already been requested/done
            $excluded[] = 'TicketValidation:submit_validation';
        }

        $excluded[] = '*:add_actor';
        $excluded[] = '*:add_task';
        $excluded[] = '*:add_followup';

        return $excluded;
    }

    /**
     * Returns criteria that can be used to get documents related to current instance.
     *
     * @return array
     */
    public function getAssociatedDocumentsCriteria($bypass_rights = false): array
    {
        $task_class = $this->getType() . 'Task';

        $or_crits = [
         // documents associated to ITIL item directly
            [
                Document_Item::getTableField('itemtype') => $this->getType(),
                Document_Item::getTableField('items_id') => $this->getID(),
            ],
        ];

       // documents associated to followups
        if ($bypass_rights || ITILFollowup::canView()) {
            $fup_crits = [
                ITILFollowup::getTableField('itemtype') => $this->getType(),
                ITILFollowup::getTableField('items_id') => $this->getID(),
            ];
            if (!$bypass_rights && !Session::haveRight(ITILFollowup::$rightname, ITILFollowup::SEEPRIVATE)) {
                $fup_crits[] = [
                    'OR' => ['is_private' => 0, 'users_id' => Session::getLoginUserID()],
                ];
            }
            $or_crits[] = [
                Document_Item::getTableField('itemtype') => ITILFollowup::getType(),
                Document_Item::getTableField('items_id') => new QuerySubQuery(
                    [
                        'SELECT' => 'id',
                        'FROM'   => ITILFollowup::getTable(),
                        'WHERE'  => $fup_crits,
                    ]
                ),
            ];
        }

       // documents associated to solutions
        if ($bypass_rights || ITILSolution::canView()) {
            $or_crits[] = [
                Document_Item::getTableField('itemtype') => ITILSolution::getType(),
                Document_Item::getTableField('items_id') => new QuerySubQuery(
                    [
                        'SELECT' => 'id',
                        'FROM'   => ITILSolution::getTable(),
                        'WHERE'  => [
                            ITILSolution::getTableField('itemtype') => $this->getType(),
                            ITILSolution::getTableField('items_id') => $this->getID(),
                        ],
                    ]
                ),
            ];
        }

       // documents associated to ticketvalidation
        $validation_class = static::getType() . 'Validation';
        if (class_exists($validation_class) && ($bypass_rights ||  $validation_class::canView())) {
            $or_crits[] = [
                Document_Item::getTableField('itemtype') => $validation_class::getType(),
                Document_Item::getTableField('items_id') => new QuerySubQuery(
                    [
                        'SELECT' => 'id',
                        'FROM'   => $validation_class::getTable(),
                        'WHERE'  => [
                            $validation_class::getTableField($validation_class::$items_id) => $this->getID(),
                        ],
                    ]
                ),
            ];
        }

       // documents associated to tasks
        if ($bypass_rights || $task_class::canView()) {
            $tasks_crit = [
                $this->getForeignKeyField() => $this->getID(),
            ];
            if (!$bypass_rights && !Session::haveRight($task_class::$rightname, CommonITILTask::SEEPRIVATE)) {
                $tasks_crit[] = [
                    'OR' => ['is_private' => 0, 'users_id' => Session::getLoginUserID()],
                ];
            }
            $or_crits[] = [
                'glpi_documents_items.itemtype' => $task_class::getType(),
                'glpi_documents_items.items_id' => new QuerySubQuery(
                    [
                        'SELECT' => 'id',
                        'FROM'   => $task_class::getTable(),
                        'WHERE'  => $tasks_crit,
                    ]
                ),
            ];
        }

        return ['OR' => $or_crits];
    }

    /**
     * Check if this item is new
     *
     * @return bool
     */
    protected function isNew()
    {
        if (isset($this->input['status'])) {
            $status = $this->input['status'];
        } else if (isset($this->fields['status'])) {
            $status = $this->fields['status'];
        } else {
            throw new \LogicException("Can't get status value: no object loaded");
        }

        return $status == CommonITILObject::INCOMING;
    }

    /**
     * Retrieve linked items table name
     *
     * @since 9.5.0
     *
     * @return string
     */
    public static function getItemsTable()
    {
        switch (static::getType()) {
            case 'Change':
                return 'glpi_changes_items';
            case 'Problem':
                return 'glpi_items_problems';
            case 'Ticket':
                return 'glpi_items_tickets';
            default:
                throw new \RuntimeException('Unknown ITIL type ' . static::getType());
        }
    }


    public function getLinkedItems(): array
    {
        global $DB;

        $assets = $DB->request([
            'SELECT' => ["itemtype", "items_id"],
            'FROM'   => static::getItemsTable(),
            'WHERE'  => [$this->getForeignKeyField() => $this->getID()]
        ]);

        $assets = iterator_to_array($assets);

        $tab = [];
        foreach ($assets as $asset) {
            if (!class_exists($asset['itemtype'])) {
                //ignore if class does not exists (maybe a plugin)
                continue;
            }
            $tab[$asset['itemtype']][$asset['items_id']] = $asset['items_id'];
        }

        return $tab;
    }

    /**
     * Should impact tab be displayed? Check if there is a valid linked item
     *
     * @return boolean
     */
    protected function hasImpactTab()
    {
        foreach ($this->getLinkedItems() as $itemtype => $items) {
            $class = $itemtype;
            if (Impact::isEnabled($class) && Session::getCurrentInterface() === "central") {
                return true;
            }
        }
        return false;
    }

    /**
     * Get criteria needed to match objets with an "open" status (= not resolved
     * or closed)
     *
     * @return array
     */
    public static function getOpenCriteria(): array
    {
        $table = static::getTable();

        return [
            'NOT' => [
                "$table.status" => array_merge(
                    static::getSolvedStatusArray(),
                    static::getClosedStatusArray()
                )
            ]
        ];
    }

    public function handleItemsIdInput(): void
    {
        if (!empty($this->input['items_id'])) {
            $item_link_class = static::getItemLinkClass();
            $item_link = new $item_link_class();
            foreach ($this->input['items_id'] as $itemtype => $items) {
                foreach ($items as $items_id) {
                    $item_link->add([
                        'items_id'                    => $items_id,
                        'itemtype'                    => $itemtype,
                        static::getForeignKeyField()  => $this->fields['id'],
                        '_disablenotif'               => true
                    ]);
                }
            }
        }
    }

    abstract public static function getItemLinkClass(): string;
    /**
     * Handle "_tasktemplates_id" special input
     */
    public function handleTaskTemplateInput()
    {
       // Check input is valid
        if (
            !isset($this->input['_tasktemplates_id'])
            || !is_array($this->input['_tasktemplates_id'])
            || !count($this->input['_tasktemplates_id'])
        ) {
            return;
        }

       // Add tasks in tasktemplates if defined in itiltemplate
        $itiltask_class = $this->getType() . 'Task';
        $itiltask   = new $itiltask_class();
        foreach ($this->input['_tasktemplates_id'] as $tasktemplates_id) {
            $itiltask->add([
                '_tasktemplates_id'           => $tasktemplates_id,
                $this->getForeignKeyField()   => $this->fields['id'],
                'date'                        => $this->fields['date'],
                '_disablenotif'               => true
            ]);
        }
    }

    /**
     * Handle "_itilfollowuptemplates_id" special input
     */
    public function handleITILFollowupTemplateInput(): void
    {
       // Check input is valid
        if (
            !isset($this->input['_itilfollowuptemplates_id'])
            || !is_array($this->input['_itilfollowuptemplates_id'])
            || !count($this->input['_itilfollowuptemplates_id'])
        ) {
            return;
        }

       // Add tasks in itilfollowup template if defined in itiltemplate
        foreach ($this->input['_itilfollowuptemplates_id'] as $fup_templates_id) {
           // Insert new followup from template
            $fup = new ITILFollowup();
            $fup->add([
                '_itilfollowuptemplates_id' => $fup_templates_id,
                'itemtype'                  => $this->getType(),
                'items_id'                  => $this->getID(),
                '_disablenotif'             => true,
            ]);
        }
    }

    /**
     * Handle "_solutiontemplates_id" special input
     */
    public function handleSolutionTemplateInput(): void
    {
        // Check input is valid
        if (!isset($this->input['_solutiontemplates_id'])) {
            return;
        }

        $solution = new ITILSolution();
        $input = [
            '_solutiontemplates_id' => $this->input['_solutiontemplates_id'],
            'itemtype'              => $this->getType(),
            'items_id'              => $this->getID(),
        ];
        if (isset($this->input['_do_not_compute_status'])) {
            $input['_do_not_compute_status'] = $this->input['_do_not_compute_status'];
        }
        $solution->add($input);
    }

    /**
     * Handle notifications to be sent after item creation.
     *
     * @return void
     */
    public function handleNewItemNotifications(): void
    {
        global $CFG_GLPI;

        if (!isset($this->input['_disablenotif']) && $CFG_GLPI['use_notifications']) {
            $this->getFromDB($this->fields['id']); // Reload item to get actual status

            NotificationEvent::raiseEvent('new', $this);

            $status = $this->fields['status'] ?? null;
            if (in_array($status, $this->getSolvedStatusArray())) {
                NotificationEvent::raiseEvent('solved', $this);
            }
            if (in_array($status, $this->getClosedStatusArray())) {
                NotificationEvent::raiseEvent('closed', $this);
            }
        }
    }

    /**
     * Manage Validation add from input (form and rules)
     *
     * @param array $input
     *
     * @return boolean
     */
    public function manageValidationAdd($input)
    {
        $validation = $this->getValidationClassInstance();

        if ($validation === null) {
            return true;
        }

        $self_fk = $this->getForeignKeyField();

        //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 => $value) {
                switch ($value) {
                    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 ($value as $groups_id) {
                                $validation_right = 'validate';
                                if ($this->getType() === Ticket::class) {
                                    $validation_right = isset($input['type']) && $input['type'] == Ticket::DEMAND_TYPE
                                        ? 'validate_request'
                                        : 'validate_incident';
                                }
                                $opt = [
                                    'groups_id' => $groups_id,
                                    'right'     => $validation_right,
                                    'entity'    => $entid
                                ];

                                $data_users = $validation->getGroupUserHaveRights($opt);

                                foreach ($data_users as $user) {
                                    $validations_to_send[] = $user['id'];
                                }
                            }
                        } else {
                            $validations_to_send[] = $value;
                        }
                }
            }

            // 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);

            if (count($validations_to_send)) {
                $values            = [];
                $values[$self_fk]  = $this->fields['id'];
                if (isset($input['id']) && $input['id'] != $this->fields['id']) {
                    $values['_itilobject_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 (!$validation->alreadyExists($values[$self_fk], $user)) {
                            $values["users_id_validate"] = $user;
                            if ($validation->add($values)) {
                                $add_done = true;
                            }
                        }
                    }
                    if ($add_done) {
                        Event::log(
                            $this->fields['id'],
                            strtolower($this->getType()),
                            4,
                            "tracking",
                            sprintf(
                                __('%1$s updates the item %2$s'),
                                $_SESSION["glpiname"],
                                $this->fields['id']
                            )
                        );
                    }
                }
            }
        }
        return true;
    }

    /**
     * Manage actors posted by itil form
     * New way to do it with a general array containing all item actors.
     * We compare to old actors (in case of items's update) to know which we need to remove/add/update
     *
     * @param bool $disable_notifications
     *
     * @since 10.0.0
     *
     * @return void
     */
    protected function updateActors(bool $disable_notifications = false)
    {
        // Reload actors to be able to categorize users as added/updated/deleted.
        $this->loadActors();

        $common_actor_input = [
            '_do_not_compute_takeintoaccount' => $this->isTakeIntoAccountComputationBlocked($this->input),
            '_from_object'                    => true,
        ];
        if ($disable_notifications) {
            $common_actor_input['_disablenotif'] = true;
        }

        $actor_itemtypes = [
            User::class,
            Group::class,
            Supplier::class,
        ];
        $actor_types = [
            'requester',
            'assign',
            'observer',
        ];

        foreach ($actor_types as $actor_type) {
            $actor_type_value = constant(CommonITILActor::class . '::' . strtoupper($actor_type));

            // List actors from all input keys
            $actors = [];
            foreach ($actor_itemtypes as $actor_itemtype) {
                $actor_fkey = getForeignKeyFieldForItemType($actor_itemtype);

                $actors_id_input_key      = sprintf('_%s_%s', $actor_fkey, $actor_type);
                $actors_notif_input_key   = sprintf('%s_notif', $actors_id_input_key);
                $actors_id_add_input_key  = $actor_itemtype === User::class
                    ? sprintf('_additional_%ss', $actor_type)
                    : sprintf('_additional_%ss_%ss', strtolower($actor_itemtype), $actor_type);

                $get_unique_key = function (array $actor) use ($actors_id_input_key): string {
                    // Use alternative_email in value key for "email" actors
                    return sprintf('%s_%s', $actors_id_input_key, $actor['items_id'] ?: $actor['alternative_email'] ?? '');
                };

                if (array_key_exists($actors_id_input_key, $this->input)) {
                    if (is_array($this->input[$actors_id_input_key])) {
                        foreach ($this->input[$actors_id_input_key] as $actor_key => $actor_id) {
                            if (!is_numeric($actor_id)) {
                                trigger_error(
                                    sprintf(
                                        'Invalid value "%s" found for actor in "%s".',
                                        $actor_id,
                                        $actors_id_input_key
                                    ),
                                    E_USER_WARNING
                                );
                            }
                            $actor_id = (int)$actor_id;
                            $actor = [
                                'itemtype' => $actor_itemtype,
                                'items_id' => $actor_id,
                                'type'     => $actor_type_value,
                            ];
                            if ($actor_itemtype !== Group::class && array_key_exists($actors_notif_input_key, $this->input)) {
                                // Expected format
                                // '_users_id_requester_notif' => [
                                //     'use_notification'  => [1, 0],
                                //     'alternative_email' => ['user1@example.com', 'user2@example.com'],
                                // ]
                                $notification_params = $this->input[$actors_notif_input_key];
                                $unexpected_format = false;
                                if (
                                    !is_array($notification_params)
                                    || (
                                        !array_key_exists('use_notification', $notification_params)
                                        && !array_key_exists('alternative_email', $notification_params)
                                    )
                                ) {
                                    $unexpected_format = true;
                                    $notification_params = [];
                                }
                                foreach ($notification_params as $key => $values) {
                                    if (!is_array($values)) {
                                        $unexpected_format = true;
                                        continue;
                                    }
                                    if (is_array($values) && array_key_exists($actor_key, $values)) {
                                        $actor[$key] = $values[$actor_key];
                                    }
                                }
                                if ($unexpected_format) {
                                    trigger_error(
                                        sprintf('Unexpected format found in "%s".', $actors_notif_input_key),
                                        E_USER_WARNING
                                    );
                                }
                            }
                            $actors[$get_unique_key($actor)] = $actor;
                        }
                    } elseif (is_numeric($this->input[$actors_id_input_key])) {
                        $actor_id = (int)$this->input[$actors_id_input_key];
                        $actor = [
                            'itemtype' => $actor_itemtype,
                            'items_id' => $actor_id,
                            'type'     => $actor_type_value,
                        ];
                        if (array_key_exists($actors_notif_input_key, $this->input)) {
                            // Expected formats
                            //
                            // Value provided by Change::getDefaultValues()
                            // '_users_id_requester_notif' => [
                            //     'use_notification'  => 1,
                            //     'alternative_email' => 'user1@example.com',
                            // ]
                            //
                            // OR
                            //
                            // Value provided by Ticket::getDefaultValues()
                            // '_users_id_requester_notif' => [
                            //     'use_notification'  => [1, 0],
                            //     'alternative_email' => ['user1@example.com', 'user2@example.com'],
                            // ]
                            $notification_params = $this->input[$actors_notif_input_key];
                            if (
                                !is_array($notification_params)
                                || (
                                    !array_key_exists('use_notification', $notification_params)
                                    && !array_key_exists('alternative_email', $notification_params)
                                )
                            ) {
                                trigger_error(
                                    sprintf('Unexpected format found in "%s".', $actors_notif_input_key),
                                    E_USER_WARNING
                                );
                                $notification_params = [];
                            }
                            foreach ($notification_params as $key => $values) {
                                if (is_array($values) && array_key_exists(0, $values)) {
                                    $actor[$key] = $values[0];
                                } elseif (!is_array($values)) {
                                    $actor[$key] = $values;
                                }
                            }
                        }
                        $actors[$get_unique_key($actor)] = $actor;
                    } elseif ($this->input[$actors_id_input_key] !== '') {
                        trigger_error(
                            sprintf(
                                'Invalid value "%s" found for actor in "%s".',
                                $this->input[$actors_id_input_key],
                                $actors_id_input_key
                            ),
                            E_USER_WARNING
                        );
                    }
                }
                if (array_key_exists($actors_id_add_input_key, $this->input)) {
                    foreach ($this->input[$actors_id_add_input_key] as $actor) {
                        $actor_id = null;
                        if (is_array($actor) && array_key_exists($actor_fkey, $actor)) {
                            $actor_id = $actor[$actor_fkey];
                        } else {
                            $actor_id = $actor;
                        }
                        if (!is_numeric($actor_id)) {
                            trigger_error(
                                sprintf(
                                    'Invalid value "%s" found for additional actor in "%s".',
                                    var_export($actor_id, true),
                                    $actors_id_add_input_key
                                ),
                                E_USER_WARNING
                            );
                            continue;
                        }
                        $actor_id = (int)$actor_id;
                        $actor = [
                            'itemtype' => $actor_itemtype,
                            'items_id' => $actor_id,
                            'type'     => $actor_type_value,
                        ];
                        $unique_key = $get_unique_key($actor);
                        if (!array_key_exists($unique_key, $actors)) {
                            $actors[$unique_key] = $actor;
                        }
                    }
                }
            }

            // Search for added/updated actors
            $existings = $this->getActorsForType($actor_type_value);
            $added     = [];
            $updated   = [];

            foreach ($actors as $actor) {
                if (
                    $actor['items_id'] === 0
                    && (
                        ($actor['itemtype'] === User::class && empty($actor['alternative_email'] ?? ''))
                        || $actor['itemtype'] !== User::class
                    )
                ) {
                    // Empty values, probably provided by static::getDefaultValues()
                    continue;
                }

                $found = false;
                foreach ($existings as $existing) {
                    if (
                        $actor['itemtype'] === User::class
                        && $actor['items_id'] == 0
                        && $actor['itemtype'] == $existing['itemtype']
                    ) {
                        // "email" actor found
                        if ($actor['alternative_email'] == $existing['alternative_email']) {
                            $found = true;
                            break;
                        }
                        // Do not check for modifications on "email" actors (they should be deleted then re-added on email change)
                        continue;
                    }

                    if ($actor['itemtype'] != $existing['itemtype'] || $actor['items_id'] != $existing['items_id']) {
                        continue;
                    }
                    $found = true;

                    if ($actor['itemtype'] === Group::class) {
                        // Do not check for modifications on "group" actors (they do not have notification settings to update)
                        continue;
                    }

                    // check if modifications exists
                    if (
                        (
                            array_key_exists('use_notification', $actor)
                            && $actor['use_notification'] != $existing['use_notification']
                        )
                        || (
                            array_key_exists('alternative_email', $actor)
                            && $actor['alternative_email'] != $existing['alternative_email']
                        )
                    ) {
                        $updated[] = $actor + ['id' => $existing['id']];
                    }

                    break; // As actor is found, do not continue to list existings
                }

                if ($found === false) {
                    $added[] = $actor;
                }
            }

            // Add new actors
            foreach ($added as $actor) {
                $actor_obj = $this->getActorObjectForItem($actor['itemtype']);
                $actor_obj->add($common_actor_input + $actor + [
                    $actor_obj->getItilObjectForeignKey() => $this->fields['id'],
                    $actor_obj->getActorForeignKey()      => $actor['items_id'],
                ]);
                if (
                    $actor['type'] === CommonITILActor::ASSIGN
                    && (
                        (!isset($this->input['status']) && in_array($this->fields['status'], $this->getNewStatusArray()))
                        || (isset($this->input['status']) && in_array($this->input['status'], $this->getNewStatusArray()))
                    )
                    && in_array(self::ASSIGNED, array_keys($this->getAllStatusArray()))
                    && !$this->isStatusComputationBlocked($this->input)
                ) {
                    $self = new static();
                    $self->update(
                        [
                            'id'                              => $this->getID(),
                            'status'                          => self::ASSIGNED,
                            '_do_not_compute_takeintoaccount' => $this->isTakeIntoAccountComputationBlocked($this->input),
                            '_from_assignment'                => true
                        ]
                    );
                    $this->fields['status'] = $self->fields['status'];
                }
            }
            // Update existing actors
            foreach ($updated as $actor) {
                $actor_obj = $this->getActorObjectForItem($actor['itemtype']);
                $actor_obj->update($common_actor_input + $actor);
            }
        }

        // Process deleted actors
        foreach ($actor_types as $actor_type) {
            foreach ($actor_itemtypes as $actor_itemtype) {
                $actor_fkey = getForeignKeyFieldForItemType($actor_itemtype);
                $actors_deleted_input_key = sprintf('_%s_%s_deleted', $actor_fkey, $actor_type);

                $deleted = array_key_exists($actors_deleted_input_key, $this->input)
                    ? $this->input[$actors_deleted_input_key]
                    : [];
                foreach ($deleted as $actor) {
                    $actor_obj = $this->getActorObjectForItem($actor['itemtype']);
                    $actor_obj->delete(['id' => $actor['id']]);
                }
            }
        }

        // We just updated actors, clear any cached data
        $this->clearLazyLoadedActors();
    }


    protected function getActorObjectForItem(string $itemtype = ""): CommonITILActor
    {
        switch ($itemtype) {
            case 'User':
                $actor = new $this->userlinkclass();
                break;
            case 'Group':
                $actor = new $this->grouplinkclass();
                break;
            case 'Supplier':
                $actor = new $this->supplierlinkclass();
                break;
            default:
                throw new \RuntimeException('Unexpected actor type.');
                break;
        }
        return $actor;
    }


    /**
     * Fill the tech and the group from the category
     * @param array $input
     * @return array
     */
    protected function setTechAndGroupFromItilCategory($input)
    {
        $cat = new ITILCategory();
        $has_user_assigned  = $this->hasValidActorInInput($input, User::class, CommonITILActor::ASSIGN);
        $has_group_assigned = $this->hasValidActorInInput($input, Group::class, CommonITILActor::ASSIGN);
        if (
            $input['itilcategories_id'] > 0
            && (!$has_user_assigned || !$has_group_assigned)
            && $cat->getFromDB($input['itilcategories_id'])
        ) {
            if (!$has_user_assigned && $cat->fields['users_id'] > 0) {
                $input['_users_id_assign'] = $cat->fields['users_id'];
            }
            if (!$has_group_assigned && $cat->fields['groups_id'] > 0) {
                $input['_groups_id_assign'] = $cat->fields['groups_id'];
            }
        }

        return $input;
    }


    /**
     * Fill the tech and the group from the hardware
     * @param array $input
     * @return array
     */
    protected function setTechAndGroupFromHardware($input, $item)
    {
        if ($item != null) {
            // Auto assign tech from item
            $has_user_assigned  = $this->hasValidActorInInput($input, User::class, CommonITILActor::ASSIGN);
            if (!$has_user_assigned && $item->isField('users_id_tech') && $item->fields['users_id_tech'] > 0) {
                $input['_users_id_assign'] = $item->fields['users_id_tech'];
            }

            // Auto assign group from item
            $has_group_assigned = $this->hasValidActorInInput($input, Group::class, CommonITILActor::ASSIGN);
            if (!$has_group_assigned && $item->isField('groups_id_tech') && $item->fields['groups_id_tech'] > 0) {
                $input['_groups_id_assign'] = $item->fields['groups_id_tech'];
            }
        }

        return $input;
    }

    /**
     * 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
     *
     * @param array $input
     *
     * @return array
     */
    protected function assign(array $input)
    {
        // FIXME Deprecate this method in GLPI 10.1.
        if (!in_array(self::ASSIGNED, array_keys($this->getAllStatusArray()))) {
            return $input;
        }

        if (
            (
                $this->hasValidActorInInput($input, User::class, CommonITILActor::ASSIGN)
                || $this->hasValidActorInInput($input, Group::class, CommonITILActor::ASSIGN)
                || $this->hasValidActorInInput($input, Supplier::class, CommonITILActor::ASSIGN)
            )
            && (in_array($input['status'], $this->getNewStatusArray()))
            && !$this->isStatusComputationBlocked($input)
        ) {
            $input["status"] = self::ASSIGNED;
        }

        return $input;
    }

    /**
     * Check if input contains a valid actor for given itemtype / actortype.
     *
     * @param array $input
     * @param string $itemtype
     * @param string $actortype
     *
     * @return bool
     */
    private function hasValidActorInInput(array $input, string $itemtype, string $actortype): bool
    {
        $input_id_key = sprintf(
            '_%s_%s',
            getForeignKeyFieldForItemType($itemtype),
            self::getActorFieldNameType($actortype)
        );
        $input_notif_key = sprintf(
            '%s_notif',
            $input_id_key
        );

        $has_valid_actor = false;
        if (array_key_exists($input_id_key, $input)) {
            if (is_array($input[$input_id_key]) && !empty($input[$input_id_key])) {
                foreach ($input[$input_id_key] as $key => $actor_id) {
                    if (
                        // actor with valid ID
                        (int)$actor_id > 0
                        // or "email" actor
                        || (
                            $itemtype === User::class
                            && (int)$actor_id === 0
                            && array_key_exists($input_notif_key, $input)
                            && (bool)($input[$input_notif_key]['use_notification'][$key] ?? false) === true
                            && !empty($input[$input_notif_key]['alternative_email'][$key])
                        )
                    ) {
                        $has_valid_actor = true;
                        break;
                    }
                }
            } elseif (is_numeric($input[$input_id_key])) {
                $actor_id = (int)$input[$input_id_key];
                if (
                    // actor with valid ID
                    $actor_id > 0
                    // or "email" actor
                    || (
                        $itemtype === User::class
                        && $actor_id === 0
                        // Expected formats
                        //
                        // Value provided by Change::getDefaultValues()
                        // '_users_id_requester_notif' => [
                        //     'use_notification'  => 1,
                        //     'alternative_email' => 'user1@example.com',
                        // ]
                        //
                        // OR
                        //
                        // Value provided by Ticket::getDefaultValues()
                        // '_users_id_requester_notif' => [
                        //     'use_notification'  => [1, 0],
                        //     'alternative_email' => ['user1@example.com', 'user2@example.com'],
                        // ]
                        && array_key_exists($input_notif_key, $input)
                        && (
                            array_key_exists('use_notification', $input[$input_notif_key])
                            && (
                                (
                                    is_array($input[$input_notif_key]['use_notification'])
                                    && (bool)($input[$input_notif_key]['use_notification'][0] ?? false) === true
                                )
                                || (bool)($input[$input_notif_key]['use_notification'] ?? false) === true
                            )
                        )
                        && (
                            array_key_exists('alternative_email', $input[$input_notif_key])
                            && (
                                (
                                    is_array($input[$input_notif_key]['alternative_email'])
                                    && !empty($input[$input_notif_key]['alternative_email'][0])
                                )
                                || !empty($input[$input_notif_key]['alternative_email'])
                            )
                        )
                    )
                ) {
                    $has_valid_actor = true;
                }
            }
        }

        return $has_valid_actor;
    }

    /**
     * Parameter class to be use for this item (user templates)
     * @return string class name
     */
    abstract public static function getContentTemplatesParametersClass(): string;

    public static function getDataToDisplayOnKanban($ID, $criteria = [])
    {
        global $DB;

        // List of items to return
        $items = [];

        // Common variables
        $self_item = new static();
        $can_update = static::canUpdate();
        $itemtype = static::class;
        $self_fk_field = static::getForeignKeyField();
        $linked_actors = [];

        // Build base query
        $WHERE = ['is_deleted' => 0];
        $WHERE += $criteria;
        $WHERE += getEntitiesRestrictCriteria();
        // visibility check hack so we don't have to load the complete DB info for every item
        $visiblity_criteria = Search::addDefaultWhere(static::class);
        if (!empty($visiblity_criteria)) {
            $WHERE[] = new QueryExpression(Search::addDefaultWhere(static::class));
        }
        $base_common_itil_query = [
            'SELECT' => [static::getTableField('id')],
            'FROM'   => static::getTable(),
            'WHERE'  => $WHERE
        ];

        // Add JOIN
        $linked_tables = [];
        $default_joint = Search::addDefaultJoin(
            $itemtype,
            getTableForItemType($itemtype),
            $linked_tables, // Passed by reference, must be a defined variable even if empty
        );
        if (!empty($default_joint)) {
            $base_common_itil_query['LEFT JOIN'] = [new QueryExpression($default_joint)];
        }

        // Load common_itil
        $common_itil_query = $base_common_itil_query;
        $common_itil_query['SELECT'][] = static::getTableField('name');
        $common_itil_query['SELECT'][] = static::getTableField('status');
        $common_itil_query['SELECT'][] = static::getTableField('itilcategories_id');
        $common_itil_query['SELECT'][] = static::getTableField('content');
        $common_itil_iterator = $DB->request($common_itil_query);

        // Load actors (users)
        $user_link_class = $self_item->userlinkclass;
        if (is_a($user_link_class, CommonITILActor::class, true)) {
            $user_link_table = getTableForItemType($user_link_class);
            $linked_user_iterator = $DB->request([
                'SELECT' => [
                    $user_link_class::getTableField($self_fk_field),
                    $user_link_class::getTableField('users_id'),
                    User::getTableField('firstname'),
                    User::getTableField('realname'),
                    User::getTableField('name'),
                ],
                'FROM'   => $user_link_table,
                'LEFT JOIN' => [
                    User::getTable() => [
                        'ON'  => [
                            $user_link_table => 'users_id',
                            User::getTable() => 'id'
                        ]
                    ]
                ],
                'WHERE'  => [
                    'type' => CommonITILActor::ASSIGN,
                    $self_fk_field => new QuerySubQuery($base_common_itil_query)
                ]
            ]);
            foreach ($linked_user_iterator as $linked_user_row) {
                $common_itil_id = $linked_user_row[$self_fk_field];

                // Init array
                if (!isset($linked_actors[$common_itil_id])) {
                    $linked_actors[$common_itil_id] = [];
                }

                // Push users
                $linked_actors[$common_itil_id][] = [
                    'itemtype'  => User::getType(),
                    'id'        => $linked_user_row['users_id'],
                    'firstname' => $linked_user_row['firstname'],
                    'realname'  => $linked_user_row['realname'],
                    'name'      => formatUserName(
                        $linked_user_row['users_id'],
                        $linked_user_row['name'],
                        $linked_user_row['realname'],
                        $linked_user_row['firstname']
                    ),
                ];
            }
        }

        // Load actors (groups)
        $group_link_class = $self_item->grouplinkclass;
        if (is_a($group_link_class, CommonITILActor::class, true)) {
            $group_link_table = getTableForItemType($group_link_class);
            $linked_group_iterator = $DB->request([
                'SELECT' => [
                    $group_link_class::getTableField($self_fk_field),
                    $group_link_class::getTableField('groups_id'),
                    Group::getTableField('name'),
                ],
                'FROM'   => $group_link_table,
                'LEFT JOIN' => [
                    Group::getTable() => [
                        'ON'  => [
                            $group_link_table => 'groups_id',
                            Group::getTable() => 'id'
                        ]
                    ]
                ],
                'WHERE'  => [
                    'type' => CommonITILActor::ASSIGN,
                    $self_fk_field => new QuerySubQuery($base_common_itil_query)
                ]
            ]);
            foreach ($linked_group_iterator as $linked_group_row) {
                $common_itil_id = $linked_group_row[$self_fk_field];

                // Init array
                if (!isset($linked_actors[$common_itil_id])) {
                    $linked_actors[$common_itil_id] = [];
                }

                // Push groups
                $linked_actors[$common_itil_id][] = [
                    'itemtype' => Group::getType(),
                    'id'       => $linked_group_row['groups_id'],
                    'name'     => $linked_group_row['name'],
                ];
            }
        }

        // Load actors (supplier)
        $supplier_link_class = $self_item->supplierlinkclass;
        if (is_a($supplier_link_class, CommonITILActor::class, true)) {
            $suplier_link_table = getTableForItemType($supplier_link_class);
            $linked_supplier_iterator = $DB->request([
                'SELECT' => [
                    $supplier_link_class::getTableField($self_fk_field),
                    $supplier_link_class::getTableField('suppliers_id'),
                    Supplier::getTableField('name'),
                ],
                'FROM'   => $suplier_link_table,
                'LEFT JOIN' => [
                    Supplier::getTable() => [
                        'ON'  => [
                            $suplier_link_table => 'suppliers_id',
                            Supplier::getTable() => 'id'
                        ]
                    ]
                ],
                'WHERE'  => [
                    'type' => CommonITILActor::ASSIGN,
                    $self_fk_field => new QuerySubQuery($base_common_itil_query)
                ]
            ]);
            foreach ($linked_supplier_iterator as $linked_supplier_row) {
                $common_itil_id = $linked_supplier_row[$self_fk_field];

                // Init array
                if (!isset($linked_actors[$common_itil_id])) {
                    $linked_actors[$common_itil_id] = [];
                }

                // Push groups
                $linked_actors[$common_itil_id][] = [
                    'itemtype' => Supplier::getType(),
                    'id'       => $linked_supplier_row['suppliers_id'],
                    'name'     => $linked_supplier_row['name'],
                ];
            }
        }

        // Load linked tickets (only for tickets)
        if (static::class === Ticket::class) {
            $linked_tickets = [];
            $linked_tickets_iterator = $DB->request(new \QueryUnion([
                // Get parents tickets
                [
                    'SELECT' => [
                        Ticket_Ticket::getTableField('tickets_id_1 AS tickets_id_parent'),
                        Ticket_Ticket::getTableField('tickets_id_2 AS tickets_id_child'),
                        Ticket::getTableField('status'),
                    ],
                    'FROM' => Ticket_Ticket::getTable(),
                    'LEFT JOIN' => [
                        Ticket::getTable() => [
                            'ON'  => [
                                Ticket_Ticket::getTable() => 'tickets_id_2',
                                Ticket::getTable() => 'id'
                            ]
                        ]
                    ],
                    'WHERE'  => [
                        'link' => Ticket_Ticket::PARENT_OF,
                        'tickets_id_1' => new QuerySubQuery($base_common_itil_query),
                    ]
                ],
                // Get children tickets
                [
                    'SELECT' => [
                        Ticket_Ticket::getTableField('tickets_id_1 AS tickets_id_child'),
                        Ticket_Ticket::getTableField('tickets_id_2 AS tickets_id_parent'),
                        Ticket::getTableField('status'),
                    ],
                    'FROM' => Ticket_Ticket::getTable(),
                    'LEFT JOIN' => [
                        Ticket::getTable() => [
                            'ON'  => [
                                Ticket_Ticket::getTable() => 'tickets_id_1',
                                Ticket::getTable() => 'id'
                            ]
                        ]
                    ],
                    'WHERE'  => [
                        'link' => Ticket_Ticket::SON_OF,
                        'tickets_id_2' => new QuerySubQuery($base_common_itil_query),
                    ]
                ]
            ]));

            foreach ($linked_tickets_iterator as $linked_ticket_row) {
                $tickets_id_parent = $linked_ticket_row['tickets_id_parent'];

                // Init array
                if (!isset($linked_tickets[$tickets_id_parent])) {
                    $linked_tickets[$tickets_id_parent] = [];
                }

                // Push links
                $linked_tickets[$tickets_id_parent][] = [
                    'tickets_id' => $linked_ticket_row['tickets_id_child'],
                    'status'     => $linked_ticket_row['status'],
                ];
            }
        }

        foreach ($common_itil_iterator as $data) {
            $data = [
                'id'        => $data['id'],
                'name'      => $data['name'],
                'category'  => $data['itilcategories_id'],
                'content'   => $data['content'],
                'status'    => $data['status'],
                '_itemtype' => $itemtype,
                '_team'     => $linked_actors[$data['id']] ?? [],
                // Only use global update right here because checking item right
                // is too expensive (need to load full item just to check right)
                '_readonly' => !$can_update,
            ];

            if (static::class === Ticket::class) {
                $data['_steps'] = $linked_tickets[$data['id']] ?? [];
            }

            $items[$data['id']] = $data;
        }

        return $items;
    }

    public static function getKanbanColumns($ID, $column_field = null, $column_ids = [], $get_default = false)
    {
        if (!in_array($column_field, ['status'])) {
            return [];
        }

        $columns = [];
        $criteria = [];
        if (empty($column_ids)) {
            return [];
        }
        // Fill columns with empty arrays for each column id to avoid missing columns in the kanban
        foreach ($column_ids as $column_id) {
            $columns[$column_id] = [];
        }
        // Never try getting cards in drop-only columns
        $columns_defined = self::getAllKanbanColumns('status');
        $statuses_from_db = array_filter($column_ids, static function ($id) use ($columns_defined) {
            $id = (int) $id;
            return isset($columns_defined[$id]) && (!isset($columns_defined[$id]['drop_only']) || $columns_defined[$id]['drop_only'] === false);
        });
        if (count($statuses_from_db)) {
            $criteria = [
                static::getTableField('status') => $statuses_from_db
            ];
        }

        // Avoid fetching everything when nothing is needed
        if (isset($criteria[static::getTableField('status')])) {
            $items = self::getDataToDisplayOnKanban($ID, $criteria);
        } else {
            $items = [];
        }


        $extracolumns = self::getAllKanbanColumns($column_field, $column_ids, $get_default);
        foreach ($extracolumns as $column_id => $column) {
            $columns[$column_id] = $column;
        }

        foreach ($items as $item) {
            if (!array_key_exists($item[$column_field], $columns)) {
                continue;
            }
            $itemtype = $item['_itemtype'];
            $card = [
                'id'              => "{$itemtype}-{$item['id']}",
                'title'           => '<span class="pointer">' . $item['name'] . '</span>',
                'title_tooltip'   => Html::resume_text(RichText::getTextFromHtml($item['content'], false, true), 100),
                'is_deleted'      => $item['is_deleted'] ?? false,
            ];

            $content = "<div class='kanban-plugin-content'>";
            $plugin_content_pre = Plugin::doHookFunction('pre_kanban_content', [
                'itemtype' => $itemtype,
                'items_id' => $item['id'],
            ]);
            if (!empty($plugin_content_pre['content'])) {
                $content .= $plugin_content_pre['content'];
            }
            $content .= "</div>";
           // Core content
            $content .= "<div class='kanban-core-content'>";
            if (isset($item['_steps']) && count($item['_steps'])) {
                $done = count(array_filter($item['_steps'], static function ($l) {
                    return in_array($l['status'], static::getClosedStatusArray());
                }));
                $total = count($item['_steps']);
                $content .= "<div class='flex-break'></div>";
                $content .= sprintf(__('%s / %s tasks complete'), $done, $total);
            }
            $content .= "<div class='flex-break'></div>";

            $content .= "</div>";
            $content .= "<div class='kanban-plugin-content'>";
            $plugin_content_post = Plugin::doHookFunction('post_kanban_content', [
                'itemtype' => $itemtype,
                'items_id' => $item['id'],
            ]);
            if (!empty($plugin_content_post['content'])) {
                $content .= $plugin_content_post['content'];
            }
            $content .= "</div>";

            $card['content'] = $content;
            $card['_team'] = $item['_team'];
            $card['_readonly'] = $item['_readonly'];
            $card['_form_link'] = $itemtype::getFormUrlWithID($item['id']);
            $card['_metadata'] = [];
            $metadata_values = ['name', 'content'];
            foreach ($metadata_values as $metadata_value) {
                if (isset($item[$metadata_value])) {
                    $card['_metadata'][$metadata_value] = $item[$metadata_value];
                }
            }
            if (isset($card['_metadata']['content']) && is_string($card['_metadata']['content'])) {
                $card['_metadata']['content'] = Glpi\RichText\RichText::getTextFromHtml($card['_metadata']['content'], false, true);
            } else {
                $card['_metadata']['content'] = '';
            }
            $card['_metadata']['category'] = $item['category'];
            $card['_metadata'] = Plugin::doHookFunction(Hooks::KANBAN_ITEM_METADATA, [
                'itemtype' => $itemtype,
                'items_id' => $item['id'],
                'metadata' => $card['_metadata']
            ])['metadata'];
            $columns[$item[$column_field]]['items'][] = $card;
        }

        $category_ids = [];
        foreach ($columns as $column_id => $column) {
            if (
                $column_id !== 0 && !in_array($column_id, $column_ids) &&
                (!isset($column['items']) || !count($column['items']))
            ) {
                // If no specific columns were asked for, drop empty columns.
                // If specific columns were asked for, such as when loading a user's Kanban view, we must preserve them.
                // We always preserve the 'No Status' column.
                unset($columns[$column_id]);
            } else if (isset($column['items'])) {
                foreach ($column['items'] as $item) {
                    if (isset($item['_metadata']['category'])) {
                        $category_ids[] = $item['_metadata']['category'];
                    }
                }
            }
        }
        $category_ids = array_filter(array_unique($category_ids), static function ($id) {
            return $id > 0;
        });

        $categories = [];
        if (!empty($category_ids)) {
            global $DB;

            $cat_table = ITILCategory::getTable();
            $trans_table = DropdownTranslation::getTable();
            $name_select = new QueryExpression('IFNULL(' . $DB::quoteName("$trans_table.value") . ',' . $DB::quoteName("$cat_table.name") . ') AS ' . $DB::quoteName('name'));
            $it = $DB->request([
                'SELECT' => ["$cat_table.id", $name_select],
                'FROM' => $cat_table,
                'LEFT JOIN' => [
                    $trans_table => [
                        'ON' => [
                            $trans_table => 'items_id',
                            $cat_table => 'id',
                            [
                                'AND' => [
                                    $trans_table . '.itemtype' => ITILCategory::getType(),
                                    $trans_table . '.field' => 'name',
                                    $trans_table . '.language' => $_SESSION['glpilanguage']
                                ]
                            ]
                        ]
                    ]
                ],
                'WHERE' => ["$cat_table.id" => $category_ids]
            ]);
            foreach ($it as $row) {
                $categories[$row['id']] = $row['name'];
            }
            // Add uncategorized category
            $categories[0] = '';
        }

        // Replace category ids with category names in items metadata
        foreach ($columns as &$column) {
            foreach (($column['items'] ?? []) as &$item) {
                $item['_metadata']['category'] = $categories[$item['_metadata']['category']] ?? '';
            }
        }

        return $columns;
    }

    public static function showKanban($ID)
    {
        $itilitem = new static();

        if (($ID > 0 && !$itilitem->getFromDB($ID)) || !$itilitem::canView()) {
            return false;
        }

        $team_role_ids = static::getTeamRoles();
        $team_roles = [];

        foreach ($team_role_ids as $role_id) {
            $team_roles[$role_id] = static::getTeamRoleName($role_id);
        }

        $supported_itemtypes = [];
        $supported_itemtypes[static::class] = [
            'name' => static::getTypeName(1),
            'icon' => static::getIcon(),
            'fields' => [
                'name'   => [
                    'placeholder'  => __('Name')
                ],
                'content'   => [
                    'placeholder'  => __('Content'),
                    'type'         => 'textarea'
                ],
                'users_id'  => [
                    'type'         => 'hidden',
                    'value'        => $_SESSION['glpiID']
                ]
            ],
            'team_itemtypes'  => static::getTeamItemtypes(),
            'team_roles'      => $team_roles,
            'allow_create'    => static::canCreate(),
        ];
        $column_field = [
            'id' => 'status',
            'extra_fields' => []
        ];

        $itemtype = static::class;
        $rights = [
            'create_item'                    => self::canCreate(),
            'delete_item'                    => self::canDelete(),
            'create_column'                  => false,
            'modify_view'                    => true,
            'order_card'                     => true,
            'create_card_limited_columns'    => []
        ];

        TemplateRenderer::getInstance()->display('components/kanban/kanban.html.twig', [
            'kanban_id'                   => 'kanban',
            'rights'                      => $rights,
            'supported_itemtypes'         => $supported_itemtypes,
            'max_team_images'             => 3,
            'column_field'                => $column_field,
            'item'                        => [
                'itemtype'  => $itemtype,
                'items_id'  => $ID
            ],
            'supported_filters'           => [
                'title' => [
                    'description' => _x('filters', 'The title of the item'),
                    'supported_prefixes' => ['!', '#'] // Support exclusions and regex
                ],
                'type' => [
                    'description' => _x('filters', 'The type of the item'),
                    'supported_prefixes' => ['!']
                ],
                'category' => [
                    'description' => _x('filters', 'The category of the item'),
                    'supported_prefixes' => ['!', '#']
                ],
                'content' => [
                    'description' => _x('filters', 'The content of the item'),
                    'supported_prefixes' => ['!', '#'] // Support exclusions and regex
                ],
                'team' => [
                    'description' => _x('filters', 'A team member for the item'),
                    'supported_prefixes' => ['!']
                ],
                'user' => [
                    'description' => _x('filters', 'A user in the team of the item'),
                    'supported_prefixes' => ['!']
                ],
                'group' => [
                    'description' => _x('filters', 'A group in the team of the item'),
                    'supported_prefixes' => ['!']
                ],
                'supplier' => [
                    'description' => _x('filters', 'A supplier in the team of the item'),
                    'supported_prefixes' => ['!']
                ],
            ] + self::getKanbanPluginFilters(static::getType()),
        ]);
    }

    public static function getAllForKanban($active = true, $current_id = -1)
    {
       // ITIL items only have a global view
        $items = [
            -1 => __('Global')
        ];
        return $items;
    }

    public static function getAllKanbanColumns($column_field = null, $column_ids = [], $get_default = false)
    {

        if ($column_field === null) {
            $column_field = 'status';
        }
        $columns = [];
        if ($column_field === null || $column_field === 'status') {
            $all_statuses = static::getAllStatusArray();
            foreach ($all_statuses as $status_id => $status) {
                $columns['status'][$status_id] = [
                    'name'         => $status,
                    'color_class'  => 'itilstatus ' . static::getStatusKey($status_id),
                    'drop_only'    => (int) $status_id === self::CLOSED
                ];
            }
        } else {
            return [];
        }
        return $columns[$column_field];
    }

    public static function getTeamRoles(): array
    {
        return [
            Team::ROLE_REQUESTER,
            Team::ROLE_OBSERVER,
            Team::ROLE_ASSIGNED,
        ];
    }

    public static function getTeamRoleName(int $role, int $nb = 1): string
    {
        switch ($role) {
            case Team::ROLE_REQUESTER:
                return _n('Requester', 'Requesters', $nb);
            case Team::ROLE_OBSERVER:
                return _n('Watcher', 'Watchers', $nb);
            case Team::ROLE_ASSIGNED:
                return _n('Assignee', 'Assignees', $nb);
        }
        return '';
    }

    public static function getTeamItemtypes(): array
    {
        return ['User', 'Group', 'Supplier'];
    }

    public function addTeamMember(string $itemtype, int $items_id, array $params = []): bool
    {
        $role = $params['role'] ?? CommonITILActor::ASSIGN;

        /** @var CommonDBTM $link_class */
        $link_class = null;
        switch ($itemtype) {
            case 'User':
                $link_class = $this->userlinkclass;
                break;
            case 'Group':
                $link_class = $this->grouplinkclass;
                break;
            case 'Supplier':
                $link_class = $this->supplierlinkclass;
                break;
        }

        if ($link_class === null) {
            return false;
        }

        $link_item = new $link_class();
        /** @var CommonDBTM $itemtype */
        $result = $link_item->add([
            static::getForeignKeyField()     => $this->getID(),
            $itemtype::getForeignKeyField()  => $items_id,
            'type'                           => $role
        ]);
        return (bool) $result;
    }

    public function deleteTeamMember(string $itemtype, int $items_id, array $params = []): bool
    {
        $role = $params['role'] ?? CommonITILActor::ASSIGN;

        /** @var CommonDBTM $link_class */
        $link_class = null;
        switch ($itemtype) {
            case 'User':
                $link_class = $this->userlinkclass;
                break;
            case 'Group':
                $link_class = $this->grouplinkclass;
                break;
            case 'Supplier':
                $link_class = $this->supplierlinkclass;
                break;
        }

        if ($link_class === null) {
            return false;
        }

        $link_item = new $link_class();
        /** @var CommonDBTM $itemtype */
        $result = $link_item->deleteByCriteria([
            static::getForeignKeyField()     => $this->getID(),
            $itemtype::getForeignKeyField()  => $items_id,
            'type'                           => $role
        ]);
        return (bool) $result;
    }

    public function getTeam(): array
    {
        global $DB;

        $team = [];

        $team_itemtypes = static::getTeamItemtypes();

        /** @var CommonDBTM $itemtype */
        foreach ($team_itemtypes as $itemtype) {
            /** @var CommonDBTM $link_class */
            $link_class = null;
            switch ($itemtype) {
                case 'User':
                    $link_class = $this->userlinkclass;
                    break;
                case 'Group':
                    $link_class = $this->grouplinkclass;
                    break;
                case 'Supplier':
                    $link_class = $this->supplierlinkclass;
                    break;
            }

            if ($link_class === null) {
                continue;
            }

            $select = [];
            if ($itemtype === 'User') {
                $select = [$link_class::getTable() . '.' . $itemtype::getForeignKeyField(), 'type', 'name', 'realname', 'firstname'];
            } else {
                $select = [
                    $link_class::getTable() . '.' . $itemtype::getForeignKeyField(), 'type', 'name',
                    new QueryExpression('NULL as realname'),
                    new QueryExpression('NULL as firstname')
                ];
            }

            $it = $DB->request([
                'SELECT' => $select,
                'FROM'   => $link_class::getTable(),
                'WHERE'  => [static::getForeignKeyField() => $this->getID()],
                'LEFT JOIN' => [
                    $itemtype::getTable() => [
                        'ON'  => [
                            $itemtype::getTable()   => 'id',
                            $link_class::getTable() => $itemtype::getForeignKeyField()
                        ]
                    ]
                ]
            ]);
            foreach ($it as $data) {
                 $items_id = $data[$itemtype::getForeignKeyField()];
                 $member = [
                     'itemtype'     => $itemtype,
                     'items_id'     => $items_id,
                     'role'         => $data['type'],
                     'name'         => $data['name'],
                     'realname'     => $data['realname'],
                     'firstname'    => $data['firstname'],
                     'display_name' => formatUserName($items_id, $data['name'], $data['realname'], $data['firstname'])
                 ];
                 $team[] = $member;
            }
        }

        return $team;
    }

    public function getTimelineStats(): array
    {
        global $DB;

        $stats = [
            'total_duration' => 0,
            'percent_done'   => 0,
        ];

       // compute itilobject duration
        $task_class  = $this->getType() . "Task";
        $task_table  = getTableForItemType($task_class);
        $foreign_key = $this->getForeignKeyField();

        $criteria = [
            'SELECT' => ['SUM' => 'actiontime AS actiontime'],
            'FROM'   => $task_table,
            'WHERE'  => [$foreign_key => $this->fields['id']]
        ];

        $req = $DB->request($criteria);
        if ($row = $req->current()) {
            $stats['total_duration'] = $row['actiontime'];
        }

       // compute itilobject percent done
        $criteria    = [
            $foreign_key => $this->fields['id'],
            'state'     => [Planning::TODO, Planning::DONE]
        ];
        $total_tasks = countElementsInTable($task_table, $criteria);
        $criteria    = [
            $foreign_key => $this->fields['id'],
            'state'      => Planning::DONE,
        ];
        $done_tasks = countElementsInTable($task_table, $criteria);
        if ($total_tasks != 0) {
            $stats['percent_done'] = floor(100 * $done_tasks / $total_tasks);
        }

        return $stats;
    }

    /**
     * Returns an instance of validation class, if it exists.
     *
     * @return CommonITILValidation|null
     */
    public static function getValidationClassInstance(): ?CommonITILValidation
    {
        $validation_class = static::class . 'Validation';
        if (class_exists($validation_class)) {
            return new $validation_class();
        }
        return null;
    }

    /**
     * Instead of "{itemtype} - {name}" we will use {itemtype} ({id}) - {name}
     * as the ID of a Ticket/Change/Problem is an important information
     *
     * @return string
     */
    public function getBrowserTabName(): string
    {
        return sprintf(
            __('%1$s (#%2$s) - %3$s'),
            static::getTypeName(1),
            $this->getID(),
            $this->getHeaderName()
        );
    }

    /**
     * Transfer "_actors" input (introduced in 10.0.0) into historical input keys.
     *
     * @param array $input
     *
     * @return array
     */
    protected function transformActorsInput(array $input): array
    {
        // Reload actors to be able to identify deleted users.
        if (!$this->isNewItem()) {
            $this->loadActors();
        }

        if (
            array_key_exists('_actors', $input)
            && is_array($input['_actors'])
            && count($input['_actors'])
        ) {
            foreach (['requester', 'observer', 'assign'] as $actor_type) {
                $actor_type_value = constant(CommonITILActor::class . '::' . strtoupper($actor_type));
                if ($actor_type_value === CommonITILActor::ASSIGN && !$this->canAssign()) {
                    continue;
                }
                if ($actor_type_value !== CommonITILActor::ASSIGN && !$this->isNewItem() && !$this->canUpdateItem()) {
                    continue;
                }

                $get_input_key = function (string $actor_itemtype, string $actor_type): string {
                    return sprintf(
                        '_%s_%s',
                        getForeignKeyFieldForItemType($actor_itemtype),
                        $actor_type
                    );
                };

                // Normalize all keys.
                foreach ([User::class, Group::class, Supplier::class] as $actor_itemtype) {
                    $input_key = $get_input_key($actor_itemtype, $actor_type);
                    $notif_key = sprintf('%s_notif', $input_key);

                    if (!array_key_exists($input_key, $input) || !is_array($input[$input_key])) {
                        $input[$input_key] = !empty($input[$input_key]) ? [$input[$input_key]] : [];
                    }

                    if ($actor_itemtype !== Group::class) {
                        if (!array_key_exists($notif_key, $input) || !is_array($input[$notif_key])) {
                            $input[$notif_key] = [
                                'use_notification'  => [],
                                'alternative_email' => [],
                            ];
                        }
                        foreach (['use_notification', 'alternative_email'] as $param_key) {
                            if (
                                !array_key_exists($param_key, $input[$notif_key])
                                || $input[$notif_key][$param_key] === ''
                            ) {
                                $input[$notif_key][$param_key] = [];
                            } elseif (!is_array($input[$notif_key][$param_key])) {
                                $input[$notif_key][$param_key] = [$input[$notif_key][$param_key]];
                            }
                        }
                    }
                    $input[sprintf('%s_deleted', $input_key)] = [];
                }

                $actors = array_key_exists($actor_type, $input['_actors']) && is_array($input['_actors'][$actor_type])
                    ? $input['_actors'][$actor_type]
                    : [];

                // Extract actors from new actors list
                foreach ($actors as $actor) {
                    $input_key = $get_input_key($actor['itemtype'], $actor_type);
                    $notif_key = sprintf('%s_notif', $input_key);

                    // Use alternative_email in value key for "email" actors
                    $value_key = sprintf('_actors_%s', $actor['items_id'] ?: $actor['alternative_email'] ?? '');

                    if (array_key_exists($value_key, $input[$input_key])) {
                        continue;
                    }

                    $input[$input_key][$value_key] = $actor['items_id'];

                    if ($actor_itemtype !== Group::class && array_key_exists('use_notification', $actor)) {
                        $input[$notif_key]['use_notification'][$value_key]  = $actor['use_notification'];
                        $input[$notif_key]['alternative_email'][$value_key] = $actor['alternative_email'] ?? '';
                    }
                }

                // Identify deleted actors
                if (!$this->isNewItem()) {
                    $existings = $this->getActorsForType($actor_type_value);
                    foreach ($existings as $existing) {
                        $found = false;
                        foreach ($actors as $actor) {
                            if (
                                (
                                    // "email" actor match
                                    $actor['itemtype'] === User::class
                                    && $actor['items_id'] == 0
                                    && $actor['itemtype'] == $existing['itemtype']
                                    && $actor['alternative_email'] == $existing['alternative_email']
                                )
                                || (
                                    // other actor match
                                    $actor['items_id'] != 0
                                    && $actor['itemtype'] == $existing['itemtype']
                                    && $actor['items_id'] == $existing['items_id']
                                )
                            ) {
                                $found = true;
                                break;
                            }
                        }
                        if ($found === false) {
                            $input_key = $get_input_key($existing['itemtype'], $actor_type);
                            $input[sprintf('%s_deleted', $input_key)][] = $existing;
                        }
                    }
                }
            }
            unset($input['_actors']);
        }

        return $input;
    }

    public function prepareInputForClone($input)
    {
        unset($input['actiontime']);
        return $input;
    }

    public static function getMessageReferenceEvent(string $event): ?string
    {
        // All actions should be attached to thread instanciated by `new` event
        return 'new';
    }
}
			
			


Thanks For 0xGh05T - DSRF14 - Mr.Dan07 - Leri01 - FxshX7 - AlkaExploiter - xLoveSyndrome'z - Acep Gans'z

JMDS TRACK – Just Another Diagnostics Lab Site

Home

JMDS TRACK Cameroon

Boost the productivity of your mobile ressources


Make An Appointment


Fleet management

  1. Reduce the operting cost and the unavailability of your vehicles
  2. reduce the fuel consumption of your fleet
  3. Improve the driving dehavior and safety of your drivers
  4. optimize the utilization rate of your equipment 
  5. protect your vehicle against theft
  6. Improve the quality of your customer service


Find out more

Assets management

  1. Track the roaming of your equipment
  2. Optimise the management of your assets on site and during transport
  3. Secure the transport of your goods
  4. Make your team responsible for preventing the loss of tools, equipment
  5. Take a real-time inventory of your equipment on site
  6. Easily find your mobile objects or equipment



Find out more



Find out more

Antitheft solutions

  1. Secure your vehicles and machinery and increase your chances of recovering them in the event of theft
  2. Protect your assets and reduce the costs associated with their loss
  3. Combine immobiliser and driver identification and limit the risk of theft
  4. Identify fuel theft and reduce costs
  5. Protect your goods and take no more risks
  6. Be alerted to abnormal events

Our Location

 Douala BP cité 

     and

Yaoundé Total Essos


Make An Appointment


Get Directions

682230363/ 677481892

What makes us different from others

  • young and dynamic team
  • call center 24/24 7/7
  • roaming throughout Africa
  • team of developers who can develop customer-specific solutions
  • diversity of services
  • reactive and prompt after-sales service when soliciting a customer or a malfunction
  • Free Maintenance and installation in the cities of Douala and Yaounde

https://youtu.be/xI1cz_Jh2x8

15+
years of experience in GPS system development, production and deployment.

15 Collaborators

More than 15 employees dedicated to the research and development of new applications and to customer care

5 000 Vehicles and mobile assets

5 000 vehicles and mobile assets under management, in Africa

Our Partners










Latest Case Studies

Our current projects 

5/5
Bon SAV , SATISFAIT DU TRAITEMENT DES REQUETES

M DIPITA CHRISTIAN
Logistic Safety Manager Road Safety Manager
5/5
La réactivité de JMDS est excellente
Nous restons satisfait dans l’ensemble des prestations relatives a la couverture de notre parc automobile

Hervé Frédéric NDENGUE
Chef Service Adjoint de la Sécurité Générale (CNPS)
5/5
L’APPLICATION EMIXIS est convivial A L’utilisation
BEIG-3 SARL
DIRECTOR GENERAL
5/5
Nevertheless I am delighted with the service
MR. BISSE BENJAMIN
CUSTOMER

Subsribe To Our Newsletter

Stay in touch with us to get latest news and special offers.



Address JMDS TRACK

Douala bp cité



and

YAOUNDE Total Essos

Call Us

+237682230363



Email Us


info@jmdstrack.cm