<?php

/**
 * ---------------------------------------------------------------------
 *
 * GLPI - Gestionnaire Libre de Parc Informatique
 *
 * http://glpi-project.org
 *
 * @copyright 2015-2026 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\Plugin\Hooks;
use Glpi\Toolbox\Sanitizer;

/**
 * Rule Class store all information about a GLPI rule :
 *   - description
 *   - criterias
 *   - actions
 **/
class Rule extends CommonDBTM
{
    use Glpi\Features\Clonable;

    public $dohistory             = true;

    // Specific ones
    ///Actions affected to this rule
    public $actions               = [];
    ///Criterias affected to this rule
    public $criterias             = [];
    /// Rules can be sorted ?
    public $can_sort              = false;
    // preview context ?
    protected $is_preview = false;
    /// field used to order rules
    public $orderby               = 'ranking';

    /// restrict matching to self::AND_MATCHING or self::OR_MATCHING : specify value to activate
    public $restrict_matching     = false;

    protected $rules_id_field     = 'rules_id';
    protected $ruleactionclass    = 'RuleAction';
    protected $rulecriteriaclass  = 'RuleCriteria';

    public $specific_parameters   = false;

    public $regex_results         = [];
    public $criterias_results     = [];

    public static $rightname             = 'config';

    public const RULE_NOT_IN_CACHE       = -1;
    public const RULE_WILDCARD           = '*';

    //Generic rules engine
    public const PATTERN_IS              = 0;
    public const PATTERN_IS_NOT          = 1;
    public const PATTERN_CONTAIN         = 2;
    public const PATTERN_NOT_CONTAIN     = 3;
    public const PATTERN_BEGIN           = 4;
    public const PATTERN_END             = 5;
    public const REGEX_MATCH             = 6;
    public const REGEX_NOT_MATCH         = 7;
    public const PATTERN_EXISTS          = 8;
    public const PATTERN_DOES_NOT_EXISTS = 9;
    public const PATTERN_FIND            = 10; // Global criteria
    public const PATTERN_UNDER           = 11;
    public const PATTERN_NOT_UNDER       = 12;
    public const PATTERN_IS_EMPTY        = 30; // Global criteria
    public const PATTERN_CIDR            = 333;
    public const PATTERN_NOT_CIDR        = 334;

    public const AND_MATCHING            = "AND";
    public const OR_MATCHING             = "OR";


    public function getCloneRelations(): array
    {
        return [
            $this->ruleactionclass,
            $this->rulecriteriaclass,
        ];
    }


    public static function getTable($classname = null)
    {
        return parent::getTable(__CLASS__);
    }


    public static function getTypeName($nb = 0)
    {
        return _n('Rule', 'Rules', $nb);
    }


    /**
     *  Get correct Rule object for specific rule
     *
     *  @since 0.84
     *
     *  @param integer $rules_id ID of the rule
     **/
    public static function getRuleObjectByID($rules_id)
    {

        $rule = new self();
        if ($rule->getFromDB($rules_id)) {
            if (class_exists($rule->fields['sub_type'])) {
                $realrule = new $rule->fields['sub_type']();
                return $realrule;
            }
        }
        return null;
    }

    /**
     *  Get condition array for rule. If empty array no condition used
     *  maybe overridden to define conditions using binary combination :
     *   example array(1 => Condition1,
     *                 2 => Condition2,
     *                 3 => Condition1&Condition2)
     *
     *  @since 0.85
     *
     *  @return array of conditions
     **/
    public static function getConditionsArray()
    {
        return [];
    }

    /**
     * Is this rule use condition
     *
     * @return boolean
     **/
    public function useConditions()
    {
        return (count($this->getConditionsArray()) > 0);
    }

    /**
     * Display a dropdown with all the rule conditions
     *
     * @since 0.85
     *
     * @param array $options array of parameters
     **/
    public static function dropdownConditions($options = [])
    {

        $p['name']      = 'condition';
        $p['value']     = 0;
        $p['display']   = true;
        $p['on_change'] = '';

        if (is_array($options) && count($options)) {
            foreach ($options as $key => $val) {
                $p[$key] = $val;
            }
        }
        $elements = static::getConditionsArray();
        if (count($elements)) {
            return Dropdown::showFromArray($p['name'], $elements, $p);
        }

        return false;
    }

    /**
     * Get rule condition type Name
     *
     * @param integer $value condition ID
     *
     * @return string
     **/
    public static function getConditionName($value)
    {

        $cond = static::getConditionsArray();

        if (isset($cond[$value])) {
            return $cond[$value];
        }

        return NOT_AVAILABLE;
    }

    public static function getMenuContent()
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        $menu = [];

        if (
            Session::haveRight("rule_ldap", READ)
            || Session::haveRight("rule_import", READ)
            || Session::haveRight("rule_location", READ)
            || Session::haveRight("rule_ticket", READ)
            || Session::haveRight("rule_softwarecategories", READ)
            || Session::haveRight("rule_mailcollector", READ)
        ) {
            $menu['rule']['title'] = static::getTypeName(Session::getPluralNumber());
            $menu['rule']['page']  = static::getSearchURL(false);
            $menu['rule']['icon']  = static::getIcon();

            foreach ($CFG_GLPI["rulecollections_types"] as $rulecollectionclass) {
                $rulecollection = new $rulecollectionclass();
                if ($rulecollection->canList()) {
                    $ruleclassname = $rulecollection->getRuleClassName();
                    $menu['rule']['options'][$rulecollection->menu_option]['title']
                              = $rulecollection->getRuleClass()->getTitle();
                    $menu['rule']['options'][$rulecollection->menu_option]['page']
                              = $ruleclassname::getSearchURL(false);
                    if ($rulecollection->canCreate()) {
                        $menu['rule']['options'][$rulecollection->menu_option]['links']['add']
                              = $ruleclassname::getFormURL(false);
                    }
                }
            }
        }

        if (
            Transfer::canView()
            && Session::isMultiEntitiesMode()
        ) {
            $menu['rule']['title'] = static::getTypeName(Session::getPluralNumber());
            $menu['rule']['page']  = static::getSearchURL(false);
            $menu['rule']['icon']  = static::getIcon();

            $menu['rule']['options']['transfer']['title']           = __('Transfer');
            $menu['rule']['options']['transfer']['page']            = "/front/transfer.php";
            $menu['rule']['options']['transfer']['links']['search'] = "/front/transfer.php";

            if (Session::haveRightsOr("transfer", [CREATE, UPDATE])) {
                $menu['rule']['options']['transfer']['links']['transfer_list']
                                                                 = "/front/transfer.action.php";
                $menu['rule']['options']['transfer']['links']['add'] = Transfer::getFormURL(false);
            }
        }

        if (
            Session::haveRight("rule_dictionnary_dropdown", READ)
            || Session::haveRight("rule_dictionnary_software", READ)
            || Session::haveRight("rule_dictionnary_printer", READ)
        ) {
            $menu['dictionnary']['title']    = _n('Dictionary', 'Dictionaries', Session::getPluralNumber());
            $menu['dictionnary']['shortcut'] = '';
            $menu['dictionnary']['page']     = '/front/dictionnary.php';
            $menu['dictionnary']['icon']     = static::getIcon();

            $menu['dictionnary']['options']['manufacturers']['title']
                           = _n('Manufacturer', 'Manufacturers', Session::getPluralNumber());
            $menu['dictionnary']['options']['manufacturers']['page']
                           = '/front/ruledictionnarymanufacturer.php';
            $menu['dictionnary']['options']['manufacturers']['links']['search']
                           = '/front/ruledictionnarymanufacturer.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['manufacturers']['links']['add']
                              = '/front/ruledictionnarymanufacturer.form.php';
            }

            $menu['dictionnary']['options']['software']['title']
                           = _n('Software', 'Software', Session::getPluralNumber());
            $menu['dictionnary']['options']['software']['page']
                           = '/front/ruledictionnarysoftware.php';
            $menu['dictionnary']['options']['software']['links']['search']
                           = '/front/ruledictionnarysoftware.php';

            if (RuleDictionnarySoftware::canCreate()) {
                $menu['dictionnary']['options']['software']['links']['add']
                              = '/front/ruledictionnarysoftware.form.php';
            }

            $menu['dictionnary']['options']['model.computer']['title']
                           = _n('Computer model', 'Computer models', Session::getPluralNumber());
            $menu['dictionnary']['options']['model.computer']['page']
                           = '/front/ruledictionnarycomputermodel.php';
            $menu['dictionnary']['options']['model.computer']['links']['search']
                           = '/front/ruledictionnarycomputermodel.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['model.computer']['links']['add']
                              = '/front/ruledictionnarycomputermodel.form.php';
            }

            $menu['dictionnary']['options']['model.monitor']['title']
                           = _n('Monitor model', 'Monitor models', Session::getPluralNumber());
            $menu['dictionnary']['options']['model.monitor']['page']
                           = '/front/ruledictionnarymonitormodel.php';
            $menu['dictionnary']['options']['model.monitor']['links']['search']
                           = '/front/ruledictionnarymonitormodel.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['model.monitor']['links']['add']
                              = '/front/ruledictionnarymonitormodel.form.php';
            }

            $menu['dictionnary']['options']['model.printer']['title']
                           = _n('Printer model', 'Printer models', Session::getPluralNumber());
            $menu['dictionnary']['options']['model.printer']['page']
                           = '/front/ruledictionnaryprintermodel.php';
            $menu['dictionnary']['options']['model.printer']['links']['search']
                           = '/front/ruledictionnaryprintermodel.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['model.printer']['links']['add']
                              = '/front/ruledictionnaryprintermodel.form.php';
            }

            $menu['dictionnary']['options']['model.peripheral']['title']
                           = _n('Peripheral model', 'Peripheral models', Session::getPluralNumber());
            $menu['dictionnary']['options']['model.peripheral']['page']
                           = '/front/ruledictionnaryperipheralmodel.php';
            $menu['dictionnary']['options']['model.peripheral']['links']['search']
                           = '/front/ruledictionnaryperipheralmodel.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['model.peripheral']['links']['add']
                              = '/front/ruledictionnaryperipheralmodel.form.php';
            }

            $menu['dictionnary']['options']['model.networking']['title']
                           = _n('Networking equipment model', 'Networking equipment models', Session::getPluralNumber());
            $menu['dictionnary']['options']['model.networking']['page']
                           = '/front/ruledictionnarynetworkequipmentmodel.php';
            $menu['dictionnary']['options']['model.networking']['links']['search']
                           = '/front/ruledictionnarynetworkequipmentmodel.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['model.networking']['links']['add']
                              = '/front/ruledictionnarynetworkequipmentmodel.form.php';
            }

            $menu['dictionnary']['options']['model.phone']['title']
                           = _n('Phone model', 'Phone models', Session::getPluralNumber());
            $menu['dictionnary']['options']['model.phone']['page']
                           = '/front/ruledictionnaryphonemodel.php';
            $menu['dictionnary']['options']['model.phone']['links']['search']
                           = '/front/ruledictionnaryphonemodel.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['model.phone']['links']['add']
                              = '/front/ruledictionnaryphonemodel.form.php';
            }

            $menu['dictionnary']['options']['type.computer']['title']
                           = _n('Computer type', 'Computer types', Session::getPluralNumber());
            $menu['dictionnary']['options']['type.computer']['page']
                           = '/front/ruledictionnarycomputertype.php';
            $menu['dictionnary']['options']['type.computer']['links']['search']
                           = '/front/ruledictionnarycomputertype.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['type.computer']['links']['add']
                              = '/front/ruledictionnarycomputertype.form.php';
            }

            $menu['dictionnary']['options']['type.monitor']['title']
                           = _n('Monitor type', 'Monitors types', Session::getPluralNumber());
            $menu['dictionnary']['options']['type.monitor']['page']
                           = '/front/ruledictionnarymonitortype.php';
            $menu['dictionnary']['options']['type.monitor']['links']['search']
                           = '/front/ruledictionnarymonitortype.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['type.monitor']['links']['add']
                              = '/front/ruledictionnarymonitortype.form.php';
            }

            $menu['dictionnary']['options']['type.printer']['title']
                           = _n('Printer type', 'Printer types', Session::getPluralNumber());
            $menu['dictionnary']['options']['type.printer']['page']
                           = '/front/ruledictionnaryprintertype.php';
            $menu['dictionnary']['options']['type.printer']['links']['search']
                           = '/front/ruledictionnaryprintertype.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['type.printer']['links']['add']
                              = '/front/ruledictionnaryprintertype.form.php';
            }

            $menu['dictionnary']['options']['type.peripheral']['title']
                           = _n('Peripheral type', 'Peripheral types', Session::getPluralNumber());
            $menu['dictionnary']['options']['type.peripheral']['page']
                           = '/front/ruledictionnaryperipheraltype.php';
            $menu['dictionnary']['options']['type.peripheral']['links']['search']
                           = '/front/ruledictionnaryperipheraltype.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['type.peripheral']['links']['add']
                              = '/front/ruledictionnaryperipheraltype.form.php';
            }

            $menu['dictionnary']['options']['type.networking']['title']
                           = _n('Networking equipment type', 'Networking equipment types', Session::getPluralNumber());
            $menu['dictionnary']['options']['type.networking']['page']
                           = '/front/ruledictionnarynetworkequipmenttype.php';
            $menu['dictionnary']['options']['type.networking']['links']['search']
                           = '/front/ruledictionnarynetworkequipmenttype.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['type.networking']['links']['add']
                              = '/front/ruledictionnarynetworkequipmenttype.form.php';
            }

            $menu['dictionnary']['options']['type.phone']['title']
                           = _n('Phone type', 'Phone types', Session::getPluralNumber());
            $menu['dictionnary']['options']['type.phone']['page']
                           = '/front/ruledictionnaryphonetype.php';
            $menu['dictionnary']['options']['type.phone']['links']['search']
                           = '/front/ruledictionnaryphonetype.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['type.phone']['links']['add']
                              = '/front/ruledictionnaryphonetype.form.php';
            }

            $menu['dictionnary']['options']['os']['title']
                           = OperatingSystem::getTypeName(1);
            $menu['dictionnary']['options']['os']['page']
                           = '/front/ruledictionnaryoperatingsystem.php';
            $menu['dictionnary']['options']['os']['links']['search']
                           = '/front/ruledictionnaryoperatingsystem.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['os']['links']['add']
                              = '/front/ruledictionnaryoperatingsystem.form.php';
            }

            $menu['dictionnary']['options']['os_sp']['title']
                           = OperatingSystemServicePack::getTypeName(1);
            $menu['dictionnary']['options']['os_sp']['page']
                           = '/front/ruledictionnaryoperatingsystemservicepack.php';
            $menu['dictionnary']['options']['os_sp']['links']['search']
                           = '/front/ruledictionnaryoperatingsystemservicepack.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['os_sp']['links']['add']
                              = '/front/ruledictionnaryoperatingsystemservicepack.form.php';
            }

            $menu['dictionnary']['options']['os_version']['title']
                           = OperatingSystemVersion::getTypeName(1);
            $menu['dictionnary']['options']['os_version']['page']
                           = '/front/ruledictionnaryoperatingsystemversion.php';
            $menu['dictionnary']['options']['os_version']['links']['search']
                           = '/front/ruledictionnaryoperatingsystemversion.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['os_version']['links']['add']
                              = '/front/ruledictionnaryoperatingsystemversion.form.php';
            }

            $menu['dictionnary']['options']['os_arch']['title']
                           = OperatingSystemArchitecture::getTypeName(1);
            $menu['dictionnary']['options']['os_arch']['page']
                           = '/front/ruledictionnaryoperatingsystemarchitecture.php';
            $menu['dictionnary']['options']['os_arch']['links']['search']
                           = '/front/ruledictionnaryoperatingsystemarchitecture.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['os_arch']['links']['add']
                              = '/front/ruledictionnaryoperatingsystemarchitecture.form.php';
            }

            $menu['dictionnary']['options']['os_edition']['title']
            = OperatingSystemEdition::getTypeName(1);
            $menu['dictionnary']['options']['os_edition']['page']
                        = '/front/ruledictionnaryoperatingsystemedition.php';
            $menu['dictionnary']['options']['os_edition']['links']['search']
                        = '/front/ruledictionnaryoperatingsystemedition.php';

            if (RuleDictionnaryDropdown::canCreate()) {
                $menu['dictionnary']['options']['os_edition']['links']['add']
                        = '/front/ruledictionnaryoperatingsystemedition.form.php';
            }

            $menu['dictionnary']['options']['printer']['title']
                           = _n('Printer', 'Printers', Session::getPluralNumber());
            $menu['dictionnary']['options']['printer']['page']
                           = '/front/ruledictionnaryprinter.php';
            $menu['dictionnary']['options']['printer']['links']['search']
                           = '/front/ruledictionnaryprinter.php';

            if (RuleDictionnaryPrinter::canCreate()) {
                $menu['dictionnary']['options']['printer']['links']['add']
                              = '/front/ruledictionnaryprinter.form.php';
            }
        }

        if (count($menu)) {
            $menu['is_multi_entries'] = true;
            return $menu;
        }

        return false;
    }


    /**
     * @phpstan-return class-string<RuleAction>
     */
    public function getRuleActionClass()
    {
        return $this->ruleactionclass;
    }


    /**
     * @phpstan-return class-string<RuleCriteria>
     */
    public function getRuleCriteriaClass()
    {
        return $this->rulecriteriaclass;
    }


    public function getRuleIdField()
    {
        return $this->rules_id_field;
    }


    public function isEntityAssign()
    {
        return false;
    }


    public function post_getEmpty()
    {
        $this->fields['is_active'] = 0;
    }


    /**
     * Get title used in rule
     *
     * @return string Title of the rule
     **/
    public function getTitle()
    {
        return __('Rules management');
    }


    /**
     * @since 0.84
     *
     * @return string
     **/
    public function getCollectionClassName()
    {
        $parent = static::class;
        do {
            $collection_class = $parent . 'Collection';
            $parent = get_parent_class($parent);
        } while ($parent !== 'CommonDBTM' && $parent !== false && !class_exists($collection_class));
        if ($collection_class === null) {
            throw new \LogicException(sprintf('Unable to find collection class for `%s`.', static::getType()));
        }
        return $collection_class;
    }


    public function getSpecificMassiveActions($checkitem = null)
    {

        $isadmin = static::canUpdate();
        $actions = parent::getSpecificMassiveActions($checkitem);

        if (!$this->isEntityAssign()) {
            unset($actions[MassiveAction::class . MassiveAction::CLASS_ACTION_SEPARATOR . 'add_transfer_list']);
        }
        $collectiontype = $this->getCollectionClassName();
        if ($collection = getItemForItemtype($collectiontype)) {
            if (
                $isadmin
                && ($collection->orderby == "ranking")
            ) {
                $actions[__CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'move_rule']
                = "<i class='fas fa-arrows-alt-v'></i>" .
                 __('Move');
            }
            $actions[__CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'export']
            = "<i class='fas fa-file-download'></i>" .
              _x('button', 'Export');
        }
        return $actions;
    }


    public static function showMassiveActionsSubForm(MassiveAction $ma)
    {

        switch ($ma->getAction()) {
            case 'move_rule':
                $input = $ma->getInput();
                $values = [
                    RuleCollection::MOVE_AFTER  => __('After'),
                    RuleCollection::MOVE_BEFORE => __('Before'),
                ];
                Dropdown::showFromArray('move_type', $values, ['width' => '20%']);

                if (isset($input['entity'])) {
                    $entity = $input['entity'];
                } else {
                    $entity = "";
                }

                if (isset($input['condition'])) {
                    $condition = $input['condition'];
                } else {
                    $condition = 0;
                }
                echo Html::hidden('rule_class_name', ['value' => $input['rule_class_name']]);

                Rule::dropdown([
                    'sub_type'        => $input['rule_class_name'],
                    'name'            => "ranking",
                    'condition'       => $condition,
                    'entity'          => $entity,
                    'width'           => '50%',
                    'order'           => 'ranking',
                ]);
                echo "<br><br><input type='submit' name='massiveaction' class='btn btn-primary' value='" .
                           _sx('button', 'Move') . "'>\n";
                return true;
        }
        return parent::showMassiveActionsSubForm($ma);
    }


    public static function processMassiveActionsForOneItemtype(
        MassiveAction $ma,
        CommonDBTM $item,
        array $ids
    ) {
        switch ($ma->getAction()) {
            case 'export':
                if (count($ids)) {
                    $_SESSION['exportitems'] = $ids;
                    $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_OK);
                    $ma->setRedirect('rule.backup.php?action=download&itemtype=' . $item->getType());
                }
                break;

            case 'move_rule':
                $input          = $ma->getInput();
                $collectionname = $input['rule_class_name'] . 'Collection';
                $rulecollection = new $collectionname();
                if ($rulecollection->canUpdate()) {
                    foreach ($ids as $id) {
                        if ($item->getFromDB($id)) {
                            if ($rulecollection->moveRule($id, $input['ranking'], $input['move_type'])) {
                                $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_KO);
                            $ma->addMessage($item->getErrorMessage(ERROR_NOT_FOUND));
                        }
                    }
                } else {
                    $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_NORIGHT);
                    $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
                }
                break;
        }
        parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
    }

    public function getForbiddenSingleMassiveActions()
    {
        $excluded = parent::getForbiddenSingleMassiveActions();
        $excluded[] = __CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'move_rule';
        return $excluded;
    }

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

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

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

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

        $tab[] = [
            'id'                 => '4',
            'table'              => $this->getTable(),
            'field'              => 'description',
            'name'               => __('Description'),
            'datatype'           => 'text',
        ];

        $tab[] = [
            'id'                 => '5',
            'table'              => $this->getTable(),
            'field'              => 'match',
            'name'               => __('Logical operator'),
            'datatype'           => 'specific',
            'massiveaction'      => false,
        ];

        $tab[] = [
            'id'                 => '8',
            'table'              => $this->getTable(),
            'field'              => 'is_active',
            'name'               => __('Active'),
            'datatype'           => 'bool',
        ];

        $tab[] = [
            'id'                 => '16',
            'table'              => $this->getTable(),
            'field'              => 'comment',
            'name'               => __('Comments'),
            'datatype'           => 'text',
        ];

        $tab[] = [
            'id'                 => '122',
            'table'              => $this->getTable(),
            'field'              => 'sub_type',
            'name'               => __('Subtype'),
            'datatype'           => 'text',
        ];

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

        $tab[] = [
            'id'                 => '86',
            'table'              => $this->getTable(),
            'field'              => 'is_recursive',
            'name'               => __('Child entities'),
            'datatype'           => 'bool',
            'massiveaction'      => false,
        ];

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

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

        return $tab;
    }


    /**
     * @param  string $field
     * @param  array $values
     * @param  array $options
     *
     * @return string
     **/
    public static function getSpecificValueToDisplay($field, $values, array $options = [])
    {

        if (!is_array($values)) {
            $values = [$field => $values];
        }

        if (isset($options['searchopt']['real_type'])) {
            $ruleclass = new $options['searchopt']['real_type']();
            return $ruleclass->getSpecificValueToDisplay($field, $values, $options);
        }

        switch ($field) {
            case 'match':
                switch ($values[$field]) {
                    case self::AND_MATCHING:
                        return __('and');

                    case self::OR_MATCHING:
                        return __('or');

                    default:
                        return NOT_AVAILABLE;
                }
                break;
        }
        return parent::getSpecificValueToDisplay($field, $values, $options);
    }


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

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

        if (isset($options['searchopt']['real_type'])) {
            $ruleclass = new $options['searchopt']['real_type']();
            return $ruleclass->getSpecificValueToSelect($field, $name, $values, $options);
        }

        switch ($field) {
            case 'match':
                if (isset($values['itemtype']) && !empty($values['itemtype'])) {
                    $options['value'] = $values[$field];
                    $options['name']  = $name;
                    $rule             = new static();
                    return $rule->dropdownRulesMatch($options);
                }
                break;
        }
        return parent::getSpecificValueToSelect($field, $name, $values, $options);
    }


    /**
     * Show the rule
     *
     * @param integer $ID    ID of the rule
     * @param array $options array of possible options:
     *     - target filename : where to go when done.
     *     - withtemplate boolean : template or basic item
     *
     * @return void
     **/
    public function showForm($ID, array $options = [])
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;
        if (!$this->isNewID($ID)) {
            $this->check($ID, READ);
        } else {
            // Create item
            $this->checkGlobal(UPDATE);
        }

        $canedit = $this->canEdit(static::$rightname);
        $rand = mt_rand();
        $this->showFormHeader($options);

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Name') . "</td>";
        echo "<td>";
        echo Html::input('name', ['value' => $this->fields['name']]);
        echo "</td>";
        echo "<td>" . __('Description') . "</td>";
        echo "<td>";
        echo Html::input('description', ['value' => $this->fields['description']]);
        echo "</td></tr>\n";

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Logical operator') . "</td>";
        echo "<td>";
        $this->dropdownRulesMatch(['value' => $this->fields["match"]]);
        echo "</td>";
        echo "<td>" . __('Active') . "</td>";
        echo "<td>";
        Dropdown::showYesNo("is_active", $this->fields["is_active"]);
        echo "</td></tr>\n";

        if ($this->useConditions()) {
            echo "<tr class='tab_bg_1'>";
            echo "<td>" . __('Use rule for') . "</td>";
            echo "<td>";
            $this->dropdownConditions(['value' => $this->fields["condition"]]);
            echo "</td>";
            echo "<td colspan='2'>";
            echo "</td></tr>\n";
        }

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Comments') . "</td>";
        echo "<td class='middle' colspan='3'>";
        echo "<textarea class='form-control' rows='3' name='comment' >" . $this->fields["comment"] . "</textarea>";

        if (!$this->isNewID($ID)) {
            if ($this->fields["date_mod"]) {
                echo "<br>";
                printf(__('Last update on %s'), Html::convDateTime($this->fields["date_mod"]));
            }
        }
        if ($canedit) {
            if (!$this->isNewID($ID)) {
                echo "<input type='hidden' name='ranking' value='" . $this->fields["ranking"] . "'>";
            }
            echo "<input type='hidden' name='sub_type' value='" . get_class($this) . "'>";
        }
        echo "</td></tr>\n";

        if ($canedit) {
            if ($ID > 0) {
                if ($plugin = isPluginItemType($this->getType())) {
                    $url = $CFG_GLPI["root_doc"] . "/plugins/" . strtolower($plugin['plugin']);
                } else {
                    $url = $CFG_GLPI["root_doc"];
                }
                echo "<tr><td class='tab_bg_2 center' colspan='4'>";
                echo "<a class='btn btn-primary' href='#'
                     data-bs-toggle='modal' data-bs-target='#ruletest$rand'>" .
                  _x('button', 'Test') . "</a>";
                Ajax::createIframeModalWindow(
                    'ruletest' . $rand,
                    $url . "/front/rule.test.php?" . "sub_type=" . $this->getType() .
                                             "&rules_id=" . $this->fields["id"],
                    ['title' => _x('button', 'Test')]
                );
                echo "</td></tr>\n";
            }
        }

        $this->showFormButtons($options);

        return true;
    }


    /**
     * Display a dropdown with all the rule matching
     *
     * @since 0.84 new proto
     *
     * @param array $options array of parameters
     *
     * @return integer|string
     **/
    public function dropdownRulesMatch($options = [])
    {

        $p['name']     = 'match';
        $p['value']    = '';
        $p['restrict'] = $this->restrict_matching;
        $p['display']  = true;

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

        $elements = [];
        if (!$p['restrict'] || ($p['restrict'] == self::AND_MATCHING)) {
            $elements[self::AND_MATCHING] = __('and');
        }

        if (!$p['restrict'] || ($p['restrict'] == self::OR_MATCHING)) {
            $elements[self::OR_MATCHING]  = __('or');
        }

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


    /**
     * Get all criteria for a given rule
     *
     * @param integer $ID              the rule_description ID
     * @param boolean $withcriterias   1 to retrieve all the criteria for a given rule (default 0)
     * @param boolean $withactions     1 to retrieve all the actions for a given rule (default 0)
     *
     * @return boolean
     **/
    public function getRuleWithCriteriasAndActions($ID, $withcriterias = false, $withactions = false)
    {

        if ($this->isNewID($ID)) {
            return $this->getEmpty();
        }
        if ($this->getFromDB($ID)) {
            if (
                $withactions
                && ($RuleAction = getItemForItemtype($this->ruleactionclass))
            ) {
                $this->actions = $RuleAction->getRuleActions($ID);
            }

            if (
                $withcriterias
                && ($RuleCriterias = getItemForItemtype($this->rulecriteriaclass))
            ) {
                $this->criterias = $RuleCriterias->getRuleCriterias($ID);
            }

            return true;
        }

        return false;
    }


    /**
     * display title for action form
     **/
    public function getTitleAction()
    {

        foreach ($this->getActions() as $key => $val) {
            if (
                isset($val['force_actions'])
                && (in_array('regex_result', $val['force_actions'])
                 || in_array('append_regex_result', $val['force_actions']))
            ) {
                echo "<table class='tab_cadre_fixe'>";
                echo "<tr class='tab_bg_2'><td><div class='alert alert-info'>" .
                  __('It is possible to affect the result of a regular expression using the string #0') .
                 "</div></td></tr>\n";
                echo "</table><br>";
                return;
            }
        }
    }


    /**
     * Get maximum number of Actions of the Rule (0 = unlimited)
     *
     * @return integer the maximum number of actions
     **/
    public function maxActionsCount()
    {
        return count(array_filter($this->getAllActions(), function ($action_obj) {
            return !isset($action_obj['duplicatewith']);
        }));
    }


    /**
     * Display all rules actions
     *
     * @param integer $rules_id rule ID
     * @param array   $options  array of options : may be readonly
     *
     * @return void
     **/
    public function showActionsList($rules_id, $options = [])
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        $rand = mt_rand();
        $p['readonly'] = false;

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

        $canedit = $this->canEdit($rules_id);
        $style   = "class='tab_cadre_fixehov'";

        if ($p['readonly']) {
            $canedit = false;
            $style   = "class='tab_cadrehov'";
        }
        $this->getTitleAction();

        if ($canedit) {
            echo "<div id='viewaction" . $rules_id . "$rand'></div>\n";
        }

        if (
            $canedit
            && (($this->maxActionsCount() == 0)
              || (sizeof($this->actions) < $this->maxActionsCount()))
        ) {
            echo "<script type='text/javascript' >\n";
            echo "function viewAddAction" . $rules_id . "$rand() {\n";
            $params = ['type'                => $this->ruleactionclass,
                'parenttype'          => $this->getType(),
                $this->rules_id_field => $rules_id,
                'id'                  => -1,
            ];
            Ajax::updateItemJsCode(
                "viewaction" . $rules_id . "$rand",
                $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php",
                $params
            );
            echo "};";
            echo "</script>\n";
            echo "<div class='center firstbloc'>" .
               "<a class='btn btn-primary' href='javascript:viewAddAction" . $rules_id . "$rand();'>";
            echo __('Add a new action') . "</a></div>\n";
        }

        $nb = count($this->actions);

        echo "<div class='spaced'>";
        if ($canedit && $nb) {
            Html::openMassiveActionsForm('mass' . $this->ruleactionclass . $rand);
            $massiveactionparams = ['num_displayed'  => min($_SESSION['glpilist_limit'], $nb),
                'check_itemtype' => get_class($this),
                'check_items_id' => $rules_id,
                'container'      => 'mass' . $this->ruleactionclass . $rand,
                'extraparams'    => ['rule_class_name'
                                                                    => $this->getType(),
                ],
            ];
            Html::showMassiveActions($massiveactionparams);
        }

        echo "<table $style>";
        echo "<tr class='noHover'>";
        echo "<th colspan='" . ($canedit && $nb ? '4' : '3') . "'>" . _n('Action', 'Actions', Session::getPluralNumber()) . "</th></tr>";

        $header_begin  = "<tr>";
        $header_top    = '';
        $header_bottom = '';
        $header_end    = '';

        if ($canedit && $nb) {
            $header_top    .= "<th width='10'>";
            $header_top    .= Html::getCheckAllAsCheckbox('mass' . $this->ruleactionclass . $rand) . "</th>";
            $header_bottom .= "<th width='10'>";
            $header_bottom .= Html::getCheckAllAsCheckbox('mass' . $this->ruleactionclass . $rand) . "</th>";
        }

        $header_end .= "<th class='center b'>" . _n('Field', 'Fields', Session::getPluralNumber()) . "</th>";
        $header_end .= "<th class='center b'>" . __('Action type') . "</th>";
        $header_end .= "<th class='center b'>" . __('Value') . "</th>";
        $header_end .= "</tr>\n";
        echo $header_begin . $header_top . $header_end;

        foreach ($this->actions as $action) {
            $this->showMinimalActionForm($action->fields, $canedit, $rand);
        }
        if ($nb) {
            echo $header_begin . $header_bottom . $header_end;
        }
        echo "</table>\n";

        if ($canedit && $nb) {
            $massiveactionparams['ontop'] = false;
            Html::showMassiveActions($massiveactionparams);
            Html::closeForm();
        }
        echo "</div>";
    }


    public function maybeRecursive()
    {
        return false;
    }


    /**
     * Display all rules criteria
     *
     * @param integer $rules_id
     * @param array $options   array of options : may be readonly
     *
     * @return void
     **/
    public function showCriteriasList($rules_id, $options = [])
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        $rand = mt_rand();
        $p['readonly'] = false;

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

        $canedit = $this->canEdit($rules_id);
        $style   = "class='tab_cadre_fixehov'";

        if ($p['readonly']) {
            $canedit = false;
            $style   = "class='tab_cadrehov'";
        }

        if ($canedit) {
            echo "<div id='viewcriteria" . $rules_id . "$rand'></div>\n";

            echo "<script type='text/javascript' >\n";
            echo "function viewAddCriteria" . $rules_id . "$rand() {\n";
            $params = ['type'                => $this->rulecriteriaclass,
                'parenttype'          => $this->getType(),
                $this->rules_id_field => $rules_id,
                'id'                  => -1,
            ];
            Ajax::updateItemJsCode(
                "viewcriteria" . $rules_id . "$rand",
                $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php",
                $params
            );
            echo "};";
            echo "</script>\n";
            echo "<div class='center firstbloc'>" .
               "<a class='btn btn-primary' href='javascript:viewAddCriteria" . $rules_id . "$rand();'>";
            echo __('Add a new criterion') . "</a></div>\n";
        }

        echo "<div class='spaced'>";

        $nb = sizeof($this->criterias);

        if ($canedit && $nb) {
            Html::openMassiveActionsForm('mass' . $this->rulecriteriaclass . $rand);
            $massiveactionparams = ['num_displayed'  => min($_SESSION['glpilist_limit'], $nb),
                'check_itemtype' => get_class($this),
                'check_items_id' => $rules_id,
                'container'      => 'mass' . $this->rulecriteriaclass . $rand,
                'extraparams'    => ['rule_class_name'
                                                                    => $this->getType(),
                ],
            ];
            Html::showMassiveActions($massiveactionparams);
        }

        echo "<table $style>";
        echo "<tr class='noHover'>" .
           "<th colspan='" . ($canedit && $nb ? " 4 " : "3") . "'>" . _n('Criterion', 'Criteria', Session::getPluralNumber()) . "</th>" .
           "</tr>\n";

        $header_begin  = "<tr>";
        $header_top    = '';
        $header_bottom = '';
        $header_end    = '';

        if ($canedit && $nb) {
            $header_top    .= "<th width='10'>";
            $header_top    .= Html::getCheckAllAsCheckbox('mass' . $this->rulecriteriaclass . $rand);
            $header_top    .= "</th>";
            $header_bottom .= "<th width='10'>";
            $header_bottom .= Html::getCheckAllAsCheckbox('mass' . $this->rulecriteriaclass . $rand);
            $header_bottom .= "</th>";
        }
        $header_end .= "<th class='center b'>" . _n('Criterion', 'Criteria', 1) . "</th>\n";
        $header_end .= "<th class='center b'>" . __('Condition') . "</th>\n";
        $header_end .= "<th class='center b'>" . __('Reason') . "</th>\n";
        $header_end .= "</tr>\n";
        echo $header_begin . $header_top . $header_end;

        foreach ($this->criterias as $criterion) {
            $this->showMinimalCriteriaForm($criterion->fields, $canedit, $rand);
        }

        if ($nb) {
            echo $header_begin . $header_bottom . $header_end;
        }
        echo "</table>\n";

        if ($canedit && $nb) {
            $massiveactionparams['ontop'] = false;
            Html::showMassiveActions($massiveactionparams);
            Html::closeForm();
        }

        echo "</div>\n";
    }



    /**
     * Display the dropdown of the criteria for the rule
     *
     * @since 0.84 new proto
     *
     * @param array $options array of options : may be readonly
     *
     * @return integer|string the initial value (first)
     **/
    public function dropdownCriteria($options = [])
    {
        $p['name']                = 'criteria';
        $p['display']             = true;
        $p['value']               = '';
        $p['display_emptychoice'] = true;

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

        $items      = [];
        $group      = [];
        $groupname  = _n('Criterion', 'Criteria', Session::getPluralNumber());
        foreach ($this->getAllCriteria() as $ID => $crit) {
            // Manage group system
            if (!is_array($crit)) {
                if (count($group)) {
                    asort($group);
                    $items[$groupname] = $group;
                }
                $group     = [];
                $groupname = $crit;
            } else {
                $group[$ID] = $crit['name'];
            }
        }
        if (count($group)) {
            asort($group);
            $items[$groupname] = $group;
        }
        return Dropdown::showFromArray($p['name'], $items, $p);
    }


    /**
     * Display the dropdown of the actions for the rule
     *
     * @param array $options already used actions
     *
     * @return integer|string the initial value (first non used)
     **/
    public function dropdownActions($options = [])
    {
        $p['name']                = 'field';
        $p['display']             = true;
        $p['used']                = [];
        $p['value']               = '';
        $p['display_emptychoice'] = true;

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

        $actions = $this->getAllActions();

        // For each used actions see if several set is available
        // Force actions to available actions for several
        foreach ($p['used'] as $key => $ID) {
            if (isset($actions[$ID]['permitseveral'])) {
                unset($p['used'][$key]);
            }
        }

        // Complete used array with duplicate items
        // add duplicates of used items
        foreach ($p['used'] as $ID) {
            if (isset($actions[$ID]['duplicatewith'])) {
                $p['used'][$actions[$ID]['duplicatewith']] = $actions[$ID]['duplicatewith'];
            }
        }

        // Parse for duplicates of already used items
        foreach ($actions as $ID => $act) {
            if (
                isset($actions[$ID]['duplicatewith'])
                && in_array($actions[$ID]['duplicatewith'], $p['used'])
            ) {
                $p['used'][$ID] = $ID;
            }
        }

        $items = [];
        foreach ($actions as $ID => $act) {
            $items[$ID] = $act['name'];
        }
        return Dropdown::showFromArray($p['name'], $items, $p);
    }


    /**
     * Get a criteria description by his ID
     *
     * @param integer $ID the criteria's ID
     *
     * @return array the criteria array
     **/
    public function getCriteria($ID)
    {

        $criteria = $this->getAllCriteria();
        if (isset($criteria[$ID])) {
            return $criteria[$ID];
        }
        return [];
    }


    /**
     * Get action description by its ID
     *
     * @param integer $ID the action's ID
     *
     * @return array the action array
     **/
    public function getAction($ID)
    {

        $actions = $this->getAllActions();
        if (isset($actions[$ID])) {
            return $actions[$ID];
        }
        return [];
    }


    /**
     * Get a criteria description by his ID
     *
     * @param integer $ID the criteria's ID
     *
     * @return string the criteria's description
     **/

    public function getCriteriaName($ID)
    {

        $criteria = $this->getCriteria($ID);
        if (isset($criteria['name'])) {
            return $criteria['name'];
        }
        return __('Unavailable');
    }


    /**
     * Get action description by his ID
     *
     * @param integer $ID the action's ID
     *
     * @return string the action's description
     **/
    public function getActionName($ID)
    {

        $action = $this->getAction($ID);
        if (isset($action['name'])) {
            return $action['name'];
        }
        return "&nbsp;";
    }


    /**
     * Process the rule
     *
     * @param array &$input the input data used to check criteria
     * @param array &$output the initial output array used to be manipulated by actions
     * @param array &$params parameters for all internal functions
     * @param array &$options array options:
     *                     - only_criteria : only react on specific criteria
     *
     * @return void
     */
    public function process(&$input, &$output, &$params, &$options = [])
    {

        if ($this->validateCriterias($options)) {
            $this->regex_results     = [];
            $this->criterias_results = [];
            $input = $this->prepareInputDataForProcess($input, $params);

            if ($this->checkCriterias($input)) {
                unset($output["_no_rule_matches"]);
                $refoutput = $output;
                $output    = $this->executeActions($output, $params, $input);

                $this->updateOnlyCriteria($options, $refoutput, $output);
                //Hook
                $hook_params["sub_type"] = $this->getType();
                $hook_params["ruleid"]   = $this->fields["id"];
                $hook_params["input"]    = $input;
                $hook_params["output"]   = $output;
                Plugin::doHook(Hooks::RULE_MATCHED, $hook_params);
                $output["_rule_process"] = true;
            }
        }
    }


    /**
     * Update Only criteria options if needed
     *
     * @param array &$options   options :
     *                            - only_criteria : only react on specific criteria
     * @param array $refoutput   the initial output array used to be manipulated by actions
     * @param array $newoutput   the output array after actions process
     *
     * @return void
     **/
    public function updateOnlyCriteria(&$options, $refoutput, $newoutput)
    {

        if (count($this->actions)) {
            if (
                isset($options['only_criteria'])
                && !is_null($options['only_criteria'])
                && is_array($options['only_criteria'])
            ) {
                foreach ($this->actions as $action) {
                    if (
                        !isset($refoutput[$action->fields["field"]])
                        || ($refoutput[$action->fields["field"]]
                        != $newoutput[$action->fields["field"]])
                    ) {
                        if (!in_array($action->fields["field"], $options['only_criteria'])) {
                            $options['only_criteria'][] = $action->fields["field"];
                        }

                        // Add linked criteria if available
                        $crit = $this->getCriteria($action->fields["field"]);
                        if (isset($crit['linked_criteria'])) {
                            $tmp = $crit['linked_criteria'];
                            if (!is_array($crit['linked_criteria'])) {
                                $tmp = [$tmp];
                            }
                            foreach ($tmp as $toadd) {
                                if (!in_array($toadd, $options['only_criteria'])) {
                                    $options['only_criteria'][] = $toadd;
                                }
                            }
                        }
                    }
                }
            }
        }
    }


    /**
     * Are criteria valid to be processed
     *
     * @since 0.85
     *
     * @param array $options
     *
     * @return boolean
     **/
    public function validateCriterias($options)
    {

        if (count($this->criterias)) {
            if (
                isset($options['only_criteria'])
                && !is_null($options['only_criteria'])
                && is_array($options['only_criteria'])
            ) {
                foreach ($this->criterias as $criterion) {
                    if (in_array($criterion->fields['criteria'], $options['only_criteria'])) {
                        return true;
                    }
                }
                return false;
            }
            return true;
        }

        return false;
    }


    /**
     * Check criteria
     *
     * @param array $input the input data used to check criteri
     *
     * @return boolean if criteria match
     **/
    public function checkCriterias($input)
    {

        reset($this->criterias);

        if ($this->fields["match"] == self::AND_MATCHING) {
            $doactions = true;

            foreach ($this->criterias as $criterion) {
                $definition_criterion = $this->getCriteria($criterion->fields['criteria']);
                if (!isset($definition_criterion['is_global']) || !$definition_criterion['is_global']) {
                    $doactions &= $this->checkCriteria($criterion, $input);
                    if (!$doactions) {
                        break;
                    }
                }
            }
        } else { // OR MATCHING
            $doactions = false;
            foreach ($this->criterias as $criterion) {
                $definition_criterion = $this->getCriteria($criterion->fields['criteria']);

                if (
                    !isset($definition_criterion['is_global'])
                    || !$definition_criterion['is_global']
                ) {
                    $doactions |= $this->checkCriteria($criterion, $input);
                    if ($doactions) {
                        break;
                    }
                }
            }
        }

        //If all simple criteria match, and if necessary, check complex criteria
        if ($doactions) {
            return $this->findWithGlobalCriteria($input);
        }
        return false;
    }


    /**
     * Check criteria
     *
     * @param array $input          the input data used to check criteria
     * @param array &$check_results
     *
     * @return void
     **/
    public function testCriterias($input, &$check_results)
    {

        reset($this->criterias);

        foreach ($this->criterias as $criterion) {
            $result = $this->checkCriteria($criterion, $input);
            $check_results[$criterion->fields["id"]]["name"]   = $criterion->fields["criteria"];
            $check_results[$criterion->fields["id"]]["value"]  = $criterion->fields["pattern"];
            $check_results[$criterion->fields["id"]]["result"] = ((!$result) ? 0 : 1);
            $check_results[$criterion->fields["id"]]["id"]     = $criterion->fields["id"];
        }
    }


    /**
     * Process a criteria of a rule
     *
     * @param RuleCriteria $criteria  criteria to check
     * @param array &$input     the input data used to check criteria
     *
     * @return boolean
     **/
    public function checkCriteria($criteria, &$input)
    {

        $partial_regex_result = [];
        // Undefine criteria field : set to blank
        if (!isset($input[$criteria->fields["criteria"]])) {
            $input[$criteria->fields["criteria"]] = '';
        }

        //If the value is not an array
        if (!is_array($input[$criteria->fields["criteria"]])) {
            $value = $this->getCriteriaValue(
                $criteria->fields["criteria"],
                $criteria->fields["condition"],
                $input[$criteria->fields["criteria"]]
            );

            $res   = RuleCriteria::match(
                $criteria,
                $value,
                $this->criterias_results,
                $partial_regex_result
            );
        } else {
            //If the value is, in fact, an array of values
            // Negative condition : Need to match all condition (never be)
            if (
                in_array($criteria->fields["condition"], [self::PATTERN_IS_NOT,
                    self::PATTERN_NOT_CONTAIN,
                    self::REGEX_NOT_MATCH,
                    self::PATTERN_DOES_NOT_EXISTS,
                ])
            ) {
                $res = true;
                foreach ($input[$criteria->fields["criteria"]] as $tmp) {
                    $value = $this->getCriteriaValue(
                        $criteria->fields["criteria"],
                        $criteria->fields["condition"],
                        $tmp
                    );

                    $res &= RuleCriteria::match(
                        $criteria,
                        $value,
                        $this->criterias_results,
                        $partial_regex_result
                    );
                    if (!$res) {
                        break;
                    }
                }
            } else {
                // Positive condition : Need to match one
                $res = false;
                foreach ($input[$criteria->fields["criteria"]] as $crit) {
                    $value = $this->getCriteriaValue(
                        $criteria->fields["criteria"],
                        $criteria->fields["condition"],
                        $crit
                    );

                    $res |= RuleCriteria::match(
                        $criteria,
                        $value,
                        $this->criterias_results,
                        $partial_regex_result
                    );
                }
            }
        }

        // Found regex on this criteria
        if (count($partial_regex_result)) {
            // No regex existing : put found
            if (!count($this->regex_results)) {
                $this->regex_results = $partial_regex_result;
            } else { // Already existing regex : append found values
                $temp_result = [];
                foreach ($partial_regex_result as $new) {
                    foreach ($this->regex_results as $old) {
                        $temp_result[] = array_merge($old, $new);
                    }
                }
                $this->regex_results = $temp_result;
            }
        }

        return $res;
    }


    /**
     * @param array $input
     *
     * return boolean
     */
    public function findWithGlobalCriteria($input)
    {
        return true;
    }


    /**
     * Specific prepare input data for the rule
     *
     * @param array $input  the input data used to check criteria
     * @param array $params parameters
     *
     * @return array the updated input data
     **/
    public function prepareInputDataForProcess($input, $params)
    {
        return $input;
    }


    /**
     * Get all data needed to process rules (core + plugins)
     *
     * @since 0.84
     * @param array $input  the input data used to check criteria
     * @param array $params parameters
     *
     * @return array the updated input data
     **/
    public function prepareAllInputDataForProcess($input, $params)
    {
        /** @var array $PLUGIN_HOOKS */
        global $PLUGIN_HOOKS;

        $input = $this->prepareInputDataForProcess($input, $params);
        if (isset($PLUGIN_HOOKS['use_rules'])) {
            foreach ($PLUGIN_HOOKS['use_rules'] as $plugin => $val) {
                if (!Plugin::isPluginActive($plugin)) {
                    continue;
                }
                if (is_array($val) && in_array($this->getType(), $val)) {
                    $results = Plugin::doOneHook(
                        $plugin,
                        "rulePrepareInputDataForProcess",
                        ['input'  => $input,
                            'params' => $params,
                        ]
                    );
                    if (is_array($results)) {
                        foreach ($results as $result) {
                            $input[] = $result;
                        }
                    }
                }
            }
        }
        return $input;
    }


    /**
     * Execute plugins actions if needed.
     *
     * @since 9.3.2 Added $input parameter
     * @since 0.84
     *
     * @param RuleAction $action
     * @param array      $output  rule execution output
     * @param array      $params  parameters
     * @param array      $input   the input data
     *
     * @return array Updated output
     */
    public function executePluginsActions($action, $output, $params, array $input = [])
    {
        /** @var array $PLUGIN_HOOKS */
        global $PLUGIN_HOOKS;

        if (isset($PLUGIN_HOOKS['use_rules'])) {
            $params['criterias_results'] = $this->criterias_results;
            $params['rule_itemtype']     = $this->getType();
            foreach ($PLUGIN_HOOKS['use_rules'] as $plugin => $val) {
                if (!Plugin::isPluginActive($plugin)) {
                    continue;
                }
                if (is_array($val) && in_array($this->getType(), $val)) {
                    $results = Plugin::doOneHook($plugin, "executeActions", ['output' => $output,
                        'params' => $params,
                        'action' => $action,
                        'input'  => $input,
                    ]);
                    if (is_array($results)) {
                        foreach ($results as $id => $result) {
                            $output[$id] = $result;
                        }
                    }
                }
            }
        }
        return $output;
    }


    /**
     * Execute the actions as defined in the rule.
     *
     * @since 9.3.2 Added $input parameter
     *
     * @param array $output  the fields to manipulate
     * @param array $params  parameters
     * @param array $input   the input data
     *
     * @return array Updated output
     **/
    public function executeActions($output, $params, array $input = [])
    {

        if (count($this->actions)) {
            foreach ($this->actions as $action) {
                switch ($action->fields["action_type"]) {
                    case "assign":
                        $output[$action->fields["field"]] = $action->fields["value"];
                        break;

                    case "append":
                        $actions = $this->getAllActions();
                        $value   = $action->fields["value"];
                        if (
                            isset($actions[$action->fields["field"]]["appendtoarray"])
                            && isset($actions[$action->fields["field"]]["appendtoarrayfield"])
                        ) {
                            $value = $actions[$action->fields["field"]]["appendtoarray"];
                            $value[$actions[$action->fields["field"]]["appendtoarrayfield"]]
                            = $action->fields["value"];
                        }
                        $output[$actions[$action->fields["field"]]["appendto"]][] = $value;
                        break;

                    case "regex_result":
                    case "append_regex_result":
                        //Regex result : assign value from the regex
                        //Append regex result : append result from a regex
                        if (isset($this->regex_results[0])) {
                            $res = RuleAction::getRegexResultById(
                                $action->fields["value"],
                                $this->regex_results[0]
                            );
                        } else {
                            $res = $action->fields["value"];
                        }

                        if ($action->fields["action_type"] == "append_regex_result") {
                            if (isset($params[$action->fields["field"]])) {
                                $res = $params[$action->fields["field"]] . $res;
                            } else {
                                //keep rule value to append in a separate entry
                                $output[$action->fields['field'] . '_append'] = $res;
                            }
                        }

                        $output[$action->fields["field"]] = $res;
                        break;

                    default:
                        //plugins actions
                        $executeaction = clone $this;
                        $output = $executeaction->executePluginsActions($action, $output, $params, $input);
                        break;
                }
            }
        }
        return $output;
    }


    public function cleanDBonPurge()
    {

        // Delete a rule and all associated criteria and actions
        if (!empty($this->ruleactionclass)) {
            $ruleactionclass = $this->ruleactionclass;
            $ra = new $ruleactionclass();
            $ra->deleteByCriteria([$this->rules_id_field => $this->fields['id']]);
        }

        if (!empty($this->rulecriteriaclass)) {
            $rulecriteriaclass = $this->rulecriteriaclass;
            $rc = new $rulecriteriaclass();
            $rc->deleteByCriteria([$this->rules_id_field => $this->fields['id']]);
        }
    }


    /**
     * Show the minimal form for the rule
     *
     * @param string $target             link to the form page
     * @param boolean $first              is it the first rule ?(false by default)
     * @param boolean$last               is it the last rule ? (false by default)
     * @param boolean $display_entities   display entities / make it read only display (false by default)
     * @param boolean $active_condition   active condition used (default false)
     *  param boolean $display_criterias  display rule criterias (false by default)
     *  param boolean $display_actions    display rule actions(false by default)
     **/
    public function showMinimalForm(
        $target,
        $first = false,
        $last = false,
        $display_entities = false,
        $active_condition = false
        // FIXME Uncomment this in GLPI 10.1
        // bool $display_criterias = false,
        // bool $display_actions = false
    ) {
        $display_criterias = func_get_args()[5] ?? false;
        $display_actions = func_get_args()[6] ?? false;
        $canedit = (self::canUpdate() && !$display_entities);
        echo "<tr class='tab_bg_1' data-rule-id='" . $this->fields['id'] . "'>";

        if ($canedit) {
            echo "<td width='10'>";
            Html::showMassiveActionCheckBox($this->getType(), $this->fields["id"]);
            echo "</td>";
        } else {
            echo "<td>&nbsp;</td>";
        }

        $link = $this->getLink();
        if (!empty($this->fields["comment"])) {
            $link = sprintf(
                __('%1$s %2$s'),
                $link,
                Html::showToolTip($this->fields["comment"], ['display' => false])
            );
        }
        echo "<td>" . $link . "</td>";
        echo "<td>" . $this->fields["description"] . "</td>";
        if ($this->useConditions()) {
            echo "<td>" . $this->getConditionName($this->fields["condition"]) . "</td>";
        }
        if (
            $display_criterias
            && ($RuleCriterias = getItemForItemtype($this->rulecriteriaclass))
        ) {
            echo "<td>";
            foreach ($RuleCriterias->getRuleCriterias($this->fields['id']) as $RuleCriteria) {
                $to_display = $this->getMinimalCriteria($RuleCriteria->fields);
                echo "<span class='glpi-badge mb-1'>" . implode('<i class="fas fa-caret-right mx-1"></i>', $to_display) . '</span><br />';
            }
            echo "</td>";
        }
        if (
            $display_actions
            && ($RuleAction = getItemForItemtype($this->ruleactionclass))
        ) {
            echo "<td>";
            foreach ($RuleAction->getRuleActions($this->fields['id']) as $RuleAction) {
                $to_display = $this->getMinimalAction($RuleAction->fields);
                echo "<span class='glpi-badge mb-1'>" . implode('<i class="fas fa-caret-right mx-1"></i>', $to_display) . '</span><br />';
            }
            echo "</td>";
        }

        $output = sprintf(
            "<i class='fas fa-circle %s' title='%s'></i> <span class='sr-only'>%s</span>",
            ($this->fields['is_active'] ? 'green' : 'red'),
            ($this->fields['is_active'] ? __('Rule is active') : __('Rule is inactive')),
            Dropdown::getYesNo($this->fields["is_active"])
        );
        echo "<td>" . $output . "</td>";

        if ($display_entities) {
            $entname = Dropdown::getDropdownName('glpi_entities', $this->fields['entities_id']);
            if (
                $this->maybeRecursive()
                && $this->fields['is_recursive']
            ) {
                $entname = sprintf(__('%1$s %2$s'), $entname, "<span class='b'>(" . __('R') . ")</span>");
            }

            echo "<td>" . $entname . "</td>";
        }

        if ($this->can_sort && $canedit) {
            echo "<td colspan='2'><i class='fas fa-grip-horizontal grip-rule'></i></td>";
        }
        echo "</tr>";
    }


    public function prepareInputForAdd($input)
    {
        //If no uuid given, generate a new one
        if (!isset($input['uuid'])) {
            $input["uuid"] = self::getUuid();
        }

        if ($this->getType() == 'Rule' && !isset($input['sub_type'])) {
            trigger_error('Sub type not specified creating a new rule', E_USER_WARNING);
            return false;
        }

        if (!isset($input['sub_type'])) {
            $input['sub_type'] = $this->getType();
        } elseif ($this->getType() != 'Rule' && $input['sub_type'] != $this->getType()) {
            Toolbox::logDebug(
                sprintf(
                    'Creating a %s rule with %s subtype.',
                    $this->getType(),
                    $input['sub_type']
                )
            );
        }

        // Before adding, add the ranking of the new rule
        $input["ranking"] = $input['ranking'] ?? $this->getNextRanking($input['sub_type']);

        return $input;
    }


    /**
     * Get the next ranking for a specified rule
     * @param string|null $sub_type Specific class for the rule. Defaults to the current class at runtime.
     *
     * @return integer
     **/
    public function getNextRanking(?string $sub_type = null)
    {
        /** @var \DBmysql $DB */
        global $DB;

        $iterator = $DB->request([
            'SELECT' => ['MAX' => 'ranking AS rank'],
            'FROM'   => self::getTable(),
            'WHERE'  => ['sub_type' => $sub_type ?? static::class],
        ]);

        if (count($iterator)) {
            $data = $iterator->current();
            return $data["rank"] + 1;
        }
        return 0;
    }


    /**
     * Show the minimal form for the action rule
     *
     * @param array   $fields  data used to display the action
     * @param boolean $canedit can edit the actions rule ?
     * @param integer $rand    random value of the form
     **/
    public function showMinimalActionForm($fields, $canedit, $rand)
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        $edit = ($canedit ? "style='cursor:pointer' onClick=\"viewEditAction" .
                         $fields[$this->rules_id_field] . $fields["id"] . "$rand();\""
                        : '');
        echo "<tr class='tab_bg_1'>";
        if ($canedit) {
            echo "<td width='10'>";
            Html::showMassiveActionCheckBox($this->ruleactionclass, $fields["id"]);
            echo "\n<script type='text/javascript' >\n";
            echo "function viewEditAction" . $fields[$this->rules_id_field] . $fields["id"] . "$rand() {\n";
            $params = ['type'                => $this->ruleactionclass,
                'parenttype'          => $this->getType(),
                $this->rules_id_field => $fields[$this->rules_id_field],
                'id'                  => $fields["id"],
            ];
            Ajax::updateItemJsCode(
                "viewaction" . $fields[$this->rules_id_field] . "$rand",
                $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php",
                $params
            );
            echo "};";
            echo "</script>\n";
            echo "</td>";
        }
        echo $this->getMinimalActionText($fields, $edit);
        echo "</tr>\n";
    }


    /**
     * Show preview result of a rule
     *
     * @param string $target    where to go if action
     * @param array $input     input data array
     * @param array $params    params used (see addSpecificParamsForPreview)
     **/
    public function showRulePreviewResultsForm($target, $input, $params)
    {
        $actions       = $this->getAllActions();
        $check_results = [];
        $output        = [];

        // specify that we are in a test context
        $this->is_preview = true;

        //Test all criteria, without stopping at the first good one
        $this->testCriterias($input, $check_results);
        //Process the rule
        $this->process($input, $output, $params);
        if (!$criteria = getItemForItemtype($this->rulecriteriaclass)) {
            return;
        }

        echo "<div class='spaced'>";
        echo "<table class='tab_cadrehov'>";
        echo "<tr><th colspan='4'>" . __('Result details') . "</th></tr>";

        echo "<tr class='tab_bg_2'>";
        echo "<td class='center b'>" . _n('Criterion', 'Criteria', 1) . "</td>";
        echo "<td class='center b'>" . __('Condition') . "</td>";
        echo "<td class='center b'>" . __('Reason') . "</td>";
        echo "<td class='center b'>" . _n('Validation', 'Validations', 1) . "</td>";
        echo "</tr>\n";

        foreach ($check_results as $ID => $criteria_result) {
            echo "<tr class='tab_bg_1'>";
            $criteria->getFromDB($criteria_result["id"]);
            echo $this->getMinimalCriteriaText($criteria->fields);
            if ($criteria->fields['condition'] != self::PATTERN_FIND) {
                echo "<td class='b'>" . Dropdown::getYesNo($criteria_result["result"]) . "</td></tr>\n";
            } else {
                echo "<td class='b'>" . Dropdown::EMPTY_VALUE . "</td></tr>\n";
            }
        }
        echo "</table></div>";

        $global_result = (isset($output["_rule_process"]) ? 1 : 0);

        echo "<div class='spaced'>";
        echo "<table class='tab_cadrehov'>";
        echo "<tr><th colspan='2'>" . __('Rule results') . "</th></tr>";
        echo "<tr class='tab_bg_1'>";
        echo "<td class='center b'>" . _n('Validation', 'Validations', 1) . "</td><td>";
        echo Dropdown::getYesNo($global_result) . "</td></tr>";

        $output = $this->preProcessPreviewResults($output);

        foreach ($output as $criteria => $value) {
            $action_def = array_filter($actions, static function ($def, $key) use ($criteria) {
                return $key === $criteria || (array_key_exists('appendto', $def) && $def['appendto'] === $criteria);
            }, ARRAY_FILTER_USE_BOTH);
            $action_def_key = key($action_def);
            if (count($action_def)) {
                $action_def = reset($action_def);
            } else {
                continue;
            }

            if (isset($action_def['type'])) {
                $actiontype = $action_def['type'];
            } else {
                $actiontype = '';
            }

            // Some action values can be an array (appendto actions). So, we will force everything to be an array and loop over it when displaying the rows.
            if (!is_array($value)) {
                $value = [$value];
            }
            foreach ($value as $v) {
                $action_value = $this->getActionValue($action_def_key, $actiontype, $v);
                echo "<tr class='tab_bg_2'>";
                echo "<td>" . $action_def["name"] . "</td>";
                echo "<td>$action_value</td></tr>";
            }
        }

        //If a regular expression was used, and matched, display the results
        if (count($this->regex_results)) {
            echo "<tr class='tab_bg_2'>";
            echo "<td>" . __('Result of the regular expression') . "</td>";
            echo "<td>";
            if (!empty($this->regex_results[0])) {
                echo "<table class='tab_cadre'>";
                echo "<tr><th>" . __('Key') . "</th><th>" . __('Value') . "</th></tr>";
                foreach ($this->regex_results[0] as $key => $value) {
                    echo "<tr class='tab_bg_1'>";
                    echo "<td>$key</td><td>$value</td></tr>";
                }
                echo "</table>";
            }
            echo "</td></tr>\n";
        }
        echo "</tr>\n";
        echo "</table></div>";
    }


    /**
     * Show the minimal form for the criteria rule
     *
     * @param array   $fields  data used to display the criteria
     * @param boolean $canedit can edit the criteria rule?
     * @param integer $rand    random value of the form
     *
     * @return void
     **/
    public function showMinimalCriteriaForm($fields, $canedit, $rand)
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        $edit = ($canedit ? "style='cursor:pointer' onClick=\"viewEditCriteria" .
                         $fields[$this->rules_id_field] . $fields["id"] . "$rand();\""
                        : '');
        echo "<tr class='tab_bg_1' >";
        if ($canedit) {
            echo "<td width='10'>";
            Html::showMassiveActionCheckBox($this->rulecriteriaclass, $fields["id"]);
            echo "\n<script type='text/javascript' >\n";
            echo "function viewEditCriteria" . $fields[$this->rules_id_field] . $fields["id"] . "$rand() {\n";
            $params = ['type'               => $this->rulecriteriaclass,
                'parenttype'          => $this->getType(),
                $this->rules_id_field => $fields[$this->rules_id_field],
                'id'                  => $fields["id"],
            ];
            Ajax::updateItemJsCode(
                "viewcriteria" . $fields[$this->rules_id_field] . "$rand",
                $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php",
                $params
            );
            echo "};";
            echo "</script>\n";
            echo "</td>";
        }

        echo $this->getMinimalCriteriaText($fields, $edit);
        echo "</tr>\n";
    }


    /**
     * @param array  $fields
     * @param string $addtotd   (default '')
     **/
    public function getMinimalCriteriaText($fields, $addtotd = '')
    {
        $to_display = $this->getMinimalCriteria($fields);
        $text  = "<td $addtotd>" . $to_display['criterion'] . "</td>";
        $text .= "<td $addtotd>" . $to_display['condition'] . "</td>";
        $text .= "<td $addtotd>" . $to_display['pattern'] . "</td>";
        return $text;
    }

    /**
     * @param array $fields
     *
     * @return array
     **/
    private function getMinimalCriteria(array $fields): array
    {
        $criterion = $this->getCriteriaName($fields["criteria"]);
        $condition = RuleCriteria::getConditionByID($fields["condition"], get_class($this), $fields["criteria"]);
        $pattern   = $this->getCriteriaDisplayPattern($fields["criteria"], $fields["condition"], $fields["pattern"]);

        // Some data may come from the database, and be sanitized (i.e. html special chars already encoded),
        // but some data may have been build from translation or from some plugin code and may be not sanitized.
        // First, extract the verbatim value (i.e. with non encoded specia chars), then encode special chars to
        // ensure HTML validity (and to prevent XSS).
        return [
            'criterion' => Sanitizer::encodeHtmlSpecialChars(Sanitizer::getVerbatimValue($criterion)),
            'condition' => Sanitizer::encodeHtmlSpecialChars(Sanitizer::getVerbatimValue($condition)),
            'pattern'   => Sanitizer::encodeHtmlSpecialChars(Sanitizer::getVerbatimValue($pattern ?? '')),
        ];
    }

    /**
     * @param array  $fields
     * @param string $addtotd   (default '')
     **/
    public function getMinimalActionText($fields, $addtotd = '')
    {
        $to_display = $this->getMinimalAction($fields);
        $text  = "<td $addtotd>" . $to_display['field'] . "</td>";
        $text .= "<td $addtotd>" . $to_display['type'] . "</td>";
        $text .= "<td $addtotd>" . $to_display['value'] . "</td>";
        return $text;
    }

    /**
     * @param array $fields
     *
     * @return array
     **/
    private function getMinimalAction(array $fields): array
    {
        $field = $this->getActionName($fields["field"]);
        $type  = RuleAction::getActionByID($fields["action_type"]);
        $value = isset($fields["value"])
            ? $this->getActionValue($fields["field"], $fields['action_type'], $fields["value"])
            : '';

        // Some data may come from the database, and be sanitized (i.e. html special chars already encoded),
        // but some data may have been build from translation or from some plugin code and may be not sanitized.
        // First, extract the verbatim value (i.e. with non encoded specia chars), then encode special chars to
        // ensure HTML validity (and to prevent XSS).
        return [
            'field' => Sanitizer::encodeHtmlSpecialChars(Sanitizer::getVerbatimValue($field)),
            'type'  => Sanitizer::encodeHtmlSpecialChars(Sanitizer::getVerbatimValue($type)),
            'value' => Sanitizer::encodeHtmlSpecialChars(Sanitizer::getVerbatimValue($value)),
        ];
    }

    /**
     * Return a value associated with a pattern associated to a criteria to display it
     *
     * @param integer  $ID        the given criteria
     * @param integer  $condition condition used
     * @param ?string  $pattern   the pattern
     *
     * @return ?string
     **/
    public function getCriteriaDisplayPattern($ID, $condition, $pattern)
    {

        if (
            ($condition == self::PATTERN_EXISTS)
            || ($condition == self::PATTERN_DOES_NOT_EXISTS)
            || ($condition == self::PATTERN_FIND)
        ) {
            return __('Yes');
        } elseif (
            in_array($condition, [self::PATTERN_IS, self::PATTERN_IS_NOT,
                self::PATTERN_NOT_UNDER, self::PATTERN_UNDER,
            ])
        ) {
            $crit = $this->getCriteria($ID);

            if (isset($crit['type'])) {
                switch ($crit['type']) {
                    case "yesonly":
                    case "yesno":
                        return Dropdown::getYesNo($pattern);

                    case "dropdown":
                        $addentity = Dropdown::getDropdownName($crit["table"], $pattern);
                        if ($this->isEntityAssign()) {
                            $itemtype = getItemTypeForTable($crit["table"]);
                            $item     = getItemForItemtype($itemtype);
                            if (
                                $item
                                && $item->getFromDB($pattern)
                                && $item->isEntityAssign()
                            ) {
                                $addentity = sprintf(
                                    __('%1$s (%2$s)'),
                                    $addentity,
                                    Dropdown::getDropdownName(
                                        'glpi_entities',
                                        $item->getEntityID()
                                    )
                                );
                            }
                        }
                        $tmp = $addentity;
                        return (($tmp == '&nbsp;') ? NOT_AVAILABLE : $tmp);

                    case "dropdown_users":
                        return getUserName($pattern);

                    case "dropdown_assets_itemtype":
                    case "dropdown_tracking_itemtype":
                        if ($item = getItemForItemtype($pattern)) {
                            return $item->getTypeName(1);
                        }
                        if (empty($pattern)) {
                            return __('General');
                        }
                        break;

                    case "dropdown_status":
                        return Ticket::getStatus($pattern);

                    case "dropdown_priority":
                        return Ticket::getPriorityName($pattern);

                    case "dropdown_urgency":
                        return Ticket::getUrgencyName($pattern);

                    case "dropdown_impact":
                        return Ticket::getImpactName($pattern);

                    case "dropdown_tickettype":
                        return Ticket::getTicketTypeName($pattern);

                    case "dropdown_validation_status":
                        return TicketValidation::getStatus($pattern);
                }
            }
        }
        if ($result = $this->getAdditionalCriteriaDisplayPattern($ID, $condition, $pattern)) {
            return $result;
        }
        return $pattern;
    }


    /**
     * Used to get specific criteria patterns
     *
     * @param integer $ID        the given criteria
     * @param integer $condition condition used
     * @param string  $pattern   the pattern
     *
     * @return mixed|false  A value associated with the criteria, or false otherwise
     **/
    public function getAdditionalCriteriaDisplayPattern($ID, $condition, $pattern)
    {
        return false;
    }


    /**
     * Display item used to select a pattern for a criteria
     *
     * @param string  $name      criteria name
     * @param integer $ID        the given criteria
     * @param integer $condition condition used
     * @param string  $value     the pattern (default '')
     * @param boolean $test      Is to test rule ? (false by default)
     *
     * @return void
     **/
    public function displayCriteriaSelectPattern($name, $ID, $condition, $value = "", $test = false)
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        $crit    = $this->getCriteria($ID);
        $display = false;
        $tested  = false;

        if (
            isset($crit['type'])
            && ($test
              || in_array($condition, [self::PATTERN_IS, self::PATTERN_IS_NOT,
                  self::PATTERN_NOT_UNDER, self::PATTERN_UNDER,
              ]))
        ) {
            $tested = true;
            switch ($crit['type']) {
                case "yesonly":
                    Dropdown::showYesNo($name, $crit['table'], 0);
                    $display = true;
                    break;

                case "yesno":
                    Dropdown::showYesNo($name, $value);
                    $display = true;
                    break;

                case "dropdown":
                    $param = ['name'  => $name,
                        'value' => $value,
                    ];
                    if (isset($crit['condition'])) {
                        $param['condition'] = $crit['condition'];
                    }
                    Dropdown::show(getItemTypeForTable($crit['table']), $param);

                    $display = true;
                    break;

                case "dropdown_users":
                    User::dropdown(['value'  => $value,
                        'name'   => $name,
                        'right'  => 'all',
                    ]);
                    $display = true;
                    break;

                case "dropdown_tracking_itemtype":
                    Dropdown::showItemTypes($name, array_keys(Ticket::getAllTypesForHelpdesk()));
                    $display = true;
                    break;

                case "dropdown_assets_itemtype":
                    Dropdown::showItemTypes($name, $CFG_GLPI['asset_types'], ['value' => $value]);
                    $display = true;
                    break;

                case "dropdown_inventory_itemtype":
                    $types = $CFG_GLPI['state_types'];
                    $types[''] = __('No item type defined');
                    Dropdown::showItemTypes($name, $types, ['value' => $value]);
                    $display = true;
                    break;

                case "dropdown_urgency":
                    Ticket::dropdownUrgency(['name'  => $name,
                        'value' => $value,
                    ]);
                    $display = true;
                    break;

                case "dropdown_impact":
                    Ticket::dropdownImpact(['name'  => $name,
                        'value' => $value,
                    ]);
                    $display = true;
                    break;

                case "dropdown_priority":
                    Ticket::dropdownPriority(['name'  => $name,
                        'value' => $value,
                        'withmajor' => true,
                    ]);
                    $display = true;
                    break;

                case "dropdown_status":
                    Ticket::dropdownStatus(['name'  => $name,
                        'value' => $value,
                    ]);
                    $display = true;
                    break;

                case "dropdown_tickettype":
                    Ticket::dropdownType($name, ['value' => $value]);
                    $display = true;
                    break;

                case "dropdown_validation_status":
                    TicketValidation::dropdownStatus($name, [
                        'global' => true,
                        'value' => $value,
                    ]);
                    $display = true;
                    break;

                default:
                    $tested = false;
                    break;
            }
        }
        //Not a standard condition
        if (!$tested) {
            $display = $this->displayAdditionalRuleCondition($condition, $crit, $name, $value, $test);
        }

        $hiddens = [
            self::PATTERN_EXISTS,
            self::PATTERN_DOES_NOT_EXISTS,
            RuleImportAsset::PATTERN_ENTITY_RESTRICT,
            RuleImportAsset::PATTERN_NETWORK_PORT_RESTRICT,
            RuleImportAsset::PATTERN_ONLY_CRITERIA_RULE,
        ];
        if (!$display && in_array($condition, $hiddens)) {
            echo Html::hidden($name, ['value' => 1]);
            $display = true;
        }

        if (
            !$display
            && ($rc = getItemForItemtype($this->rulecriteriaclass))
        ) {
            echo Html::input($name, ['value' => $value, 'size' => '70']);
        }
    }


    /**
     * Return a "display" value associated with a pattern associated to a criteria
     *
     * @param integer $ID     the given action
     * @param string  $type   the type of action
     * @param string  $value  the value
     *
     * @return string
     **/
    public function getActionValue($ID, $type, $value)
    {

        $action = $this->getAction($ID);
        if (isset($action['type'])) {
            switch ($action['type']) {
                case "dropdown_entity":
                    if ($value == -1) {
                        return __('Full structure');
                    }
                    // Intentional fall-through to handle dropdown cases
                    // no break
                case "dropdown":
                    if ($type == 'defaultfromuser' || $type == 'fromuser' || $type == 'fromitem' || $type == 'firstgroupfromuser') {
                        return Dropdown::getYesNo($value);
                    }

                    // $type == regex_result display text
                    if ($type == 'regex_result') {
                        return $this->displayAdditionRuleActionValue($value);
                    }

                    if ($this->is_preview && !is_numeric($value)) {
                        // In preview mode, if the value corresponds to a string
                        // that does not match an existing dropdown entry,
                        // it will not be imported and therefore tha value will not correspond to a dropdown valid ID.
                        return $value;
                    }

                    // $type == assign
                    $name = Dropdown::getDropdownName($action["table"], $value);
                    return (($name == '&nbsp;') ? NOT_AVAILABLE : $name);

                case "dropdown_status":
                    return Ticket::getStatus($value);

                case "dropdown_assign":
                case "dropdown_users":
                case "dropdown_users_validate":
                    return getUserName($value);

                case "dropdown_groups_validate":
                    $name = Dropdown::getDropdownName('glpi_groups', $value);
                    return (($name == '&nbsp;') ? NOT_AVAILABLE : $name);

                case "dropdown_validation_percent":
                    return Dropdown::getValueWithUnit($value, '%');

                case "yesonly":
                case "yesno":
                    return Dropdown::getYesNo($value);

                case "dropdown_urgency":
                    return Ticket::getUrgencyName($value);

                case "dropdown_impact":
                    return Ticket::getImpactName($value);

                case "dropdown_priority":
                    return Ticket::getPriorityName($value);

                case "dropdown_tickettype":
                    return Ticket::getTicketTypeName($value);

                case "dropdown_management":
                    return Dropdown::getGlobalSwitch($value);

                case "dropdown_validation_status":
                    return TicketValidation::getStatus($value);

                default:
                    return $this->displayAdditionRuleActionValue($value);
            }
        }

        return $value;
    }


    /**
     * Return a value associated with a pattern associated to a criteria to display it
     *
     * @param integer $ID        the given criteria
     * @param integer $condition condition used
     * @param string  $value     the pattern
     *
     * @return string
     **/
    public function getCriteriaValue($ID, $condition, $value)
    {

        if (
            !in_array($condition, [self::PATTERN_DOES_NOT_EXISTS, self::PATTERN_EXISTS,
                self::PATTERN_IS, self::PATTERN_IS_NOT,
                self::PATTERN_NOT_UNDER, self::PATTERN_UNDER,
            ])
        ) {
            $crit = $this->getCriteria($ID);
            if (isset($crit['type'])) {
                switch ($crit['type']) {
                    case "dropdown":
                        $tmp = Dropdown::getDropdownName($crit["table"], $value, false, false);
                        //$tmp = Dropdown::getDropdownName($crit["table"], $value);
                        // return empty string to be able to check if set
                        if ($tmp == '&nbsp;') {
                            return '';
                        }
                        return $tmp;

                    case "dropdown_assign":
                    case "dropdown_users":
                        return getUserName($value);

                    case "yesonly":
                    case "yesno":
                        return Dropdown::getYesNo($value);

                    case "dropdown_impact":
                        return Ticket::getImpactName($value);

                    case "dropdown_urgency":
                        return Ticket::getUrgencyName($value);

                    case "dropdown_priority":
                        return Ticket::getPriorityName($value);

                    case "dropdown_validation_status":
                        return TicketValidation::getStatus($value);
                }
            }
        }
        return $value;
    }


    /**
     * Function used to display type specific criteria during rule's preview
     *
     * @param array $fields fields values
     **/
    public function showSpecificCriteriasForPreview($fields) {}


    /**
     * Function used to add specific params before rule processing
     *
     * @param array $params parameters
     *
     * @return array
     **/
    public function addSpecificParamsForPreview($params)
    {
        return $params;
    }


    /**
     * Criteria form used to preview rule
     *
     * @param string  $target   target of the form
     * @param integer $rules_id ID of the rule
     **/
    public function showRulePreviewCriteriasForm($target, $rules_id)
    {
        $criteria = $this->getAllCriteria();

        if ($this->getRuleWithCriteriasAndActions($rules_id, 1, 0)) {
            echo "<form name='testrule_form' id='testrule_form' method='post' action='$target'>\n";
            echo "<div class='spaced'>";
            echo "<table class='tab_cadre_fixe'>";
            echo "<tr><th colspan='3'>" . _n('Criterion', 'Criteria', Session::getPluralNumber()) . "</th></tr>";

            $type_match        = (($this->fields["match"] == self::AND_MATCHING) ? __('and') : __('or'));
            $already_displayed = [];
            $first             = true;

            //Brower all criteria
            foreach ($this->criterias as $criterion) {
                //Look for the criteria in the field of already displayed criteria :
                //if present, don't display it again
                if (!in_array($criterion->fields["criteria"], $already_displayed)) {
                    $already_displayed[] = $criterion->fields["criteria"];
                    echo "<tr class='tab_bg_1'>";
                    echo "<td>";

                    if ($first) {
                        echo "&nbsp;";
                        $first = false;
                    } else {
                        echo $type_match;
                    }

                    echo "</td>";
                    $criteria_constants = $criteria[$criterion->fields["criteria"]];
                    echo "<td>" . $criteria_constants["name"] . "</td>";
                    echo "<td>";
                    $value = "";
                    if (isset($_POST[$criterion->fields["criteria"]])) {
                        $value = $_POST[$criterion->fields["criteria"]];
                    }

                    $this->displayCriteriaSelectPattern(
                        $criterion->fields['criteria'],
                        $criterion->fields['criteria'],
                        $criterion->fields['condition'],
                        $value,
                        true
                    );
                    echo "</td></tr>\n";
                }
            }
            $this->showSpecificCriteriasForPreview($_POST);

            echo "<tr><td class='tab_bg_2 center' colspan='3'>";
            echo "<input type='submit' name='test_rule' value=\"" . _sx('button', 'Test') . "\"
                class='btn btn-primary'>";
            echo "<input type='hidden' name='" . $this->rules_id_field . "' value='$rules_id'>";
            echo "<input type='hidden' name='sub_type' value='" . $this->getType() . "'>";
            echo "</td></tr>\n";
            echo "</table></div>\n";
            Html::closeForm();
        }
    }


    /**
     * @param array $output
     *
     * @return array
     **/
    public function preProcessPreviewResults($output)
    {
        /** @var array $PLUGIN_HOOKS */
        global $PLUGIN_HOOKS;

        if (isset($PLUGIN_HOOKS['use_rules'])) {
            $params['criterias_results'] = $this->criterias_results;
            $params['rule_itemtype']     = $this->getType();
            foreach ($PLUGIN_HOOKS['use_rules'] as $plugin => $val) {
                if (!Plugin::isPluginActive($plugin)) {
                    continue;
                }
                if (is_array($val) && in_array($this->getType(), $val)) {
                    $results = Plugin::doOneHook(
                        $plugin,
                        "preProcessRulePreviewResults",
                        ['output' => $output,
                            'params' => $params,
                        ]
                    );
                    if (is_array($results)) {
                        foreach ($results as $id => $result) {
                            $output[$id] = $result;
                        }
                    }
                }
            }
        }
        return $output;
    }


    /**
     * Dropdown rules for a defined sub_type of rule
     *
     * @param array $options array of possible options:
     *    - name : string / name of the select (default is depending on itemtype)
     *    - sub_type : integer / sub_type of rule
     *    - hide_if_no_elements  : boolean / hide dropdown if there is no elements (default false)
     **/
    public static function dropdown($options = [])
    {
        $p = [
            'sub_type'     => '',
            'name'         => 'rules_id',
            'entity'       => '',
            'condition'    => 0,
            'hide_if_no_elements' => false,
        ];

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

        if ($p['sub_type'] == '') {
            return false;
        }

        $conditions = [
            'sub_type' => $p['sub_type'],
        ];
        if ($p['condition'] > 0) {
            $conditions['condition'] = ['&', (int) $p['condition']];
        }

        $p['condition'] = $conditions;
        return Dropdown::show($p['sub_type'], $p);
    }


    public function getAllCriteria()
    {

        return self::doHookAndMergeResults(
            "getRuleCriteria",
            $this->getCriterias(),
            $this->getType()
        );
    }


    public function getCriterias()
    {
        return [];
    }


    /**
     * @since 0.84
     * @return array
     */
    public function getAllActions()
    {
        return self::doHookAndMergeResults("getRuleActions", $this->getActions(), $this->getType());
    }


    public function getActions()
    {
        $actions = [];
        $collection_class = $this->getCollectionClassName();
        /** @var RuleCollection $collection */
        $collection = new $collection_class();
        if (!$collection->stop_on_first_match) {
            $actions['_stop_rules_processing'] = [
                'name' => __('Skip remaining rules'),
                'type' => 'yesonly',
            ];
        }
        return $actions;
    }


    /**
     *  Execute a hook if necessary and merge results
     *
     *  @since 0.84
     *
     * @param string $hook            the hook to execute
     * @param array $params   array  input parameters
     * @param string $itemtype        (default '')
     *
     * @return array input parameters merged with hook parameters
     **/
    public static function doHookAndMergeResults($hook, $params = [], $itemtype = '')
    {
        /** @var array $PLUGIN_HOOKS */
        global $PLUGIN_HOOKS;

        if (empty($itemtype)) {
            $itemtype = static::getType();
        }

        //Agregate all plugins criteria for this rules engine
        $toreturn = $params;
        if (isset($PLUGIN_HOOKS['use_rules'])) {
            foreach ($PLUGIN_HOOKS['use_rules'] as $plugin => $val) {
                if (!Plugin::isPluginActive($plugin)) {
                    continue;
                }
                if (is_array($val) && in_array($itemtype, $val)) {
                    $results = Plugin::doOneHook($plugin, $hook, ['rule_itemtype' => $itemtype,
                        'values'        => $params,
                    ]);
                    if (is_array($results)) {
                        foreach ($results as $id => $result) {
                            $toreturn[$id] = $result;
                        }
                    }
                }
            }
        }
        return $toreturn;
    }


    /**
     * @param string $sub_type
     *
     * @return array
     **/
    public static function getActionsByType($sub_type)
    {

        if ($rule = getItemForItemtype($sub_type)) {
            return $rule->getAllActions();
        }
        return [];
    }


    /**
     * Return all rules from database
     *
     * @param array $crit array of criteria (at least, 'field' and 'value')
     *
     * @return array of Rule objects
     **/
    public function getRulesForCriteria($crit)
    {
        /** @var \DBmysql $DB */
        global $DB;

        $rules = [];

        /// TODO : not working for SLALevels : no sub_type

        //Get all the rules whose sub_type is $sub_type and entity is $ID
        $query = [
            'SELECT' => $this->getTable() . '.id',
            'FROM'   => [
                getTableForItemType($this->ruleactionclass),
                $this->getTable(),
            ],
            'WHERE'  => [
                getTableForItemType($this->ruleactionclass) . "." . $this->rules_id_field   => new \QueryExpression(DBmysql::quoteName($this->getTable() . '.id')),
                $this->getTable() . '.sub_type'                                           => get_class($this),

            ],
        ];

        foreach ($crit as $field => $value) {
            $query['WHERE'][getTableForItemType($this->ruleactionclass) . '.' . $field] = $value;
        }

        $iterator = $DB->request($query);

        foreach ($iterator as $rule) {
            $affect_rule = new Rule();
            $affect_rule->getRuleWithCriteriasAndActions($rule["id"], 0, 1);
            $rules[]     = $affect_rule;
        }
        return $rules;
    }


    /**
     * @param integer $ID
     *
     * @return void
     **/
    public function showNewRuleForm($ID)
    {

        echo "<form method='post' action='" . Toolbox::getItemTypeFormURL('Entity') . "'>";
        echo "<table class='tab_cadre_fixe'>";
        echo "<tr><th colspan='7'>" . $this->getTitle() . "</th></tr>\n";
        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Name') . "</td><td>";
        echo Html::input('name', ['value' => '', 'size' => '33']);
        echo "</td><td>" . __('Description') . "</td><td>";
        echo Html::input('description', ['value' => '', 'size' => '33']);
        echo "</td><td>" . __('Logical operator') . "</td><td>";
        $this->dropdownRulesMatch();
        echo "</td><td class='tab_bg_2 center'>";
        echo "<input type=hidden name='sub_type' value='" . get_class($this) . "'>";
        echo "<input type=hidden name='entities_id' value='0'>";
        echo "<input type=hidden name='affectentity' value='$ID'>";
        echo "<input type=hidden name='_method' value='AddRule'>";
        echo "<input type='submit' name='execute' value=\"" . _sx('button', 'Add') . "\" class='btn btn-primary'>";
        echo "</td></tr>\n";
        echo "</table>";
        Html::closeForm();
    }


    /**
     * @param CommonDBTM $item
     *
     * @return void
     **/
    public function showAndAddRuleForm($item)
    {

        $rand    = mt_rand();
        $canedit = self::canUpdate();

        if (
            $canedit
            && ($item instanceof Entity)
        ) {
            $this->showNewRuleForm($item->getField('id'));
        }

        //Get all rules and actions
        $crit = ['field' => getForeignKeyFieldForTable($item->getTable()),
            'value' => $item->getField('id'),
        ];

        $rules = $this->getRulesForCriteria($crit);
        $nb    = count($rules);
        echo "<div class='spaced'>";

        if (!$nb) {
            echo "<table class='tab_cadre_fixehov'>";
            echo "<tr><th>" . __('No item found') . "</th>";
            echo "</tr>\n";
            echo "</table>\n";
        } else {
            if ($canedit) {
                Html::openMassiveActionsForm('mass' . get_called_class() . $rand);
                $massiveactionparams
                = ['num_displayed'
                           => min($_SESSION['glpilist_limit'], $nb),
                    'specific_actions'
                           => ['update' => _x('button', 'Update'),
                               'purge'  => _x('button', 'Delete permanently'),
                           ],
                ];
                //     'extraparams'
                //           => array('rule_class_name' => $this->getRuleClassName()));
                Html::showMassiveActions($massiveactionparams);
            }
            echo "<table class='tab_cadre_fixehov'>";
            $header_begin  = "<tr>";
            $header_top    = '';
            $header_bottom = '';
            $header_end    = '';
            if ($canedit) {
                $header_begin  .= "<th width='10'>";
                $header_top    .= Html::getCheckAllAsCheckbox('mass' . get_called_class() . $rand);
                $header_bottom .= Html::getCheckAllAsCheckbox('mass' . get_called_class() . $rand);
                $header_end    .= "</th>";
            }
            $header_end .= "<th>" . $this->getTitle() . "</th>";
            $header_end .= "<th>" . __('Description') . "</th>";
            $header_end .= "<th>" . __('Active') . "</th>";
            $header_end .= "</tr>\n";
            echo $header_begin . $header_top . $header_end;

            Session::initNavigateListItems(
                get_class($this),
                //TRANS: %1$s is the itemtype name,
                //       %2$s is the name of the item (used for headings of a list)
                sprintf(
                    __('%1$s = %2$s'),
                    $item->getTypeName(1),
                    $item->getName()
                )
            );

            foreach ($rules as $rule) {
                Session::addToNavigateListItems(get_class($this), $rule->fields["id"]);
                echo "<tr class='tab_bg_1'>";

                if ($canedit) {
                    echo "<td width='10'>";
                    Html::showMassiveActionCheckBox(__CLASS__, $rule->fields["id"]);
                    echo "</td>";
                    echo "<td><a href='" . $this->getFormURLWithID($rule->fields["id"])
                                   . "&amp;onglet=1'>" . $rule->fields["name"] . "</a></td>";
                } else {
                    echo "<td>" . $rule->fields["name"] . "</td>";
                }

                echo "<td>" . $rule->fields["description"] . "</td>";
                echo "<td>" . Dropdown::getYesNo($rule->fields["is_active"]) . "</td>";
                echo "</tr>\n";
            }
            echo $header_begin . $header_bottom . $header_end;
            echo "</table>\n";

            if ($canedit) {
                $massiveactionparams['ontop'] = false;
                Html::showMassiveActions($massiveactionparams);
                Html::closeForm();
            }
        }
        echo "</div>";
    }


    public function defineTabs($options = [])
    {

        $ong = [];
        $this->addDefaultFormTab($ong);
        $this->addStandardTab(__CLASS__, $ong, $options);
        $this->addStandardTab('Log', $ong, $options);

        return $ong;
    }


    /**
     * Add more criteria specific to this type of rule
     *
     * @return array
     **/
    public static function addMoreCriteria()
    {
        return [];
    }


    /**
     * Add more actions specific to this type of rule
     *
     * @param string $value
     *
     * @return string
     **/
    public function displayAdditionRuleActionValue($value)
    {
        return $value;
    }


    /**
     * @param $condition
     * @param $criteria
     * @param $name
     * @param $value
     * @param $test         (false by default)
     **/
    public function displayAdditionalRuleCondition($condition, $criteria, $name, $value, $test = false)
    {
        return false;
    }


    /**
     * @param array  $action
     * @param string $value          value to display (default '')
     *
     * @return boolean
     **/
    public function displayAdditionalRuleAction(array $action, $value = '')
    {
        return false;
    }


    /**
     * Clean Rule with Action or Criteria linked to an item
     *
     * @param CommonDBTM $item
     * @param string     $field      name (default is FK to item)
     * @param Rule       $ruleitem   instance of Rules of SlaLevel
     * @param string     $table      glpi_ruleactions, glpi_rulescriterias or glpi_slalevelcriterias
     * @param string     $valfield   value or pattern
     * @param string     $fieldfield criteria of field
     **/
    private static function cleanForItemActionOrCriteria(
        $item,
        $field,
        $ruleitem,
        $table,
        $valfield,
        $fieldfield
    ) {
        /** @var \DBmysql $DB */
        global $DB;

        $fieldid = getForeignKeyFieldForTable($ruleitem->getTable());

        if (empty($field)) {
            $field = getForeignKeyFieldForTable($item->getTable());
        }

        if (isset($item->input['_replace_by']) && ($item->input['_replace_by'] > 0)) {
            $DB->update(
                $table,
                [
                    $valfield => $item->input['_replace_by'],
                ],
                [
                    $valfield   => $item->getField('id'),
                    $fieldfield => ['LIKE', $field],
                ]
            );
        } else {
            $iterator = $DB->request([
                'SELECT' => [$fieldid],
                'FROM'   => $table,
                'WHERE'  => [
                    $valfield   => $item->getField('id'),
                    $fieldfield => ['LIKE', $field],
                ],
            ]);

            if (count($iterator) > 0) {
                $input['is_active'] = 0;

                foreach ($iterator as $data) {
                    $input['id'] = $data[$fieldid];
                    $ruleitem->update($input);
                }
                Session::addMessageAfterRedirect(
                    __('Rules using the object have been disabled.'),
                    true
                );
            }
        }
    }


    /**
     * Clean Rule with Action is assign to an item
     *
     * @param $item            Object
     * @param $field  string   name (default is FK to item) (default '')
     **/
    public static function cleanForItemAction($item, $field = '')
    {

        self::cleanForItemActionOrCriteria(
            $item,
            $field,
            new self(),
            'glpi_ruleactions',
            'value',
            'field'
        );

        self::cleanForItemActionOrCriteria(
            $item,
            $field,
            new SlaLevel(),
            'glpi_slalevelactions',
            'value',
            'field'
        );

        self::cleanForItemActionOrCriteria(
            $item,
            $field,
            new OlaLevel(),
            'glpi_olalevelactions',
            'value',
            'field'
        );
    }


    /**
     * Clean Rule with Criteria on an item
     *
     * @param $item            Object
     * @param $field  string   name (default is FK to item) (default '')
     **/
    public static function cleanForItemCriteria($item, $field = '')
    {

        self::cleanForItemActionOrCriteria(
            $item,
            $field,
            new self(),
            'glpi_rulecriterias',
            'pattern',
            'criteria'
        );
    }


    public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
    {

        if (!$withtemplate) {
            $nb = 0;
            switch ($item->getType()) {
                case 'Entity':
                    /** @var Entity $item */
                    if ($_SESSION['glpishow_count_on_tabs']) {
                        $types      = [];
                        $collection = new RuleRightCollection();
                        if ($collection->canList()) {
                            $types[] = 'RuleRight';
                        }
                        $collection = new RuleImportEntityCollection();
                        if ($collection->canList()) {
                            $types[] = 'RuleImportEntity';
                        }
                        $collection = new RuleMailCollectorCollection();
                        if ($collection->canList()) {
                            $types[] = 'RuleMailCollector';
                        }
                        if (count($types)) {
                            $nb = countElementsInTable(
                                ['glpi_rules', 'glpi_ruleactions'],
                                [
                                    'glpi_ruleactions.rules_id'   => new \QueryExpression(DBmysql::quoteName('glpi_rules.id')),
                                    'glpi_rules.sub_type'         => $types,
                                    'glpi_ruleactions.field'      => 'entities_id',
                                    'glpi_ruleactions.value'      => $item->getID(),
                                ]
                            );
                        }
                    }
                    return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb);

                case 'SLA':
                case 'OLA':
                    /** @var SLA|OLA $item */
                    if ($_SESSION['glpishow_count_on_tabs']) {
                        $nb = countElementsInTable(
                            'glpi_ruleactions',
                            ['field' => $item::getFieldNames($item->fields['type'])[1],
                                'value' => $item->getID(),
                            ]
                        );
                    }
                    return self::createTabEntry(self::getTypeName($nb), $nb);

                default:
                    if ($item instanceof Rule) {
                        $ong    = [];
                        $nbcriteria = 0;
                        $nbaction   = 0;
                        if ($_SESSION['glpishow_count_on_tabs']) {
                            $nbcriteria = countElementsInTable(
                                getTableForItemType($item->getRuleCriteriaClass()),
                                [$item->getRuleIdField() => $item->getID()]
                            );
                            $nbaction   = countElementsInTable(
                                getTableForItemType($item->getRuleActionClass()),
                                [$item->getRuleIdField() => $item->getID()]
                            );
                        }

                        $ong[1] = self::createTabEntry(
                            RuleCriteria::getTypeName(Session::getPluralNumber()),
                            $nbcriteria
                        );
                        $ong[2] = self::createTabEntry(
                            RuleAction::getTypeName(Session::getPluralNumber()),
                            $nbaction
                        );
                        return $ong;
                    }
            }
        }
        return '';
    }


    /**
     * @param CommonGLPI $item         CommonGLPI object
     * @param integer    $tabnum       (default 1)
     * @param integer    $withtemplate (default 0)
     **/
    public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0)
    {

        if ($item->getType() == 'Entity') {
            $collection = new RuleRightCollection();
            if ($collection->canList()) {
                $ldaprule = new RuleRight();
                $ldaprule->showAndAddRuleForm($item);
            }

            $collection = new RuleImportEntityCollection();
            if ($collection->canList()) {
                $importrule = new RuleImportEntity();
                $importrule->showAndAddRuleForm($item);
            }

            $collection = new RuleMailCollectorCollection();
            if ($collection->canList()) {
                $mailcollector = new RuleMailCollector();
                $mailcollector->showAndAddRuleForm($item);
            }
        } elseif ($item instanceof LevelAgreement) {
            $item->showRulesList();
        } elseif ($item instanceof Rule) {
            $item->getRuleWithCriteriasAndActions($item->getID(), 1, 1);
            switch ($tabnum) {
                case 1:
                    $item->showCriteriasList($item->getID());
                    break;

                case 2:
                    $item->showActionsList($item->getID());
                    break;
            }
        }

        return true;
    }


    /**
     * Generate unique id for rule based on server name, glpi directory and basetime
     *
     * @since 0.85
     *
     * @return string uuid
     **/
    public static function getUuid()
    {

        //encode uname -a, ex Linux localhost 2.4.21-0.13mdk #1 Fri Mar 14 15:08:06 EST 2003 i686
        $serverSubSha1 = substr(sha1(php_uname('a')), 0, 8);
        // encode script current dir, ex : /var/www/glpi_X
        $dirSubSha1    = substr(sha1(__FILE__), 0, 8);

        return uniqid("$serverSubSha1-$dirSubSha1-", true);
    }


    /**
     * Display debug information for current object
     *
     * @since 0.85
     *
     * @retrun void
     **/
    public function showDebug()
    {

        echo "<div class='spaced'>";
        printf(__('%1$s: %2$s'), "<b>UUID</b>", $this->fields['uuid']);
        echo "</div>";
    }

    public static function canCreate()
    {
        return static::canUpdate();
    }

    public static function canPurge()
    {
        return static::canUpdate();
    }

    public static function getIcon()
    {
        return "ti ti-book";
    }

    public function prepareInputForClone($input)
    {
        //get ranking
        $nextRanking = $this->getNextRanking();

        //Update fields of the new collection
        $input['is_active']   = 0;
        $input['ranking']     = $nextRanking;
        $input['uuid']        = static::getUuid();

        return $input;
    }

    /**
     * Create rules (initialisation).
     *
     * @param boolean $reset        Whether to reset before adding new rules, defaults to true
     * @param boolean $with_plugins Use plugins rules or not
     * @param boolean $check        Check if rule exists before creating
     *
     * @return boolean
     *
     * @FIXME Make it final in GLPI 10.1.
     * @FIXME Remove $reset, $with_plugins and $check parameters in GLPI 10.1, they are actually not used or have no effect where they are used.
     */
    public static function initRules($reset = true, $with_plugins = true, $check = false): bool
    {
        $self = new static();

        if (!$self->hasDefaultRules()) {
            return false;
        }

        if ($reset === true) {
            $rules = $self->find(['sub_type' => static::class]);
            foreach ($rules as $data) {
                $delete = $self->delete($data);
                if (!$delete) {
                    return false; // Do not continue if reset failed
                }
            }

            $check = false; // Nothing to check
        }

        $xml = simplexml_load_file(self::getDefaultRulesFilePath());
        if ($xml === false) {
            return false;
        }

        $ranking_increment = 0;
        if ($reset === false) {
            /** @var \DBmysql $DB */
            global $DB;
            $ranking_increment = $DB->request([
                'SELECT' => ['MAX' => 'ranking AS rank'],
                'FROM'   => static::getTable(),
                'WHERE'  => ['sub_type' => static::class],
            ])->current()['rank'];
        }

        $has_errors = false;
        foreach ($xml->xpath('/rules/rule') as $rulexml) {
            if ((string) $rulexml->sub_type !== self::getType()) {
                trigger_error(sprintf('Unexpected rule type for rule `%s`.', (string) $rulexml->uuid), E_USER_WARNING);
                $has_errors = true;
                continue;
            }
            if ((string) $rulexml->entities_id !== 'Root entity') {
                trigger_error(sprintf('Unexpected entity value for rule `%s`.', (string) $rulexml->uuid), E_USER_WARNING);
                $has_errors = true;
                continue;
            }

            $rule = new static();

            if ($check === true && $rule->getFromDBByCrit(['uuid' => (string) $rulexml->uuid])) {
                // Rule already exists, ignore it.
                continue;
            }

            $rule_input = [
                'entities_id'  => 0, // Always add default rules to root entity
                'sub_type'     => self::getType(),
                'ranking'      => (int) $rulexml->ranking + $ranking_increment,
                'name'         => (string) $rulexml->name,
                'description'  => (string) $rulexml->description,
                'match'        => (string) $rulexml->match,
                'is_active'    => (int) $rulexml->is_active,
                'comment'      => (string) $rulexml->comment,
                'is_recursive' => (int) $rulexml->is_recursive,
                'uuid'         => (string) $rulexml->uuid,
                'condition'    => (string) $rulexml->condition,
            ];

            $rule_id = $rule->add(Sanitizer::sanitize($rule_input));
            if ($rule_id === false) {
                trigger_error(
                    sprintf('Unable to create rule `%s`.', (string) $rulexml->uuid),
                    E_USER_WARNING
                );
                $has_errors = true;
                continue;
            }

            $available_criteria = $rule->getCriterias();
            $available_actions   = $rule->getActions();

            foreach ($rulexml->xpath('./rulecriteria') as $criteriaxml) {
                if (!array_key_exists((string) $criteriaxml->criteria, $available_criteria)) {
                    trigger_error(
                        sprintf(
                            'Unknown criteria `%s` for rule `%s`.',
                            (string) $criteriaxml->criteria,
                            (string) $rulexml->uuid
                        ),
                        E_USER_WARNING
                    );
                    $has_errors = true;
                    continue;
                }
                $criteria_input = [
                    'rules_id'  => $rule_id,
                    'criteria'  => (string) $criteriaxml->criteria,
                    'condition' => (string) $criteriaxml->condition,
                    'pattern'   => (string) $criteriaxml->pattern,
                ];

                $criteria = new RuleCriteria();
                $criteria_id = $criteria->add(Sanitizer::sanitize($criteria_input));
                if ($criteria_id === false) {
                    trigger_error(
                        sprintf('Unable to create criteria for rule `%s`.', (string) $rulexml->uuid),
                        E_USER_WARNING
                    );
                    $has_errors = true;
                    continue;
                }
            }

            foreach ($rulexml->xpath('./ruleaction') as $actionxml) {
                if (!array_key_exists((string) $actionxml->field, $available_actions)) {
                    trigger_error(
                        sprintf(
                            'Unknown action field `%s` for rule `%s`.',
                            (string) $actionxml->field,
                            (string) $rulexml->uuid
                        ),
                        E_USER_WARNING
                    );
                    $has_errors = true;
                    continue;
                }
                $action_input = [
                    'rules_id'    => $rule_id,
                    'action_type' => (string) $actionxml->action_type,
                    'field'       => (string) $actionxml->field,
                    'value'       => (string) $actionxml->value,
                ];

                $action = new RuleAction();
                $action_id = $action->add(Sanitizer::sanitize($action_input));
                if ($action_id === false) {
                    trigger_error(
                        sprintf('Unable to create action for rule `%s`.', (string) $rulexml->uuid),
                        E_USER_WARNING
                    );
                    $has_errors = true;
                    continue;
                }
            }
        }

        return !$has_errors;
    }

    /**
     * Check whether default rules exists.
     *
     * @return boolean
     */
    final public static function hasDefaultRules(): bool
    {
        return file_exists(self::getDefaultRulesFilePath());
    }

    /**
     * Returns default rules file path.
     *
     * @return string
     */
    private static function getDefaultRulesFilePath(): string
    {
        return sprintf(
            '%s/resources/Rules/%s.xml',
            GLPI_ROOT,
            static::class
        );
    }
}
