<?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\Application\ErrorHandler;
use Glpi\Application\View\TemplateRenderer;
use Glpi\Toolbox\Filesystem;
use Glpi\Toolbox\Sanitizer;

/**
 *  Class used to manage Auth LDAP config
 */
class AuthLDAP extends CommonDBTM
{
    public const SIMPLE_INTERFACE = 'simple';
    public const EXPERT_INTERFACE = 'expert';

    public const ACTION_IMPORT      = 0;
    public const ACTION_SYNCHRONIZE = 1;
    public const ACTION_ALL         = 2;

    public const USER_IMPORTED      = 0;
    public const USER_SYNCHRONIZED  = 1;
    public const USER_DELETED_LDAP  = 2;
    public const USER_RESTORED_LDAP = 3;

    //Import user by giving his login
    public const IDENTIFIER_LOGIN = 'login';

    //Import user by giving his email
    public const IDENTIFIER_EMAIL = 'email';

    public const GROUP_SEARCH_USER    = 0;
    public const GROUP_SEARCH_GROUP   = 1;
    public const GROUP_SEARCH_BOTH    = 2;

    /**
     * Deleted user strategy: preserve user.
     * @var integer
     */
    public const DELETED_USER_PRESERVE = 0;

    /**
     * Deleted user strategy: put user in trashbin.
     * @var integer
     */
    public const DELETED_USER_DELETE = 1;

    /**
     * Deleted user strategy: withdraw dynamic authorizations and groups.
     * @var integer
     */
    public const DELETED_USER_WITHDRAWDYNINFO = 2;

    /**
     * Deleted user strategy: disable user.
     * @var integer
     */
    public const DELETED_USER_DISABLE = 3;

    /**
     * Deleted user strategy: disable user and withdraw dynamic authorizations and groups.
     * @var integer
     */
    public const DELETED_USER_DISABLEANDWITHDRAWDYNINFO = 4;

    /**
     * Deleted user strategy: disable user and withdraw groups.
     * @var integer
     */
    public const DELETED_USER_DISABLEANDDELETEGROUPS = 5;

    /**
     * Restored user strategy: Make no change to GLPI user
     * @var integer
     * @since 10.0.0
     */
    public const RESTORED_USER_PRESERVE = 0;

    /**
     * Restored user strategy: Restore user from trash
     * @var integer
     * @since 10.0.0
     */
    public const RESTORED_USER_RESTORE = 1;

    /**
     * Restored user strategy: Re-enable user
     * @var integer
     * @since 10.0.0
     */
    public const RESTORED_USER_ENABLE  = 3;

    // From CommonDBTM
    public $dohistory = true;

    public static $rightname = 'config';

    //connection caching stuff
    public static $conn_cache = [];

    public static $undisclosedFields = [
        'rootdn_passwd',
    ];

    public static function getTypeName($nb = 0)
    {
        return _n('LDAP directory', 'LDAP directories', $nb);
    }

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

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

    public function post_getEmpty()
    {

        $this->fields['port']                        = '389';
        $this->fields['condition']                   = '';
        $this->fields['login_field']                 = 'uid';
        $this->fields['sync_field']                  = null;
        $this->fields['use_tls']                     = 0;
        $this->fields['group_field']                 = '';
        $this->fields['group_condition']             = '';
        $this->fields['group_search_type']           = self::GROUP_SEARCH_USER;
        $this->fields['group_member_field']          = '';
        $this->fields['email1_field']                = 'mail';
        $this->fields['email2_field']                = '';
        $this->fields['email3_field']                = '';
        $this->fields['email4_field']                = '';
        $this->fields['realname_field']              = 'sn';
        $this->fields['firstname_field']             = 'givenname';
        $this->fields['phone_field']                 = 'telephonenumber';
        $this->fields['phone2_field']                = '';
        $this->fields['mobile_field']                = '';
        $this->fields['registration_number_field']   = '';
        $this->fields['comment_field']               = '';
        $this->fields['title_field']                 = '';
        $this->fields['use_dn']                      = 0;
        $this->fields['use_bind']                    = 1;
        $this->fields['picture_field']               = '';
        $this->fields['responsible_field']           = '';
        $this->fields['can_support_pagesize']        = 0;
        $this->fields['pagesize']                    = 0;
        $this->fields['ldap_maxlimit']               = 0;
    }


    /**
     * Preconfig datas for standard system
     *
     * @param string $type type of standard system : AD
     *
     * @return void
     */
    public function preconfig($type)
    {

        switch ($type) {
            case 'AD':
                $this->fields['port']                      = "389";
                $this->fields['condition']
                 = '(&(objectClass=user)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))';
                $this->fields['login_field']               = 'samaccountname';
                $this->fields['sync_field']                = 'objectguid';
                $this->fields['use_tls']                   = 0;
                $this->fields['group_field']               = 'memberof';
                $this->fields['group_condition']
                 = '(&(objectClass=user)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))';
                $this->fields['group_search_type']         = self::GROUP_SEARCH_USER;
                $this->fields['group_member_field']        = '';
                $this->fields['email1_field']              = 'mail';
                $this->fields['email2_field']              = '';
                $this->fields['email3_field']              = '';
                $this->fields['email4_field']              = '';
                $this->fields['realname_field']            = 'sn';
                $this->fields['firstname_field']           = 'givenname';
                $this->fields['phone_field']               = 'telephonenumber';
                $this->fields['phone2_field']              = 'othertelephone';
                $this->fields['mobile_field']              = 'mobile';
                $this->fields['registration_number_field'] = 'employeenumber';
                $this->fields['comment_field']             = 'info';
                $this->fields['title_field']               = 'title';
                $this->fields['entity_field']              = 'ou';
                $this->fields['entity_condition']          = '(objectclass=organizationalUnit)';
                $this->fields['use_dn']                    = 1;
                $this->fields['can_support_pagesize']      = 1;
                $this->fields['pagesize']                  = '1000';
                $this->fields['picture_field']             = '';
                $this->fields['responsible_field']         = 'manager';
                break;
            case 'OpenLDAP':
                $this->fields['port']                      = "389";
                $this->fields['condition']                 = '(objectClass=inetOrgPerson)';
                $this->fields['login_field']               = 'uid';
                $this->fields['sync_field']                = 'entryuuid';
                $this->fields['use_tls']                   = 0;
                $this->fields['group_field']               = '';
                $this->fields['group_condition']           = '(objectClass=inetOrgPerson)';
                $this->fields['group_search_type']         = self::GROUP_SEARCH_GROUP;
                $this->fields['group_member_field']        = 'member';
                $this->fields['email1_field']              = 'mail';
                $this->fields['email2_field']              = '';
                $this->fields['email3_field']              = '';
                $this->fields['email4_field']              = '';
                $this->fields['realname_field']            = 'sn';
                $this->fields['firstname_field']           = 'givenname';
                $this->fields['phone_field']               = 'telephonenumber';
                $this->fields['phone2_field']              = 'homephone';
                $this->fields['mobile_field']              = 'mobile';
                $this->fields['registration_number_field'] = 'employeenumber';
                $this->fields['comment_field']             = 'description';
                $this->fields['title_field']               = 'title';
                $this->fields['entity_field']              = 'ou';
                $this->fields['entity_condition']          = '(objectClass=organizationalUnit)';
                $this->fields['use_dn']                    = 1;
                $this->fields['can_support_pagesize']      = 1;
                $this->fields['pagesize']                  = '1000';
                $this->fields['picture_field']             = 'jpegphoto';
                $this->fields['responsible_field']         = 'manager';
                $this->fields['category_field']            = 'businesscategory';
                $this->fields['language_field']            = 'preferredlanguage';
                $this->fields['location_field']            = 'l';
                break;

            default:
                $this->post_getEmpty();
        }
    }

    public function prepareInputForUpdate($input)
    {

        if (isset($input["rootdn_passwd"])) {
            if (empty($input["rootdn_passwd"])) {
                unset($input["rootdn_passwd"]);
            } else {
                $input["rootdn_passwd"] = (new GLPIKey())->encrypt($input["rootdn_passwd"]);
            }
        }

        if (isset($input["_blank_passwd"]) && $input["_blank_passwd"]) {
            $input['rootdn_passwd'] = '';
        }

        // Set attributes in lower case
        if (count($input)) {
            foreach ($input as $key => $val) {
                if (preg_match('/_field$/', $key)) {
                    $input[$key] = Toolbox::strtolower($val);
                }
            }
        }

        //do not permit to override sync_field
        if (
            $this->isSyncFieldEnabled()
            && isset($input['sync_field'])
            && $this->isSyncFieldUsed()
        ) {
            if ($input['sync_field'] == $this->fields['sync_field']) {
                unset($input['sync_field']);
            } else {
                Session::addMessageAfterRedirect(
                    __('Synchronization field cannot be changed once in use.'),
                    false,
                    ERROR
                );
                return false;
            };
        }

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

        return $input;
    }

    public static function getSpecificValueToDisplay($field, $values, array $options = [])
    {

        if (!is_array($values)) {
            $values = [$field => $values];
        }
        switch ($field) {
            case 'group_search_type':
                return self::getGroupSearchTypeName($values[$field]);
        }
        return parent::getSpecificValueToDisplay($field, $values, $options);
    }

    public static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = [])
    {

        if (!is_array($values)) {
            $values = [$field => $values];
        }
        $options['display'] = false;
        switch ($field) {
            case 'group_search_type':
                $options['value'] = $values[$field];
                $options['name']  = $name;
                return self::dropdownGroupSearchType($options);
        }
        return parent::getSpecificValueToSelect($field, $name, $values, $options);
    }

    public static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, array $ids)
    {
        $input = $ma->getInput();

        switch ($ma->getAction()) {
            case 'import_group':
                $group = new Group();
                if (
                    !Session::haveRight("user", User::UPDATEAUTHENT)
                    || !$group->canGlobal(UPDATE)
                ) {
                    $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_NORIGHT);
                    $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
                    return;
                }
                foreach ($ids as $id) {
                    if (isset($input["dn"][$id])) {
                        $group_dn = $input["dn"][$id];
                        if (isset($input["ldap_import_entities"][$id])) {
                            $entity = $input["ldap_import_entities"][$id];
                        } else {
                            $entity = $_SESSION["glpiactive_entity"];
                        }
                        // Is recursive is in the main form and thus, don't pass through
                        // zero_on_empty mechanism inside massive action form ...
                        $is_recursive = (empty($input['ldap_import_recursive'][$id]) ? 0 : 1);
                        $options      = ['authldaps_id' => $_SESSION['ldap_server'],
                            'entities_id'  => $entity,
                            'is_recursive' => $is_recursive,
                            'type'         => $input['ldap_import_type'][$id],
                        ];
                        if (AuthLDAP::ldapImportGroup($group_dn, $options)) {
                            $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                        } else {
                            $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                            $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION, $group_dn));
                        }
                    }
                    // Clean history as id does not correspond to group
                    $_SESSION['glpimassiveactionselected'] = [];
                }
                return;

            case 'import':
            case 'sync':
                if (!Session::haveRight("user", User::IMPORTEXTAUTHUSERS)) {
                    $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_NORIGHT);
                    $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
                    return;
                }
                foreach ($ids as $id) {
                    if (
                        AuthLDAP::ldapImportUserByServerId(
                            ['method' => AuthLDAP::IDENTIFIER_LOGIN,
                                'value'  => $id,
                            ],
                            $_SESSION['ldap_import']['mode'],
                            $_SESSION['ldap_import']['authldaps_id'],
                            true
                        )
                    ) {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                    } else {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                        $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION, $id));
                    }
                }
                return;
        }

        parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
    }

    /**
     * Print the auth ldap form
     *
     * @param integer $ID      ID of the item
     * @param array   $options Options
     *     - target for the form
     *
     * @return void|boolean (display) Returns false if there is a rights error.
     */
    public function showForm($ID, array $options = [])
    {

        if (!Config::canUpdate()) {
            return false;
        }

        // warning and no form if can't read keyfile
        $glpi_encryption_key = new GLPIKey();
        if ($glpi_encryption_key->hasReadErrors()) {
            $glpi_encryption_key->showReadErrors();

            return false;
        }

        if (empty($ID)) {
            $this->getEmpty();
            if (isset($options['preconfig'])) {
                $this->preconfig($options['preconfig']);
            }
        } else {
            $this->getFromDB($ID);
        }

        if (Toolbox::canUseLdap()) {
            $this->showFormHeader($options);
            if (empty($ID)) {
                $target = $this->getFormURL();
                echo "<tr class='tab_bg_2'><td>" . __('Preconfiguration') . "</td> ";
                echo "<td colspan='3'>";
                echo "<a href='$target?preconfig=AD'>" . __('Active Directory') . "</a>";
                echo "&nbsp;&nbsp;/&nbsp;&nbsp;";
                echo "<a href='$target?preconfig=OpenLDAP'>" . __('OpenLDAP') . "</a>";
                echo "&nbsp;&nbsp;/&nbsp;&nbsp;";
                echo "<a href='$target?preconfig=default'>" . __('Default values');
                echo "</a></td></tr>";
            }
            echo "<tr class='tab_bg_1'><td><label for='name'>" . __('Name') . "</label></td>";
            echo "<td><input type='text' id='name' name='name' value='" . Html::cleanInputText($this->fields["name"]) . "' class='form-control'></td>";
            if ($ID > 0) {
                echo "<td>" . __('Last update') . "</td><td>" . Html::convDateTime($this->fields["date_mod"]);
            } else {
                echo "<td colspan='2'>&nbsp;";
            }
            echo "</td></tr>";

            $defaultrand = mt_rand();
            echo "<tr class='tab_bg_1'><td><label for='dropdown_is_default$defaultrand'>" . __('Default server') . "</label></td>";
            echo "<td>";
            Dropdown::showYesNo('is_default', $this->fields['is_default'], -1, ['rand' => $defaultrand]);
            echo "</td>";
            $activerand = mt_rand();
            echo "<td><label for='dropdown_is_active$activerand'>" . __('Active') . "</label></td>";
            echo "<td>";
            Dropdown::showYesNo('is_active', $this->fields['is_active'], -1, ['rand' => $activerand]);
            echo "</td></tr>";

            echo "<tr class='tab_bg_1'><td><label for='host'>" . __('Server') . "</label></td>";
            echo "<td><input type='text' id='host' name='host' value='" . Html::cleanInputText($this->fields["host"]) . "' class='form-control'></td>";
            echo "<td><label for='port'>" . __('Port (default=389)') . "</label></td>";
            echo "<td><input id='port' type='number' id='port' name='port' value='" . Html::cleanInputText($this->fields["port"]) . "' class='form-control'>";
            echo "</td></tr>";

            echo "<tr class='tab_bg_1'><td><label for='condition'>" . __('Connection filter') . "</label></td>";
            echo "<td colspan='3'>";
            echo "<textarea class='form-control' id='condition' name='condition'>" . $this->fields["condition"];
            echo "</textarea>";
            echo "</td></tr>";

            echo "<tr class='tab_bg_1'><td><label for='basedn'>" . __('BaseDN') . "</label></td>";
            echo "<td colspan='3'>";
            echo "<input type='text' id='basedn' name='basedn' size='100' value=\"" . Html::cleanInputText($this->fields["basedn"]) . "\" class='form-control'>";
            echo "</td></tr>";

            echo "<tr class='tab_bg_1'><td><label for='use_bind'>";
            echo __('Use bind') . "</label>&nbsp;";
            Html::showToolTip(__("Indicates whether a simple bind operation should be used during connection to LDAP server. Disabling this behaviour can be required when LDAPS bind is used."));
            echo "</td>";
            echo "<td colspan='3'>";
            $rand_use_bind = mt_rand();
            Dropdown::showYesNo('use_bind', $this->fields["use_bind"], -1, [
                'rand' => $rand_use_bind,
            ]);
            echo Html::scriptBlock("$(document).ready(function() {
                $('#dropdown_use_bind$rand_use_bind').on('select2:select', function() {
                    if ($(this).val() == 1) {
                        $('#rootdn_block, #rootdn_passwd_block')
                            .addClass('d-table-row')
                            .removeClass('d-none');
                    } else {
                        $('#rootdn_block, #rootdn_passwd_block')
                            .removeClass('d-table-row')
                            .addClass('d-none');
                    }
                });
            });");
            echo "</td></tr>";

            $rootdn_class = 'd-none';
            if ($this->fields["use_bind"]) {
                $rootdn_class = 'd-table-row';
            }
            echo "<tr class='tab_bg_1 $rootdn_class' id='rootdn_block'><td><label for='rootdn'>" . __('RootDN (for non anonymous binds)') . "</label></td>";
            echo "<td colspan='3'><input type='text' name='rootdn' id='rootdn' size='100' value=\"" .
                Html::cleanInputText($this->fields["rootdn"]) . "\" class='form-control'>";
            echo "</td></tr>";

            echo "<tr class='tab_bg_1 $rootdn_class' id='rootdn_passwd_block'><td><label for='rootdn_passwd'>" .
            __('Password (for non-anonymous binds)') . "</label></td>";
            echo "<td><input type='password' id='rootdn_passwd' name='rootdn_passwd' value='' autocomplete='new-password' class='form-control'>";
            if ($ID) {
                echo "<input type='checkbox' name='_blank_passwd' id='_blank_passwd'>&nbsp;"
                . "<label for='_blank_passwd'>" . __('Clear') . "</label>";
            }
            echo "</td>";
            echo "<td rowspan='3'><label for='comment'>" . __('Comments') . "</label></td>";
            echo "<td rowspan='3' class='middle'>";
            echo "<textarea class='form-control' name='comment' id='comment'>" . $this->fields["comment"] . "</textarea>";
            echo "</td></tr>";

            echo "<tr class='tab_bg_1'>";
            echo "<td><label for='login_field'>" . __('Login field') . "</label></td>";
            echo "<td><input type='text' id='login_field' name='login_field' value='" . Html::cleanInputText($this->fields["login_field"]) . "' class='form-control'>";
            echo "</td></tr>";

            $info_message = __s('Synchronization field cannot be changed once in use.');
            echo "<tr class='tab_bg_1'>";
            echo "<td><label for='sync_field'>" . __('Synchronization field') . "<i class='pointer fa fa-info' title='$info_message'></i></td>";
            echo "<td><input type='text' id='sync_field' name='sync_field' value='" . Html::cleanInputText($this->fields["sync_field"]) . "' title='$info_message' class='form-control'";
            if ($this->isSyncFieldEnabled() && $this->isSyncFieldUsed()) {
                echo " disabled='disabled'";
            }
            echo ">";
            echo "</td></tr>";

            //Fill fields when using preconfiguration models
            if (!$ID) {
                $hidden_fields = ['comment_field', 'email1_field', 'email2_field',
                    'email3_field', 'email4_field', 'entity_condition',
                    'entity_field', 'firstname_field', 'group_condition',
                    'group_field', 'group_member_field', 'group_search_type',
                    'mobile_field', 'phone_field', 'phone2_field',
                    'realname_field', 'registration_number_field', 'title_field',
                    'use_dn', 'use_tls', 'picture_field', 'responsible_field',
                    'category_field', 'language_field', 'location_field',
                    'can_support_pagesize', 'pagesize',
                ];

                foreach ($hidden_fields as $hidden_field) {
                    echo "<input type='hidden' name='$hidden_field' value='" .
                      Html::cleanInputText($this->fields[$hidden_field]) . "'>";
                }
            }

            echo "</td></tr>";

            $this->showFormButtons($options);
        } else {
            echo "<div class='center'>&nbsp;<table class='tab_cadre_fixe'>";
            echo "<tr><th colspan='2'>" . self::getTypeName(1) . "</th></tr>";
            echo "<tr class='tab_bg_2'><td class='center'>";
            echo "<p class='red'>" . sprintf(__('%s extension is missing'), 'LDAP') . "</p>";
            echo "<p>" . __('Impossible to use LDAP as external source of connection') . "</p>" .
              "</td></tr></table>";

            echo "<p><strong>" . GLPINetwork::getSupportPromoteMessage() . "</strong></p>";
            echo "</div>";
        }
    }

    /**
     * Show advanced config form
     *
     * @return void
     */
    public function showFormAdvancedConfig()
    {
        // warning and no form if can't read keyfile
        $glpi_encryption_key = new GLPIKey();
        if ($glpi_encryption_key->hasReadErrors()) {
            $glpi_encryption_key->showReadErrors();

            return;
        }

        $ID = $this->getField('id');
        $hidden = '';

        echo "<div class='center'>";
        echo "<form method='post' action='" . Toolbox::getItemTypeFormURL(__CLASS__) . "'>";
        echo "<table class='tab_cadre_fixe'>";

        echo "<tr class='tab_bg_2'><th colspan='4'>";
        echo "<input type='hidden' name='id' value='$ID'>" . __('Advanced information') . "</th></tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Use TLS') . "</td><td>";
        if (function_exists("ldap_start_tls")) {
            Dropdown::showYesNo('use_tls', $this->fields["use_tls"]);
        } else {
            echo "<input type='hidden' name='use_tls' value='0'>" . __('ldap_start_tls does not exist');
        }
        echo "</td>";
        echo "<td>" . __('LDAP directory time zone') . "</td><td>";
        Dropdown::showGMT("time_offset", $this->fields["time_offset"]);
        echo"</td></tr>";

        if (self::isLdapPageSizeAvailable(false, false)) {
            echo "<tr class='tab_bg_1'>";
            echo "<td>" . __('Use paged results') . "</td><td>";
            Dropdown::showYesNo('can_support_pagesize', $this->fields["can_support_pagesize"]);
            echo "</td>";
            echo "<td>" . __('Page size') . "</td><td>";
            Dropdown::showNumber("pagesize", ['value' => $this->fields['pagesize'],
                'min'   => 100,
                'max'   => 100000,
                'step'  => 100,
            ]);
            echo"</td></tr>";

            echo "<tr class='tab_bg_1'>";
            echo "<td>" . __('Maximum number of results') . "</td><td>";
            Dropdown::showNumber('ldap_maxlimit', ['value' => $this->fields['ldap_maxlimit'],
                'min'   => 100,
                'max'   => 999999,
                'step'  => 100,
                'toadd' => [0 => __('Unlimited')],
            ]);
            echo "</td><td colspan='2'></td></tr>";
        } else {
            $hidden .= "<input type='hidden' name='can_support_pagesize' value='0'>";
            $hidden .= "<input type='hidden' name='pagesize' value='0'>";
            $hidden .= "<input type='hidden' name='ldap_maxlimit' value='0'>";
        }

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('How LDAP aliases should be handled') . "</td><td colspan='4'>";
        $alias_options = [
            LDAP_DEREF_NEVER     => __('Never dereferenced (default)'),
            LDAP_DEREF_ALWAYS    => __('Always dereferenced'),
            LDAP_DEREF_SEARCHING => __('Dereferenced during the search (but not when locating)'),
            LDAP_DEREF_FINDING   => __('Dereferenced when locating (not during the search)'),
        ];
        Dropdown::showFromArray(
            "deref_option",
            $alias_options,
            ['value' => $this->fields["deref_option"]]
        );
        echo"</td></tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Domain name used by inventory tool for link the user') . "</td>";
        echo "<td colspan='3'>";
        echo Html::input('inventory_domain', ['value' => $this->fields['inventory_domain'], 'size' => 100]);
        echo "</td></tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('TLS Certfile') . "</td><td>";
        echo "<input type='text' name='tls_certfile' class='form-control' id='tls_certfile' value='" . $this->fields["tls_certfile"] . "'>";
        echo "</td>";
        echo "<td>" . __('TLS Keyfile') . "</td><td>";
        echo "<input type='text' name='tls_keyfile' class='form-control' id='tls_keyfile' value='" . $this->fields["tls_keyfile"] . "'>";
        echo "</td>";
        echo "</tr>";

        echo "<tr class='tab_bg_1'><td><label for='timeout'>" . __('Timeout') . "</label></td>";
        echo "<td colspan='3'>";

        Dropdown::showNumber('timeout', ['value'  => $this->fields["timeout"],
            'min'    => 1,
            'max'    => 30,
            'step'   => 1,
            'toadd'  => [0 => __('No timeout')],
        ]);
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'><td class='center' colspan='4'>";
        echo "<input type='submit' name='update' class='btn btn-primary' value=\"" . __s('Save') . "\">";
        echo $hidden;
        echo "</td></tr>";

        echo "</table>";
        Html::closeForm();
        echo "</div>";
    }

    /**
     * Show config replicates form
     *
     * @return void
     */
    public function showFormReplicatesConfig()
    {
        /** @var \DBmysql $DB */
        global $DB;

        $ID     = $this->getField('id');
        $target = $this->getFormURL();
        $rand   = mt_rand();

        AuthLdapReplicate::addNewReplicateForm($target, $ID);

        $iterator = $DB->request([
            'FROM'   => 'glpi_authldapreplicates',
            'WHERE'  => [
                'authldaps_id' => $ID,
            ],
            'ORDER'  => ['name'],
        ]);

        if (($nb = count($iterator)) > 0) {
            echo "<br>";

            echo "<div class='center'>";
            Html::openMassiveActionsForm('massAuthLdapReplicate' . $rand);
            $massiveactionparams = ['num_displayed' => min($_SESSION['glpilist_limit'], $nb),
                'container'     => 'massAuthLdapReplicate' . $rand,
            ];
            Html::showMassiveActions($massiveactionparams);
            echo "<input type='hidden' name='id' value='$ID'>";
            echo "<table class='tab_cadre_fixehov'>";
            echo "<tr class='noHover'>" .
              "<th colspan='4'>" . __('List of LDAP directory replicates') . "</th></tr>";

            if (isset($_SESSION["LDAP_TEST_MESSAGE"])) {
                echo "<tr class='tab_bg_2'><td class='center' colspan='4'>";
                echo $_SESSION["LDAP_TEST_MESSAGE"];
                echo"</td></tr>";
                unset($_SESSION["LDAP_TEST_MESSAGE"]);
            }
            $header_begin   = "<tr>";
            $header_top     = "<th>" . Html::getCheckAllAsCheckbox('massAuthLdapReplicate' . $rand) . "</th>";
            $header_bottom  = "<th>" . Html::getCheckAllAsCheckbox('massAuthLdapReplicate' . $rand) . "</th>";
            $header_end     = "<th class='center b'>" . __('Name') . "</th>";
            $header_end    .= "<th class='center b'>" . _n('Replicate', 'Replicates', 1) . "</th>";
            $header_end    .= "<th class='center b'>" . __('Timeout') . "</th>" .
              "<th class='center'></th></tr>";
            echo $header_begin . $header_top . $header_end;

            foreach ($iterator as $ldap_replicate) {
                echo "<tr class='tab_bg_1'><td class='center' width='10'>";
                Html::showMassiveActionCheckBox('AuthLdapReplicate', $ldap_replicate["id"]);
                echo "</td>";
                echo "<td class='center'>" . $ldap_replicate["name"] . "</td>";
                echo "<td class='center'>" . sprintf(
                    __('%1$s: %2$s'),
                    $ldap_replicate["host"],
                    $ldap_replicate["port"]
                );
                echo "</td>";
                echo "<td class='center'>" . $ldap_replicate["timeout"] . "</td>";
                echo "<td class='center'>";
                Html::showSimpleForm(
                    static::getFormURL(),
                    'test_ldap_replicate',
                    _sx('button', 'Test'),
                    ['id'                => $ID,
                        'ldap_replicate_id' => $ldap_replicate["id"],
                    ]
                );
                echo "</td></tr>";
            }
            echo $header_begin . $header_bottom . $header_end;
            echo "</table>";
            $massiveactionparams['ontop'] = false;
            Html::showMassiveActions($massiveactionparams);

            Html::closeForm();
            echo "</div>";
        }
    }

    /**
     * Build a dropdown
     *
     * @since 0.84
     *
     * @param array $options Options
     *
     * @return string
     */
    public static function dropdownGroupSearchType(array $options)
    {

        $p = [
            'name'    => 'group_search_type',
            'value'   => self::GROUP_SEARCH_USER,
            'display' => true,
        ];

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

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

    /**
     * Get the possible value for contract alert
     *
     * @since 0.83
     *
     * @param integer $val if not set, ask for all values, else for 1 value (default NULL)
     *
     * @return array|string
     */
    public static function getGroupSearchTypeName($val = null)
    {
        $tmp = [
            self::GROUP_SEARCH_USER    => __('In users'),
            self::GROUP_SEARCH_GROUP   => __('In groups'),
            self::GROUP_SEARCH_BOTH    => __('In users and groups'),
        ];

        if (is_null($val)) {
            return $tmp;
        }
        if (isset($tmp[$val])) {
            return $tmp[$val];
        }
        return NOT_AVAILABLE;
    }

    /**
     * Show group config form
     *
     * @return void
     */
    public function showFormGroupsConfig()
    {

        $ID = $this->getField('id');

        echo "<div class='center'>";
        echo "<form method='post' action='" . Toolbox::getItemTypeFormURL(__CLASS__) . "'>";
        echo "<input type='hidden' name='id' value='$ID'>";
        echo "<table class='tab_cadre_fixe'>";

        echo "<tr><th class='center' colspan='4'>" . __('Belonging to groups') . "</th></tr>";

        echo "<tr class='tab_bg_1'><td>" . __('Search type') . "</td><td>";
        self::dropdownGroupSearchType(['value' => $this->fields["group_search_type"]]);
        echo "</td>";
        echo "<td>" . __('User attribute containing its groups') . "</td>";
        echo "<td><input type='text' name='group_field' class='form-control' value='" . $this->fields["group_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_1'><td>" . __('Filter to search in groups') . "</td><td colspan='3'>";
        echo "<textarea class='form-control' name='group_condition'>" . $this->fields["group_condition"];
        echo "</textarea>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_1'><td>" . __('Group attribute containing its users') . "</td>";
        echo "<td><input type='text' class='form-control' name='group_member_field' value='" .
                 $this->fields["group_member_field"] . "'></td>";
        echo "<td>" . __('Use DN in the search') . "</td>";
        echo "<td>";
        Dropdown::showYesNo("use_dn", $this->fields["use_dn"]);
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'><td class='center' colspan='4'>";
        echo "<input type='submit' name='update' class='btn btn-primary' value=\"" . __s('Save') . "\">";
        echo "</td></tr>";
        echo "</table>";
        Html::closeForm();
        echo "</div>";
    }

    /**
     * Show ldap test form
     *
     * @return void
     */
    public function showFormTestLDAP()
    {

        $ID = $this->getField('id');

        if ($ID > 0) {
            echo "<div class='center'>";
            echo "<form method='post' action='" . Toolbox::getItemTypeFormURL(__CLASS__) . "'>";
            echo "<input type='hidden' name='id' value='$ID'>";
            echo "<table class='tab_cadre_fixe'>";
            echo "<tr><th colspan='4'>" . __('Test of connection to LDAP directory') . "</th></tr>";

            if (isset($_SESSION["LDAP_TEST_MESSAGE"])) {
                echo "<tr class='tab_bg_2'><td class='center' colspan='4'>";
                echo $_SESSION["LDAP_TEST_MESSAGE"];
                echo"</td></tr>";
                unset($_SESSION["LDAP_TEST_MESSAGE"]);
            }

            echo "<tr class='tab_bg_2'><td class='center' colspan='4'>";
            echo "<input type='submit' name='test_ldap' class='btn btn-primary' value=\"" .
                _sx('button', 'Test') . "\">";
            echo "</td></tr>";
            echo "</table>";
            Html::closeForm();
            echo "</div>";
        }
    }

    /**
     * Show user config form
     *
     * @return void
     */
    public function showFormUserConfig()
    {

        $ID = $this->getField('id');

        echo "<div class='center'>";
        echo "<form method='post' action='" . Toolbox::getItemTypeFormURL(__CLASS__) . "'>";
        echo "<input type='hidden' name='id' value='$ID'>";
        echo "<table class='tab_cadre_fixe'>";

        echo "<tr class='tab_bg_1'>";
        echo "<th class='center' colspan='4'>" . __('Binding to the LDAP directory') . "</th></tr>";

        echo "<tr class='tab_bg_2'><td>" . __('Surname') . "</td>";
        echo "<td><input type='text' class='form-control' name='realname_field' value='" .
                 $this->fields["realname_field"] . "'></td>";
        echo "<td>" . __('First name') . "</td>";
        echo "<td><input type='text' class='form-control' name='firstname_field' value='" .
                 $this->fields["firstname_field"] . "'></td></tr>";

        echo "<tr class='tab_bg_2'><td>" . __('Comments') . "</td>";
        echo "<td><input type='text' class='form-control' name='comment_field' value='" . $this->fields["comment_field"] . "'>";
        echo "</td>";
        echo "<td>" . _x('user', 'Administrative number') . "</td>";
        echo "<td>";
        echo "<input type='text' class='form-control' name='registration_number_field' value='" .
             $this->fields["registration_number_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'>";
        echo "<td>" . _n('Email', 'Emails', 1) . "</td>";
        echo "<td><input type='text' class='form-control' name='email1_field' value='" . $this->fields["email1_field"] . "'>";
        echo "</td>";
        echo "<td>" . sprintf(__('%1$s %2$s'), _n('Email', 'Emails', 1), '2') . "</td>";
        echo "<td><input type='text' class='form-control' name='email2_field' value='" . $this->fields["email2_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'>";
        echo "<td>" . sprintf(__('%1$s %2$s'), _n('Email', 'Emails', 1), '3') . "</td>";
        echo "<td><input type='text' class='form-control' name='email3_field' value='" . $this->fields["email3_field"] . "'>";
        echo "</td>";
        echo "<td>" . sprintf(__('%1$s %2$s'), _n('Email', 'Emails', 1), '4') . "</td>";
        echo "<td><input type='text' class='form-control' name='email4_field' value='" . $this->fields["email4_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'><td>" . _x('ldap', 'Phone') . "</td>";
        echo "<td><input type='text' class='form-control' name='phone_field'value='" . $this->fields["phone_field"] . "'>";
        echo "</td>";
        echo "<td>" . __('Phone 2') . "</td>";
        echo "<td><input type='text' class='form-control' name='phone2_field'value='" . $this->fields["phone2_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'><td>" . __('Mobile phone') . "</td>";
        echo "<td><input type='text' class='form-control' name='mobile_field'value='" . $this->fields["mobile_field"] . "'>";
        echo "</td>";
        echo "<td>" . _x('person', 'Title') . "</td>";
        echo "<td><input type='text' class='form-control' name='title_field' value='" . $this->fields["title_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'><td>" . _n('Category', 'Categories', 1) . "</td>";
        echo "<td><input type='text' class='form-control' name='category_field' value='" .
                 $this->fields["category_field"] . "'></td>";
        echo "<td>" . __('Language') . "</td>";
        echo "<td><input type='text' class='form-control' name='language_field' value='" .
                 $this->fields["language_field"] . "'></td></tr>";

        echo "<tr class='tab_bg_2'><td>" . _n('Picture', 'Pictures', 1) . "</td>";
        echo "<td><input type='text' class='form-control' name='picture_field' value='" .
                 $this->fields["picture_field"] . "'></td>";
        echo "<td>" . Location::getTypeName(1) . "</td>";
        echo "<td><input type='text' class='form-control' name='location_field' value='" . $this->fields["location_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_2'><td>" . __('Responsible') . "</td>";
        echo "<td><input type='text' class='form-control' name='responsible_field' value='" .
           $this->fields["responsible_field"] . "'></td>";
        echo "<td colspan='2'></td></tr>";

        echo "<tr><td colspan=4 class='center green'>" . __('You can use a field name or an expression using various %{fieldname}') .
           " <br />" . __('Example for location: %{city} > %{roomnumber}') . "</td></tr>";

        echo "<tr class='tab_bg_2'><td class='center' colspan='4'>";
        echo "<input type='submit' name='update' class='btn btn-primary' value=\"" . __s('Save') . "\">";
        echo "</td></tr>";
        echo "</table>";
        Html::closeForm();
        echo "</div>";
    }

    /**
     * Show entity config form
     *
     * @return void
     */
    public function showFormEntityConfig()
    {

        $ID = $this->getField('id');

        echo "<div class='center'>";
        echo "<form method='post' action='" . Toolbox::getItemTypeFormURL(__CLASS__) . "'>";
        echo "<input type='hidden' name='id' value='$ID'>";
        echo "<table class='tab_cadre_fixe'>";

        echo "<tr><th class='center' colspan='4'>" . __('Import entities from LDAP directory') .
           "</th></tr>";

        echo "<tr class='tab_bg_1'><td>" . __('Attribute representing entity') . "</td>";
        echo "<td colspan='3'>";
        echo "<input type='text' name='entity_field' value='" . $this->fields["entity_field"] . "'>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_1'><td>" . __('Search filter for entities') . "</td>";
        echo "<td colspan='3'>";
        echo "<input type='text' name='entity_condition' value='" . $this->fields["entity_condition"] . "'
             size='100'></td></tr>";

        echo "<tr class='tab_bg_2'><td class='center' colspan='4'>";
        echo "<input type='submit' name='update' class='btn btn-primary' value=\"" . __s('Save') . "\">";
        echo "</td></tr>";
        echo "</table>";
        Html::closeForm();
        echo "</div>";
    }

    public function defineTabs($options = [])
    {

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

        return $ong;
    }

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

        $tab[] = [
            'id'                 => 'common',
            'name'               => $this->getTypeName(1),
        ];

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

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

        $tab[] = [
            'id'                 => '3',
            'table'              => $this->getTable(),
            'field'              => 'host',
            'name'               => __('Server'),
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '4',
            'table'              => $this->getTable(),
            'field'              => 'port',
            'name'               => _n('Port', 'Ports', 1),
            'datatype'           => 'integer',
        ];

        $tab[] = [
            'id'                 => '5',
            'table'              => $this->getTable(),
            'field'              => 'basedn',
            'name'               => __('BaseDN'),
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '6',
            'table'              => $this->getTable(),
            'field'              => 'condition',
            'name'               => __('Connection filter'),
            'datatype'           => 'text',
        ];

        $tab[] = [
            'id'                 => '7',
            'table'              => $this->getTable(),
            'field'              => 'is_default',
            'name'               => __('Default server'),
            'datatype'           => 'bool',
            'massiveaction'      => false,
        ];

        $tab[] = [
            'id'                 => '8',
            'table'              => $this->getTable(),
            'field'              => 'login_field',
            'name'               => __('Login field'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '9',
            'table'              => $this->getTable(),
            'field'              => 'realname_field',
            'name'               => __('Surname'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '10',
            'table'              => $this->getTable(),
            'field'              => 'firstname_field',
            'name'               => __('First name'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '11',
            'table'              => $this->getTable(),
            'field'              => 'phone_field',
            'name'               =>  _x('ldap', 'Phone'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '12',
            'table'              => $this->getTable(),
            'field'              => 'phone2_field',
            'name'               => __('Phone 2'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '13',
            'table'              => $this->getTable(),
            'field'              => 'mobile_field',
            'name'               => __('Mobile phone'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '14',
            'table'              => $this->getTable(),
            'field'              => 'title_field',
            'name'               => _x('person', 'Title'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '15',
            'table'              => $this->getTable(),
            'field'              => 'category_field',
            'name'               => _n('Category', 'Categories', 1),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

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

        $tab[] = [
            'id'                 => '17',
            'table'              => $this->getTable(),
            'field'              => 'email1_field',
            'name'               => _n('Email', 'Emails', 1),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '25',
            'table'              => $this->getTable(),
            'field'              => 'email2_field',
            'name'               => sprintf(__('%1$s %2$s'), _n('Email', 'Emails', 1), '2'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '26',
            'table'              => $this->getTable(),
            'field'              => 'email3_field',
            'name'               => sprintf(__('%1$s %2$s'), _n('Email', 'Emails', 1), '3'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '27',
            'table'              => $this->getTable(),
            'field'              => 'email4_field',
            'name'               => sprintf(__('%1$s %2$s'), _n('Email', 'Emails', 1), '4'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '18',
            'table'              => $this->getTable(),
            'field'              => 'use_dn',
            'name'               => __('Use DN in the search'),
            '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,
        ];

        $tab[] = [
            'id'                 => '20',
            'table'              => $this->getTable(),
            'field'              => 'language_field',
            'name'               => __('Language'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '21',
            'table'              => $this->getTable(),
            'field'              => 'group_field',
            'name'               => __('User attribute containing its groups'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '22',
            'table'              => $this->getTable(),
            'field'              => 'group_condition',
            'name'               => __('Filter to search in groups'),
            'massiveaction'      => false,
            'datatype'           => 'text',
        ];

        $tab[] = [
            'id'                 => '23',
            'table'              => $this->getTable(),
            'field'              => 'group_member_field',
            'name'               => __('Group attribute containing its users'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '24',
            'table'              => $this->getTable(),
            'field'              => 'group_search_type',
            'datatype'           => 'specific',
            'name'               => __('Search type'),
            'massiveaction'      => false,
        ];

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

        $tab[] = [
            'id'                 => '28',
            'table'              => $this->getTable(),
            'field'              => 'sync_field',
            'name'               => __('Synchronization field'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '29',
            'table'              => $this->getTable(),
            'field'              => 'responsible_field',
            'name'               => __('Responsible'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '31',
            'table'              => $this->getTable(),
            'field'              => 'inventory_domain',
            'name'               => __('Domain name used by inventory tool'),
            'massiveaction'      => false,
            'datatype'           => 'string',
        ];

        $tab[] = [
            'id'                 => '32',
            'table'              => $this->getTable(),
            'field'              => 'timeout',
            'name'               => __('Timeout'),
            'massiveaction'      => false,
            'datatype'           => 'number',
            'unit'               => 'second',
            'toadd'              => [
                '0'                  => __('No timeout'),
            ],
        ];

        return $tab;
    }

    /**
     * Show system information form
     *
     * @param integer $width The number of characters at which the string will be wrapped.
     *
     * @return void
     */
    public function showSystemInformations($width)
    {

        // No need to translate, this part always display in english (for copy/paste to forum)

        $ldap_servers = self::getLdapServers();

        if (!empty($ldap_servers)) {
            echo "<tr class='tab_bg_2'><th class='section-header'>" . self::getTypeName(Session::getPluralNumber()) . "</th></tr>\n";
            echo "<tr class='tab_bg_1'><td><pre class='section-content'>\n&nbsp;\n";
            foreach ($ldap_servers as $value) {
                $fields = ['Server'            => 'host',
                    'Port'              => 'port',
                    'BaseDN'            => 'basedn',
                    'Connection filter' => 'condition',
                    'RootDN'            => 'rootdn',
                    'Use TLS'           => 'use_tls',
                ];
                $msg   = '';
                $first = true;
                foreach ($fields as $label => $field) {
                    $msg .= (!$first ? ', ' : '') .
                        $label . ': ' .
                        ($value[$field] ? '\'' . $value[$field] . '\'' : 'none');
                    $first = false;
                }
                echo wordwrap($msg . "\n", $width, "\n\t\t");
            }
            echo "\n</pre></td></tr>";
        }
    }


    /**
     * Get LDAP fields to sync to GLPI data from a glpi_authldaps array
     *
     * @param array $authtype_array Authentication method config array (from table)
     *
     * @return array of "user table field name" => "config value"
     */
    public static function getSyncFields(array $authtype_array)
    {

        $ret    = [];
        $fields = ['login_field'               => 'name',
            'email1_field'              => 'email1',
            'email2_field'              => 'email2',
            'email3_field'              => 'email3',
            'email4_field'              => 'email4',
            'realname_field'            => 'realname',
            'firstname_field'           => 'firstname',
            'phone_field'               => 'phone',
            'phone2_field'              => 'phone2',
            'mobile_field'              => 'mobile',
            'location_field'            => 'locations_id',
            'comment_field'             => 'comment',
            'title_field'               => 'usertitles_id',
            'category_field'            => 'usercategories_id',
            'language_field'            => 'language',
            'registration_number_field' => 'registration_number',
            'picture_field'             => 'picture',
            'responsible_field'         => 'users_id_supervisor',
            'sync_field'                => 'sync_field',
        ];

        foreach ($fields as $key => $val) {
            if (isset($authtype_array[$key]) && !empty($authtype_array[$key])) {
                $ret[$val] = $authtype_array[$key];
            }
        }
        return $ret;
    }


    /**
     * Display LDAP filter
     *
     * @param string  $target target for the form
     * @param boolean $users  for user? (true by default)
     *
     * @return void
     */
    public static function displayLdapFilter($target, $users = true)
    {

        $config_ldap = new self();
        if (!isset($_SESSION['ldap_server'])) {
            throw new \RuntimeException('LDAP server must be set!');
        }
        $config_ldap->getFromDB($_SESSION['ldap_server']);

        $filter_name1 = null;
        $filter_name2 = null;
        if ($users) {
            $filter_name1 = "condition";
            $filter_var   = "ldap_filter";
        } else {
            $filter_var = "ldap_group_filter";
            switch ($config_ldap->fields["group_search_type"]) {
                case self::GROUP_SEARCH_USER:
                    $filter_name1 = "condition";
                    break;

                case self::GROUP_SEARCH_GROUP:
                    $filter_name1 = "group_condition";
                    break;

                case self::GROUP_SEARCH_BOTH:
                    $filter_name1 = "group_condition";
                    $filter_name2 = "condition";
                    break;
            }
        }

        if ($filter_name1 !== null && (!isset($_SESSION[$filter_var]) || $_SESSION[$filter_var] == '')) {
            $_SESSION[$filter_var] = Sanitizer::unsanitize($config_ldap->fields[$filter_name1]);
        }

        echo "<div class='card'>";
        echo "<form method='post' action='$target'>";
        echo "<table class='table card-table'>";
        echo "<tr><td>" . ($users ? __('Search filter for users')
                                           : __('Filter to search in groups')) . "</td>";

        echo "<td>";
        echo "<input type='text' name='ldap_filter' value='" . htmlspecialchars($_SESSION[$filter_var], ENT_QUOTES) . "' size='70'>";
        //Only display when looking for groups in users AND groups
        if (
            !$users
            && ($config_ldap->fields["group_search_type"] == self::GROUP_SEARCH_BOTH)
        ) {
            if ($filter_name2 !== null && (!isset($_SESSION["ldap_group_filter2"]) || $_SESSION["ldap_group_filter2"] == '')) {
                $_SESSION["ldap_group_filter2"] = Sanitizer::unsanitize($config_ldap->fields[$filter_name2]);
            }
            echo "</td></tr>";

            echo "<tr><td>" . __('Search filter for users') . "</td";

            echo "<td>";
            echo "<input type='text' name='ldap_filter2' value='" . htmlspecialchars($_SESSION["ldap_group_filter2"], ENT_QUOTES) . "'
                size='70'></td></tr>";
        }

        echo "<tr class='tab_bg_2'><td class='center' colspan='2'>";
        echo "<input class=submit type='submit' name='change_ldap_filter' value=\"" .
            _sx('button', 'Search') . "\"></td></tr>";
        echo "</table>";
        Html::closeForm();
        echo "</div>";
    }


    /**
     * Converts LDAP timestamps over to Unix timestamps
     *
     * @param string  $ldapstamp        LDAP timestamp
     * @param integer $ldap_time_offset time offset (default 0)
     *
     * @return integer unix timestamp
     */
    public static function ldapStamp2UnixStamp($ldapstamp, $ldap_time_offset = 0)
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        //Check if timestamp is well format, otherwise return ''
        if (!preg_match("/[\d]{14}(\.[\d]{0,4})*Z/", $ldapstamp)) {
            return '';
        }

        $year    = substr($ldapstamp, 0, 4);
        $month   = substr($ldapstamp, 4, 2);
        $day     = substr($ldapstamp, 6, 2);
        $hour    = substr($ldapstamp, 8, 2);
        $minute  = substr($ldapstamp, 10, 2);
        $seconds = substr($ldapstamp, 12, 2);
        $stamp   = gmmktime($hour, $minute, $seconds, $month, $day, $year);
        $stamp  += $CFG_GLPI["time_offset"] - $ldap_time_offset;

        return $stamp;
    }


    /**
     * Converts a Unix timestamp to an LDAP timestamps
     *
     * @param string $date datetime
     *
     * @return string ldap timestamp
     */
    public static function date2ldapTimeStamp($date)
    {
        return date("YmdHis", strtotime($date)) . '.0Z';
    }


    /**
     * Return the LDAP field to use for user synchronization
     * It may be sync_field if defined, or login_field
     * @since 9.2
     *
     * @return string the ldap field to use for user synchronization
     */
    public function getLdapIdentifierToUse()
    {
        if (!empty($this->fields['sync_field'])) {
            return $this->fields['sync_field'];
        } else {
            return $this->fields['login_field'];
        }
    }

    /**
     * Return the database field to use for user synchronization
     * @since 9.2
     *
     * @return string the database field to use for user synchronization
     */
    public function getDatabaseIdentifierToUse()
    {
        if (!empty($this->fields['sync_field'])) {
            return 'sync_field';
        } else {
            return 'name';
        }
    }

    /**
     * Indicates if there's a sync_field enabled in the LDAP configuration
     * @since 9.2
     *
     * @return boolean true if the sync_field is enabled (the field is filled)
     */
    public function isSyncFieldEnabled()
    {
        return (!empty($this->fields['sync_field']));
    }

    /**
     * Check if the sync_field is configured for an LDAP server
     *
     * @since 9.2
     * @param integer $authldaps_id the LDAP server ID
     *
     * @return boolean true if configured, false if not configured
     */
    public static function isSyncFieldConfigured($authldaps_id)
    {
        $authldap = new self();
        $authldap->getFromDB($authldaps_id);
        return ($authldap->isSyncFieldEnabled());
    }

    /**
     * Test a LDAP connection
     *
     * @param integer $auths_id     ID of the LDAP server
     * @param integer $replicate_id use a replicate if > 0 (default -1)
     *
     * @return boolean connection succeeded?
     */
    public static function testLDAPConnection($auths_id, $replicate_id = -1)
    {

        $config_ldap = new self();
        $res         = $config_ldap->getFromDB($auths_id);

        // we prevent some delay...
        if (!$res) {
            return false;
        }

        //Test connection to a replicate
        if ($replicate_id != -1) {
            $replicate = new AuthLdapReplicate();
            $replicate->getFromDB($replicate_id);
            $host = $replicate->fields["host"];
            $port = $replicate->fields["port"];
        } else {
            //Test connection to a master ldap server
            $host = $config_ldap->fields['host'];
            $port = $config_ldap->fields['port'];
        }
        $ds = self::connectToServer(
            $host,
            $port,
            $config_ldap->fields['rootdn'],
            (new GLPIKey())->decrypt($config_ldap->fields['rootdn_passwd']),
            $config_ldap->fields['use_tls'],
            $config_ldap->fields['deref_option'],
            $config_ldap->fields['tls_certfile'],
            $config_ldap->fields['tls_keyfile'],
            $config_ldap->fields['use_bind'],
            $config_ldap->fields['timeout']
        );
        if ($ds) {
            return true;
        }
        return false;
    }


    /**
     * Display a warnign about size limit
     *
     * @since 0.84
     *
     * @param boolean $limitexceeded (false by default)
     *
     * @return void
     */
    public static function displaySizeLimitWarning($limitexceeded = false)
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        if ($limitexceeded) {
            echo "<div class='firstbloc'><table class='tab_cadre_fixe'>";
            echo "<tr><th class='red'>";
            echo "<img class='center' src='" . $CFG_GLPI["root_doc"] . "/pics/warning.png'
                alt='" . __('Warning') . "'>&nbsp;" .
             __('Warning: The request exceeds the limit of the directory. The results are only partial.');
            echo "</th></tr></table><div>";
        }
    }


    /**
     * Show LDAP users to add or synchronise
     *
     * @return void
     */
    public static function showLdapUsers()
    {

        $values = [
            'order' => 'DESC',
            'start' => 0,
        ];

        foreach ($_SESSION['ldap_import'] as $option => $value) {
            $values[$option] = $value;
        }

        $rand          = mt_rand();
        $results       = [];
        $limitexceeded = false;
        $ldap_users    = self::getUsers($values, $results, $limitexceeded);

        $config_ldap   = new AuthLDAP();
        $config_ldap->getFromDB($values['authldaps_id']);

        if (is_array($ldap_users)) {
            $numrows = count($ldap_users);

            if ($numrows > 0) {
                echo "<div class='card'>";
                self::displaySizeLimitWarning($limitexceeded);

                Html::printPager($values['start'], $numrows, $_SERVER['PHP_SELF'], '');

                // delete end
                array_splice($ldap_users, $values['start'] + $_SESSION['glpilist_limit']);
                // delete begin
                if ($values['start'] > 0) {
                    array_splice($ldap_users, 0, $values['start']);
                }

                $form_action = '';
                $textbutton  = '';
                if ($_SESSION['ldap_import']['mode']) {
                    $textbutton  = _x('button', 'Synchronize');
                    $form_action = __CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'sync';
                } else {
                    $textbutton  = _x('button', 'Import');
                    $form_action = __CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'import';
                }

                Html::openMassiveActionsForm('mass' . __CLASS__ . $rand);
                $massiveactionparams = [
                    'num_displayed'    => min(count($ldap_users), $_SESSION['glpilist_limit']),
                    'container'        => 'mass' . __CLASS__ . $rand,
                    'specific_actions' => [$form_action => $textbutton],
                ];
                echo "<div class='ms-2 ps-1 d-flex mb-2'>";
                Html::showMassiveActions($massiveactionparams);
                echo "</div>";

                echo "<table class='table card-table'>";
                echo "<thead>";
                echo "<tr>";
                echo "<th width='10'>";
                echo Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand);
                echo "</th>";
                $num = 0;
                if ($config_ldap->isSyncFieldEnabled()) {
                    echo Search::showHeaderItem(
                        Search::HTML_OUTPUT,
                        __('Synchronization field'),
                        $num,
                        $_SERVER['PHP_SELF'] .
                        "?order=" . ($values['order'] == "DESC" ? "ASC" : "DESC")
                    );
                }
                echo Search::showHeaderItem(
                    Search::HTML_OUTPUT,
                    User::getTypeName(Session::getPluralNumber()),
                    $num,
                    $_SERVER['PHP_SELF'] .
                    "?order=" . ($values['order'] == "DESC" ? "ASC" : "DESC")
                );
                echo "<th>" . __('Last update in the LDAP directory') . "</th>";
                if ($_SESSION['ldap_import']['mode']) {
                    echo "<th>" . __('Last update in GLPI') . "</th>";
                }
                echo "</tr>";
                echo "</thead>";

                foreach ($ldap_users as $userinfos) {
                    echo "<tr>";
                    //Need to use " instead of ' because it doesn't work with names with ' inside !
                    echo "<td>";
                    echo Html::getMassiveActionCheckBox(__CLASS__, $userinfos['uid']);
                    echo "</td>";
                    if ($config_ldap->isSyncFieldEnabled()) {
                        echo "<td>" . $userinfos['uid'] . "</td>";
                    }
                    echo "<td>";
                    if (isset($userinfos['id']) && User::canView()) {
                        echo "<a href='" . $userinfos['link'] . "'>" . $userinfos['name'] . "</a>";
                    } else {
                        echo $userinfos['link'];
                    }
                    echo "</td>";

                    if ($userinfos['stamp'] != '') {
                        echo "<td>" . Html::convDateTime(date("Y-m-d H:i:s", $userinfos['stamp'])) . "</td>";
                    } else {
                        echo "<td>&nbsp;</td>";
                    }
                    if ($_SESSION['ldap_import']['mode']) {
                        if ($userinfos['date_sync'] != '') {
                            echo "<td>" . Html::convDateTime($userinfos['date_sync']) . "</td>";
                        }
                    }
                    echo "</tr>";
                }
                echo "<tfoot>";
                echo "<tr>";
                echo "<th width='10'>";
                echo Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand);
                echo "</th>";
                $num = 0;

                if ($config_ldap->isSyncFieldEnabled()) {
                    echo Search::showHeaderItem(
                        Search::HTML_OUTPUT,
                        __('Synchronization field'),
                        $num,
                        $_SERVER['PHP_SELF'] .
                        "?order=" . ($values['order'] == "DESC" ? "ASC" : "DESC")
                    );
                }
                echo Search::showHeaderItem(
                    Search::HTML_OUTPUT,
                    User::getTypeName(Session::getPluralNumber()),
                    $num,
                    $_SERVER['PHP_SELF'] .
                    "?order=" . ($values['order'] == "DESC" ? "ASC" : "DESC")
                );
                echo "<th>" . __('Last update in the LDAP directory') . "</th>";
                if ($_SESSION['ldap_import']['mode']) {
                    echo "<th>" . __('Last update in GLPI') . "</th>";
                }
                echo "</tr>";
                echo "</tfoot>";
                echo "</table>";

                $massiveactionparams['ontop'] = false;
                echo "<div class='ms-2 ps-1 mt-2 d-flex'>";
                Html::showMassiveActions($massiveactionparams);
                echo "</div>";

                Html::closeForm();

                Html::printPager($values['start'], $numrows, $_SERVER['PHP_SELF'], '');

                echo "</div>";
            } else {
                echo "<div class='center b'>" .
                  ($_SESSION['ldap_import']['mode'] ? __('No user to be synchronized')
                                                   : __('No user to be imported')) . "</div>";
            }
        } else {
            echo "<div class='center b'>" .
               ($_SESSION['ldap_import']['mode'] ? __('No user to be synchronized')
                                                : __('No user to be imported')) . "</div>";
        }
    }

    /**
     * Search users
     *
     * @param resource $ds            An LDAP link identifier
     * @param array    $values        values to search
     * @param string   $filter        search filter
     * @param array    $attrs         An array of the required attributes
     * @param boolean  $limitexceeded is limit exceeded
     * @param array    $user_infos    user information
     * @param array    $ldap_users    ldap users
     * @param object   $config_ldap   ldap configuration
     *
     * @return boolean
     */
    public static function searchForUsers(
        $ds,
        $values,
        $filter,
        $attrs,
        &$limitexceeded,
        &$user_infos,
        &$ldap_users,
        $config_ldap
    ) {

        //If paged results cannot be used (PHP < 5.4)
        $cookie   = ''; //Cookie used to perform query using pages
        $count    = 0;  //Store the number of results ldap_search

        do {
            $filter = Sanitizer::unsanitize($filter);
            if (self::isLdapPageSizeAvailable($config_ldap)) {
                $controls = [
                    [
                        'oid'       => LDAP_CONTROL_PAGEDRESULTS,
                        'iscritical' => true,
                        'value'     => [
                            'size'   => $config_ldap->fields['pagesize'],
                            'cookie' => $cookie,
                        ],
                    ],
                ];
                $sr = @ldap_search($ds, $values['basedn'], $filter, $attrs, 0, -1, -1, LDAP_DEREF_NEVER, $controls);
                if (
                    $sr === false
                    || @ldap_parse_result($ds, $sr, $errcode, $matcheddn, $errmsg, $referrals, $controls) === false
                ) {
                    // 32 = LDAP_NO_SUCH_OBJECT => This error can be silented as it just means that search produces no result.
                    if (ldap_errno($ds) !== 32) {
                        trigger_error(
                            static::buildError(
                                $ds,
                                sprintf('LDAP search with base DN `%s` and filter `%s` failed', $values['basedn'], $filter)
                            ),
                            E_USER_WARNING
                        );
                    }
                    return false;
                }
                if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
                    $cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
                } else {
                    $cookie = '';
                }
            } else {
                $sr = @ldap_search($ds, $values['basedn'], $filter, $attrs);
                if ($sr === false) {
                    // 32 = LDAP_NO_SUCH_OBJECT => This error can be silented as it just means that search produces no result.
                    if (ldap_errno($ds) !== 32) {
                        trigger_error(
                            static::buildError(
                                $ds,
                                sprintf('LDAP search with base DN `%s` and filter `%s` failed', $values['basedn'], $filter)
                            ),
                            E_USER_WARNING
                        );
                    }
                    return false;
                }
            }

            if (in_array(ldap_errno($ds), [4,11])) {
                // openldap return 4 for Size limit exceeded
                $limitexceeded = true;
            }

            $info = self::get_entries_clean($ds, $sr);
            if (in_array(ldap_errno($ds), [4,11])) {
                // openldap return 4 for Size limit exceeded
                $limitexceeded = true;
            }

            $count += $info['count'];
            //If page results are enabled and the number of results is greater than the maximum allowed
            //warn user that limit is exceeded and stop search
            if (
                self::isLdapPageSizeAvailable($config_ldap)
                && $config_ldap->fields['ldap_maxlimit']
                && ($count > $config_ldap->fields['ldap_maxlimit'])
            ) {
                $limitexceeded = true;
                break;
            }

            $field_for_sync = $config_ldap->getLdapIdentifierToUse();
            $login_field = $config_ldap->fields['login_field'];

            for ($ligne = 0; $ligne < $info["count"]; $ligne++) {
                if (in_array($field_for_sync, $info[$ligne])) {
                    $uid = self::getFieldValue($info[$ligne], $field_for_sync);

                    if ($login_field != $field_for_sync && !isset($info[$ligne][$login_field])) {
                        trigger_error("Missing field $login_field for LDAP entry $field_for_sync $uid", E_USER_WARNING);
                        //Login field may be missing... Skip the user
                        continue;
                    }

                    if (isset($info[$ligne]['modifytimestamp'])) {
                        $user_infos[$uid]["timestamp"] = self::ldapStamp2UnixStamp(
                            $info[$ligne]['modifytimestamp'][0],
                            $config_ldap->fields['time_offset']
                        );
                    } else {
                        $user_infos[$uid]["timestamp"] = '';
                    }

                    $user_infos[$uid]["user_dn"] = $info[$ligne]['dn'];
                    $user_infos[$uid][$field_for_sync] = $uid;
                    if ($config_ldap->isSyncFieldEnabled()) {
                        $user_infos[$uid][$login_field] = $info[$ligne][$login_field][0];
                    }

                    if ($values['mode'] == self::ACTION_IMPORT) {
                        //If ldap add
                        $ldap_users[$uid] = $uid;
                    } else {
                        //If ldap synchronisation
                        if (isset($info[$ligne]['modifytimestamp'])) {
                            $ldap_users[$uid] = self::ldapStamp2UnixStamp(
                                $info[$ligne]['modifytimestamp'][0],
                                $config_ldap->fields['time_offset']
                            );
                        } else {
                            $ldap_users[$uid] = '';
                        }
                        $user_infos[$uid]["name"] = $info[$ligne][$login_field][0];
                    }
                }
            }
        } while (($cookie !== null) && ($cookie != ''));

        return true;
    }


    /**
     * Get the list of LDAP users to add/synchronize
     *
     * @param array   $options       possible options:
     *          - authldaps_id ID of the server to use
     *          - mode user to synchronize or add?
     *          - ldap_filter ldap filter to use
     *          - basedn force basedn (default authldaps_id one)
     *          - order display order
     *          - begin_date begin date to time limit
     *          - end_date end date to time limit
     *          - script true if called by an external script
     * @param array   $results       result stats
     * @param boolean $limitexceeded limit exceeded exception
     *
     * @return false|array
     */
    public static function getAllUsers(array $options, &$results, &$limitexceeded)
    {
        /** @var \DBmysql $DB */
        global $DB;

        $config_ldap = new self();
        $res         = $config_ldap->getFromDB($options['authldaps_id']);

        $values = [
            'order'       => 'DESC',
            'mode'        => self::ACTION_SYNCHRONIZE,
            'ldap_filter' => '',
            'basedn'      => $config_ldap->fields['basedn'],
            'begin_date'  => null,
            'end_date'    => date('Y-m-d H:i:s', time() - DAY_TIMESTAMP),
            'script'      => 0, //Called by an external script or not
        ];

        foreach ($options as $option => $value) {
            // this test break mode detection - if ($value != '') {
            $values[$option] = $value;
            //}
        }

        $ldap_users    = [];
        $user_infos    = [];
        $limitexceeded = false;

        // we prevent some delay...
        if (!$res) {
            return false;
        }
        if ($values['order'] != "DESC") {
            $values['order'] = "ASC";
        }
        $ds = $config_ldap->connect();
        $field_for_sync = $config_ldap->getLdapIdentifierToUse();
        $field_for_db   = $config_ldap->getDatabaseIdentifierToUse();
        if ($ds) {
            //Search for ldap login AND modifyTimestamp,
            //which indicates the last update of the object in directory
            $attrs = [$config_ldap->fields['login_field'], "modifyTimestamp"];
            if ($field_for_sync != $config_ldap->fields['login_field']) {
                $attrs[] = $field_for_sync;
            }

            // Try a search to find the DN
            if ($values['ldap_filter'] == '') {
                $filter = "(" . $field_for_sync . "=*)";
                if (!empty($config_ldap->fields['condition'])) {
                    $filter = "(& $filter " . Sanitizer::unsanitize($config_ldap->fields['condition']) . ")";
                }
            } else {
                $filter = $values['ldap_filter'];
            }

            if ($values['script'] && !empty($values['begin_date'])) {
                $filter_timestamp = self::addTimestampRestrictions(
                    $values['begin_date'],
                    $values['end_date']
                );
                $filter           = "(&$filter $filter_timestamp)";
            }
            $result = self::searchForUsers(
                $ds,
                $values,
                $filter,
                $attrs,
                $limitexceeded,
                $user_infos,
                $ldap_users,
                $config_ldap
            );
            if (!$result) {
                return false;
            }
        } else {
            return false;
        }

        $glpi_users = [];

        $select = [
            'FROM'   => User::getTable(),
            'ORDER'  => ['name ' . $values['order']],
        ];

        if ($values['mode'] != self::ACTION_IMPORT) {
            $select['WHERE'] = [
                'authtype'  => [-1, Auth::NOT_YET_AUTHENTIFIED, Auth::LDAP, Auth::EXTERNAL, Auth::CAS],
                'auths_id'  => $options['authldaps_id'],
            ];
        }

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

        foreach ($iterator as $user) {
            $tmpuser = new User();

            //Ldap add : fill the array with the login of the user
            if ($values['mode'] == self::ACTION_IMPORT) {
                $glpi_users[$user['name']] = $user['name'];
            } else {
                //Ldap synchronisation : look if the user exists in the directory
                //and compares the modifications dates (ldap and glpi db)
                $userfound = self::dnExistsInLdap($user_infos, $user['user_dn']);
                if (!empty($ldap_users[$user[$field_for_db]]) || $userfound) {
                    // userfound seems that user dn is present in GLPI DB but do not correspond to an GLPI user
                    // -> renaming case
                    if ($userfound) {
                        //Get user in DB with this dn
                        if (!$tmpuser->getFromDBByDn(Sanitizer::sanitize($user['user_dn']))) {
                            //This should never happened
                            //If a user_dn is present more than one time in database
                            //Just skip user synchronization to avoid errors
                            continue;
                        }
                        $glpi_users[] = ['id'         => $user['id'],
                            'user'       => $userfound['name'],
                            $field_for_sync => ($userfound[$config_ldap->fields['sync_field']] ?? 'NULL'),
                            'timestamp'  => $user_infos[$userfound[$field_for_sync]]['timestamp'],
                            'date_sync'  => $tmpuser->fields['date_sync'],
                            'dn'         => $user['user_dn'],
                        ];
                    } elseif (
                        ($values['mode'] == self::ACTION_ALL)
                          || (($ldap_users[$user[$field_for_db]] - strtotime($user['date_sync'])) > 0)
                    ) {
                        //If entry was modified or if script should synchronize all the users
                        $glpi_users[] = ['id'         => $user['id'],
                            'user'       => $user['name'],
                            $field_for_sync => $user['sync_field'],
                            'timestamp'  => $user_infos[$user[$field_for_db]]['timestamp'],
                            'date_sync'  => $user['date_sync'],
                            'dn'         => $user['user_dn'],
                        ];
                    }
                } elseif (
                    ($values['mode'] == self::ACTION_ALL)
                        && !$limitexceeded
                ) {
                    // Only manage deleted user if ALL (because of entity visibility in delegated mode)

                    if ($user['auths_id'] == $options['authldaps_id']) {
                        if (!$userfound && $user['is_deleted_ldap'] == 0) {
                            //If user is marked as coming from LDAP, but is not present in it anymore
                            User::manageDeletedUserInLdap($user['id']);
                            $results[self::USER_DELETED_LDAP]++;
                        } elseif ($userfound && $user['is_deleted_ldap'] == 1) {
                            // User is marked as coming from LDAP, but was previously deleted
                            User::manageRestoredUserInLdap($user['id']);
                            $results[self::USER_RESTORED_LDAP]++;
                        }
                    }
                }
            }
        }

        //If add, do the difference between ldap users and glpi users
        if ($values['mode'] == self::ACTION_IMPORT) {
            $diff    = array_diff_ukey($ldap_users, $glpi_users, 'strcasecmp');
            $list    = [];
            $tmpuser = new User();

            foreach ($diff as $user) {
                //If user dn exists in DB, it means that user login field has changed
                if (!$tmpuser->getFromDBByDn(Sanitizer::sanitize($user_infos[$user]["user_dn"]))) {
                    $entry  = ["user"      => $user_infos[$user][$config_ldap->fields['login_field']],
                        "timestamp" => $user_infos[$user]["timestamp"],
                        "date_sync" => Dropdown::EMPTY_VALUE,
                    ];
                    if ($config_ldap->isSyncFieldEnabled()) {
                        $entry[$field_for_sync] = $user_infos[$user][$field_for_sync];
                    }
                    $list[] = $entry;
                }
            }
            if ($values['order'] == 'DESC') {
                rsort($list);
            } else {
                sort($list);
            }

            return $list;
        }
        return $glpi_users;
    }


    /**
     * Check if a user DN exists in a ldap user search result
     *
     * @since 0.84
     *
     * @param array  $ldap_infos ldap user search result
     * @param string $user_dn    user dn to look for
     *
     * @return false|array false if the user dn doesn't exist, user ldap infos otherwise
     */
    public static function dnExistsInLdap($ldap_infos, $user_dn)
    {

        $found = false;
        foreach ($ldap_infos as $ldap_info) {
            if ($ldap_info['user_dn'] == $user_dn) {
                $found = $ldap_info;
                break;
            }
        }
        return $found;
    }


    /**
     * Show LDAP groups to add or synchronize in an entity
     *
     * @param string  $target  target page for the form
     * @param integer $start   where to start the list
     * @param integer $sync    synchronize or add? (default 0)
     * @param string  $filter  ldap filter to use (default '')
     * @param string  $filter2 second ldap filter to use (which case?) (default '')
     * @param integer $entity  working entity
     * @param string  $order   display order (default DESC)
     *
     * @return void
     */
    public static function showLdapGroups(
        $target,
        $start,
        $sync = 0,
        $filter = '',
        $filter2 = '',
        $entity = 0,
        $order = 'DESC'
    ) {

        echo "<br>";
        $limitexceeded = false;
        $ldap_groups   = self::getAllGroups(
            $_SESSION["ldap_server"],
            $filter,
            $filter2,
            $entity,
            $limitexceeded,
            $order
        );

        if (is_array($ldap_groups)) {
            $numrows     = count($ldap_groups);
            $rand        = mt_rand();
            if ($numrows > 0) {
                echo "<div class='card'>";
                self::displaySizeLimitWarning($limitexceeded);
                $parameters = '';
                Html::printPager($start, $numrows, $target, $parameters);

                // delete end
                array_splice($ldap_groups, $start + $_SESSION['glpilist_limit']);
                // delete begin
                if ($start > 0) {
                    array_splice($ldap_groups, 0, $start);
                }

                Html::openMassiveActionsForm('mass' . __CLASS__ . $rand);
                $massiveactionparams  = [
                    'num_displayed' => min($_SESSION['glpilist_limit'], count($ldap_groups)),
                    'container' => 'mass' . __CLASS__ . $rand,
                    'specific_actions' => [
                        __CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'import_group'
                                       => _sx('button', 'Import'),
                    ],
                    'extraparams' => [
                        'massive_action_fields' => [
                            'dn',
                            'ldap_import_type',
                            'ldap_import_entities',
                            'ldap_import_recursive',
                        ],
                    ],
                ];
                echo "<div class='ms-2 ps-1 d-flex mb-2'>";
                Html::showMassiveActions($massiveactionparams);
                echo "</div>";

                echo "<table class='table table-sm card-table'>";
                echo "<thead>";
                echo "<tr>";
                echo "<th width='10'>";
                Html::showCheckbox(['criterion' => ['tag_for_massive' => 'select_item']]);
                echo "</th>";
                $header_num = 0;
                echo Search::showHeaderItem(
                    Search::HTML_OUTPUT,
                    Group::getTypeName(1),
                    $header_num,
                    $target . "?order=" . ($order == "DESC" ? "ASC" : "DESC"),
                    1,
                    $order
                );
                echo "<th>" . __('Group DN') . "</th>";
                echo "<th>" . __('Destination entity') . "</th>";
                if (Session::isMultiEntitiesMode()) {
                    echo"<th>";
                    Html::showCheckbox(['criterion' => ['tag_for_massive' => 'select_item_child_entities']]);
                    echo "&nbsp;" . __('Child entities');
                    echo "</th>";
                }
                echo "</tr>";
                echo "</thead>";

                $dn_index = 0;
                foreach ($ldap_groups as $groupinfos) {
                    $group       = $groupinfos["cn"];
                    $group_dn    = $groupinfos["dn"];
                    $search_type = $groupinfos["search_type"];

                    echo "<tr>";
                    echo "<td>";
                    echo Html::hidden("dn[$dn_index]", ['value'                 => $group_dn,
                        'data-glpicore-ma-tags' => 'common',
                    ]);
                    echo Html::hidden("ldap_import_type[$dn_index]", ['value'                 => $search_type,
                        'data-glpicore-ma-tags' => 'common',
                    ]);
                    Html::showMassiveActionCheckBox(
                        __CLASS__,
                        $dn_index,
                        ['massive_tags' => 'select_item']
                    );
                    echo "</td>";
                    echo "<td>" . $group . "</td>";
                    echo "<td>" . $group_dn . "</td>";
                    echo "<td>";
                    Entity::dropdown(['value'         => $entity,
                        'name'          => "ldap_import_entities[$dn_index]",
                        'specific_tags' => ['data-glpicore-ma-tags' => 'common'],
                    ]);
                    echo "</td>";
                    if (Session::isMultiEntitiesMode()) {
                        echo "<td>";
                        Html::showMassiveActionCheckBox(
                            __CLASS__,
                            $dn_index,
                            ['massive_tags'  => 'select_item_child_entities',
                                'name'          => "ldap_import_recursive[$dn_index]",
                                'specific_tags' => ['data-glpicore-ma-tags' => 'common'],
                            ]
                        );
                        echo "</td>";
                    } else {
                        echo Html::hidden("ldap_import_recursive[$dn_index]", ['value'                 => 0,
                            'data-glpicore-ma-tags' => 'common',
                        ]);
                    }
                    echo "</tr>";
                    $dn_index++;
                }
                echo "</table>";

                $massiveactionparams['ontop'] = false;
                echo "<div class='ms-2 ps-1 mt-2 d-flex'>";
                Html::showMassiveActions($massiveactionparams);
                echo "</div>";

                Html::closeForm();
                Html::printPager($start, $numrows, $target, $parameters);
                echo "</div>";
            } else {
                echo "<div class='center b'>" . __('No group to be imported') . "</div>";
            }
        } else {
            echo "<div class='center b'>" . __('No group to be imported') . "</div>";
        }
    }


    /**
     * Get all LDAP groups from a ldap server which are not already in an entity
     *
     * @since 0.84 new parameter $limitexceeded
     *
     * @param integer $auths_id      ID of the server to use
     * @param string  $filter        ldap filter to use
     * @param string  $filter2       second ldap filter to use if needed
     * @param string  $entity        entity to search
     * @param boolean $limitexceeded is limit exceeded
     * @param string  $order         order to use (default DESC)
     *
     * @return array of the groups
     */
    public static function getAllGroups(
        $auths_id,
        $filter,
        $filter2,
        $entity,
        &$limitexceeded,
        $order = 'DESC'
    ) {
        /** @var \DBmysql $DB */
        global $DB;

        $config_ldap = new self();
        $config_ldap->getFromDB($auths_id);
        $infos       = [];
        $groups      = [];

        $ds = $config_ldap->connect();
        if ($ds) {
            switch ($config_ldap->fields["group_search_type"]) {
                case self::GROUP_SEARCH_USER:
                    $infos = self::getGroupsFromLDAP(
                        $ds,
                        $config_ldap,
                        $filter,
                        $limitexceeded,
                        false,
                        $infos
                    );
                    break;

                case self::GROUP_SEARCH_GROUP:
                    $infos = self::getGroupsFromLDAP(
                        $ds,
                        $config_ldap,
                        $filter,
                        $limitexceeded,
                        true,
                        $infos
                    );
                    break;

                case self::GROUP_SEARCH_BOTH:
                    $infos = self::getGroupsFromLDAP(
                        $ds,
                        $config_ldap,
                        $filter,
                        $limitexceeded,
                        true,
                        $infos
                    );
                    $infos = self::getGroupsFromLDAP(
                        $ds,
                        $config_ldap,
                        $filter2,
                        $limitexceeded,
                        false,
                        $infos
                    );
                    break;
            }
            if (!empty($infos)) {
                $glpi_groups = [];

                //Get all groups from GLPI DB for the current entity and the subentities
                $iterator = $DB->request([
                    'SELECT' => ['ldap_group_dn','ldap_value'],
                    'FROM'   => 'glpi_groups',
                    'WHERE'  => getEntitiesRestrictCriteria('glpi_groups'),
                ]);

                //If the group exists in DB -> unset it from the LDAP groups
                foreach ($iterator as $group) {
                    //use DN for next step
                    //depending on the type of search when groups are imported
                    //the DN may be in two separate fields
                    if (isset($group["ldap_group_dn"]) && !empty($group["ldap_group_dn"])) {
                        $glpi_groups[$group["ldap_group_dn"]] = 1;
                    } elseif (isset($group["ldap_value"]) && !empty($group["ldap_value"])) {
                        $glpi_groups[$group["ldap_value"]] = 1;
                    }
                }
                $ligne = 0;

                foreach ($infos as $dn => $info) {
                    //reconcile by DN
                    if (!isset($glpi_groups[$dn])) {
                        $groups[$ligne]["dn"]          = $dn;
                        $groups[$ligne]["cn"]          = $info["cn"];
                        $groups[$ligne]["search_type"] = $info["search_type"];
                        $ligne++;
                    }
                }
            }

            usort(
                $groups,
                function ($a, $b) use ($order) {
                    return $order == 'DESC' ? strcasecmp($b['cn'], $a['cn']) : strcasecmp($a['cn'], $b['cn']);
                }
            );
        }
        return $groups;
    }


    /**
     * Get the group's cn by giving his DN
     *
     * @param resource $ldap_connection ldap connection to use
     * @param string   $group_dn        the group's dn
     *
     * @return false|string the group cn
     */
    public static function getGroupCNByDn($ldap_connection, $group_dn)
    {

        $sr = @ldap_read($ldap_connection, $group_dn, "objectClass=*", ["cn"]);
        if ($sr === false) {
            // 32 = LDAP_NO_SUCH_OBJECT => This error can be silented as it just means that search produces no result.
            if (ldap_errno($ldap_connection) !== 32) {
                trigger_error(
                    static::buildError(
                        $ldap_connection,
                        sprintf(
                            'Unable to get LDAP group having DN `%s`',
                            $group_dn
                        )
                    ),
                    E_USER_WARNING
                );
            }
            return false;
        }
        $v  = self::get_entries_clean($ldap_connection, $sr);
        if (!is_array($v) || (count($v) == 0) || empty($v[0]["cn"][0])) {
            return false;
        }
        return $v[0]["cn"][0];
    }


    /**
     * Set groups from ldap
     *
     * @since 0.84 new parameter $limitexceeded
     *
     * @param resource $ldap_connection  LDAP connection
     * @param object   $config_ldap      LDAP configuration
     * @param string   $filter           Filters
     * @param boolean  $limitexceeded    Is limit exceeded
     * @param boolean  $search_in_groups Search in groups (true by default)
     * @param array    $groups           Groups to search
     *
     * @return array
     */
    public static function getGroupsFromLDAP(
        $ldap_connection,
        $config_ldap,
        $filter,
        &$limitexceeded,
        $search_in_groups = true,
        $groups = []
    ) {
        /** @var \DBmysql $DB */
        global $DB;

        //First look for groups in group objects
        $extra_attribute = ($search_in_groups ? "cn" : $config_ldap->fields["group_field"]);
        $attrs           = ["dn", $extra_attribute];

        if ($filter == '') {
            if ($search_in_groups) {
                $filter = (!empty($config_ldap->fields['group_condition'])
                       ? Sanitizer::unsanitize($config_ldap->fields['group_condition']) : "(objectclass=*)");
            } else {
                $filter = (!empty($config_ldap->fields['condition'])
                       ? Sanitizer::unsanitize($config_ldap->fields['condition']) : "(objectclass=*)");
            }
        }
        $cookie = '';
        $count  = 0;
        do {
            $filter = Sanitizer::unsanitize($filter);
            if (self::isLdapPageSizeAvailable($config_ldap)) {
                $controls = [
                    [
                        'oid'       => LDAP_CONTROL_PAGEDRESULTS,
                        'iscritical' => true,
                        'value'     => [
                            'size'   => $config_ldap->fields['pagesize'],
                            'cookie' => $cookie,
                        ],
                    ],
                ];
                $sr = @ldap_search($ldap_connection, $config_ldap->fields['basedn'], $filter, $attrs, 0, -1, -1, LDAP_DEREF_NEVER, $controls);
                if (
                    $sr === false
                    || @ldap_parse_result($ldap_connection, $sr, $errcode, $matcheddn, $errmsg, $referrals, $controls) === false
                ) {
                    // 32 = LDAP_NO_SUCH_OBJECT => This error can be silented as it just means that search produces no result.
                    if (ldap_errno($ldap_connection) !== 32) {
                        trigger_error(
                            static::buildError(
                                $ldap_connection,
                                sprintf('LDAP search with base DN `%s` and filter `%s` failed', $config_ldap->fields['basedn'], $filter)
                            ),
                            E_USER_WARNING
                        );
                    }
                    return $groups;
                }
                if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
                    $cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
                } else {
                    $cookie = '';
                }
            } else {
                $sr = @ldap_search($ldap_connection, $config_ldap->fields['basedn'], $filter, $attrs);
                if ($sr === false) {
                    // 32 = LDAP_NO_SUCH_OBJECT => This error can be silented as it just means that search produces no result.
                    if (ldap_errno($ldap_connection) !== 32) {
                        trigger_error(
                            static::buildError(
                                $ldap_connection,
                                sprintf('LDAP search with base DN `%s` and filter `%s` failed', $config_ldap->fields['basedn'], $filter)
                            ),
                            E_USER_WARNING
                        );
                    }
                    return $groups;
                }
            }

            if (in_array(ldap_errno($ldap_connection), [4,11])) {
                // openldap return 4 for Size limit exceeded
                $limitexceeded = true;
            }

            $infos  = self::get_entries_clean($ldap_connection, $sr);
            if (in_array(ldap_errno($ldap_connection), [4,11])) {
                // openldap return 4 for Size limit exceeded
                $limitexceeded = true;
            }

            $count += $infos['count'];
            //If page results are enabled and the number of results is greater than the maximum allowed
            //warn user that limit is exceeded and stop search
            if (
                self::isLdapPageSizeAvailable($config_ldap)
                && $config_ldap->fields['ldap_maxlimit']
                && ($count > $config_ldap->fields['ldap_maxlimit'])
            ) {
                $limitexceeded = true;
                break;
            }

            for ($ligne = 0; $ligne < $infos["count"]; $ligne++) {
                if ($search_in_groups) {
                    // No cn : not a real object
                    if (isset($infos[$ligne]["cn"][0])) {
                        $groups[$infos[$ligne]["dn"]] = (["cn" => $infos[$ligne]["cn"][0],
                            "search_type" => "groups",
                        ]);
                    }
                } else {
                    if (isset($infos[$ligne][$extra_attribute])) {
                        if (
                            ($config_ldap->fields["group_field"] == 'dn')
                            || in_array('ou', $groups)
                        ) {
                            $dn = $infos[$ligne][$extra_attribute];
                            $ou = [];
                            for ($tmp = $dn; count($tmptab = explode(',', $tmp, 2)) == 2; $tmp = $tmptab[1]) {
                                $ou[] = $tmptab[1];
                            }

                            /// Search in DB for group with ldap_group_dn
                            if (
                                ($config_ldap->fields["group_field"] == 'dn')
                                && (count($ou) > 0)
                            ) {
                                $iterator = $DB->request([
                                    'SELECT' => ['ldap_value'],
                                    'FROM'   => 'glpi_groups',
                                    'WHERE'  => [
                                        'ldap_group_dn' => Sanitizer::sanitize($ou),
                                    ],
                                ]);

                                foreach ($iterator as $group) {
                                    $groups[$group['ldap_value']] = ["cn"          => $group['ldap_value'],
                                        "search_type" => "users",
                                    ];
                                }
                            }
                        } else {
                            for (
                                $ligne_extra = 0; $ligne_extra < $infos[$ligne][$extra_attribute]["count"];
                                $ligne_extra++
                            ) {
                                $groups[$infos[$ligne][$extra_attribute][$ligne_extra]]
                                = ["cn"   => self::getGroupCNByDn(
                                    $ldap_connection,
                                    $infos[$ligne][$extra_attribute][$ligne_extra]
                                ),
                                    "search_type"
                                         => "users",
                                ];
                            }
                        }
                    }
                }
            }
        } while (($cookie !== null) && ($cookie != ''));

        return $groups;
    }


    /**
     * Form to choose a ldap server
     *
     * @param string $target target page for the form
     *
     * @return void
     */
    public static function ldapChooseDirectory($target)
    {
        /** @var \DBmysql $DB */
        global $DB;

        $iterator = $DB->request([
            'FROM'   => self::getTable(),
            'WHERE'  => [
                'is_active' => 1,
            ],
            'ORDER'  => 'name ASC',
        ]);

        if (count($iterator) == 1) {
            //If only one server, do not show the choose ldap server window
            $ldap                    = $iterator->current();
            $_SESSION["ldap_server"] = $ldap["id"];
            Html::redirect($_SERVER['PHP_SELF']);
        }

        echo TemplateRenderer::getInstance()->render('pages/admin/ldap.choose_directory.html.twig', [
            'target'          => $target,
            'nb_ldap_servers' => count($iterator),
        ]);
    }

    /**
     * Force synchronization for one user
     *
     * @param User    $user              User to synchronize
     * @param boolean $clean_ldap_fields empty user_dn and sync_field before import user again
     * @param boolean $display           Display message information on redirect (true by default)
     *
     * @return array|boolean  with state, else false
     */
    public static function forceOneUserSynchronization(User $user, $clean_ldap_fields = false, $display = true)
    {
        $authldap = new AuthLDAP();

        //Get the LDAP server from which the user has been imported
        if ($authldap->getFromDB($user->fields['auths_id'])) {
            // clean ldap fields if asked by admin
            if ($clean_ldap_fields) {
                $user->update([
                    'id'         => $user->fields['id'],
                    'user_dn'    => '',
                    'sync_field' => '',
                ]);
            }

            $user_field = 'name';
            $id_field = $authldap->fields['login_field'];
            if ($authldap->isSyncFieldEnabled() && !empty($user->fields['sync_field'])) {
                $user_field = 'sync_field';
                $id_field   = $authldap->fields['sync_field'];
            }
            return AuthLDAP::ldapImportUserByServerId(
                [
                    'method'             => self::IDENTIFIER_LOGIN,
                    'value'              => $user->fields[$user_field],
                    'identifier_field'   => $id_field,
                    'user_field'         => $user_field,
                ],
                true,
                $user->fields["auths_id"],
                $display
            );
        }
        return false;
    }

    /**
     * Import a user from a specific ldap server
     *
     * @param array   $params      of parameters: method (IDENTIFIER_LOGIN or IDENTIFIER_EMAIL) + value
     * @param boolean $action      synchoronize (true) or import (false)
     * @param integer $ldap_server ID of the LDAP server to use
     * @param boolean $display     display message information on redirect (false by default)
     *
     * @return array|boolean  with state, else false
     */
    public static function ldapImportUserByServerId(
        array $params,
        $action,
        $ldap_server,
        $display = false
    ) {
        /** @var \DBmysql $DB */
        global $DB;

        $params      = Sanitizer::unsanitize($params);
        $config_ldap = new self();
        $res         = $config_ldap->getFromDB($ldap_server);
        $input = [];

        // we prevent some delay...
        if (!$res) {
            return false;
        }

        if (!isset($params['identifier_field'])) {
            $params['identifier_field'] = $config_ldap->getLdapIdentifierToUse();
        }
        if (!isset($params['user_field'])) {
            $params['user_field'] = $config_ldap->getDatabaseIdentifierToUse();
        }

        $search_parameters = [];
        //Connect to the directory
        if (
            isset(self::$conn_cache[$ldap_server])
            // check that connection is still alive
            && @ldap_read(self::$conn_cache[$ldap_server], '', '(objectclass=*)', ['dn'], 0, 1) !== false
        ) {
            $ds = self::$conn_cache[$ldap_server];
        } else {
            $ds = $config_ldap->connect();
        }
        if ($ds) {
            self::$conn_cache[$ldap_server] = $ds;
            $search_parameters['method']                         = $params['method'];
            $search_parameters['fields'][self::IDENTIFIER_LOGIN] = $params['identifier_field'];

            if ($params['method'] == self::IDENTIFIER_EMAIL) {
                $search_parameters['fields'][self::IDENTIFIER_EMAIL]
                                       = $config_ldap->fields['email1_field'];
            }

            //Get the user's dn & login
            $attribs = ['basedn'            => $config_ldap->fields['basedn'],
                'login_field'       => $search_parameters['fields'][$search_parameters['method']],
                'search_parameters' => $search_parameters,
                'user_params'       => $params,
                'condition'         => Sanitizer::unsanitize($config_ldap->fields['condition']),
            ];

            try {
                $error = null;
                $infos = self::searchUserDn($ds, $attribs, $error);

                if ($error === true) {
                    return false;
                }

                if ($infos && $infos['dn']) {
                    $user_dn = $infos['dn'];
                    $user    = new User();

                    $login   = self::getFieldValue($infos, $search_parameters['fields'][$search_parameters['method']]);

                    //Get information from LDAP
                    if (
                        $user->getFromLDAP(
                            $ds,
                            $config_ldap->fields,
                            $user_dn,
                            Sanitizer::sanitize($login),
                            ($action == self::ACTION_IMPORT)
                        )
                    ) {
                        //Get the ID by sync field (Used to check if restoration is needed)
                        $searched_user = new User();
                        $user_found = false;
                        if ($login === null || !($user_found = $searched_user->getFromDBbySyncField(Sanitizer::sanitize($login)))) {
                            //In case user id has changed : get id by dn (Used to check if restoration is needed)
                            $user_found = $searched_user->getFromDBbyDn(Sanitizer::sanitize($user_dn));
                        }
                        if ($user_found && $searched_user->fields['is_deleted_ldap'] && $searched_user->fields['user_dn']) {
                            User::manageRestoredUserInLdap($searched_user->fields['id']);
                            return ['action' => self::USER_RESTORED_LDAP,
                                'id' => $searched_user->fields['id'],
                            ];
                        }

                        // Add the auth method
                        // Force date sync
                        $user->fields["date_sync"] = $_SESSION["glpi_currenttime"];
                        $user->fields['is_deleted_ldap'] = 0;

                        //Save information in database !
                        $input = $user->fields;

                        //clean picture from input
                        // (picture managed in User::post_addItem and prepareInputForUpdate)
                        unset($input['picture']);

                        if ($action == self::ACTION_IMPORT) {
                            $input["authtype"] = Auth::LDAP;
                            $input["auths_id"] = $ldap_server;
                            // Display message after redirect
                            if ($display) {
                                $input['add'] = 1;
                            }

                            $user->fields["id"] = $user->add($input);
                            return ['action' => self::USER_IMPORTED,
                                'id'     => $user->fields["id"],
                            ];
                        }
                        //Get the ID by user name
                        if (!($id = User::getIdByfield($params['user_field'], $login))) {
                            //In case user id as changed : get id by dn
                            $id = User::getIdByfield('user_dn', $user_dn);
                        }
                        $input['id'] = $id;

                        if ($display) {
                            $input['update'] = 1;
                        }
                        $user->update($input);
                        return ['action' => self::USER_SYNCHRONIZED,
                            'id'     => $input['id'],
                        ];
                    }
                    return false;
                }
                if ($action != self::ACTION_IMPORT) {
                    $users_id = User::getIdByField($params['user_field'], $params['value']);
                    User::manageDeletedUserInLdap($users_id);
                    return ['action' => self::USER_DELETED_LDAP,
                        'id'     => $users_id,
                    ];
                }
            } catch (\RuntimeException $e) {
                ErrorHandler::getInstance()->handleException($e);
                return false;
            }
        }
        return false;
    }


    /**
     * Import grousp from an LDAP directory
     *
     * @param string $group_dn dn of the group to import
     * @param array  $options  array for
     *             - authldaps_id
     *             - entities_id where group must to be imported
     *             - is_recursive
     *
     * @return integer|false
     */
    public static function ldapImportGroup($group_dn, $options = [])
    {

        $config_ldap = new self();
        $res         = $config_ldap->getFromDB($options['authldaps_id']);

        // we prevent some delay...
        if (!$res) {
            return false;
        }

        //Connect to the directory
        $ds = $config_ldap->connect();
        if ($ds) {
            $group_infos = self::getGroupByDn($ds, Sanitizer::unsanitize($group_dn));
            $group       = new Group();
            if ($options['type'] == "groups") {
                return $group->add(Sanitizer::sanitize([
                    "name"          => $group_infos["cn"][0],
                    "ldap_group_dn" => $group_infos["dn"],
                    "entities_id"   => $options['entities_id'],
                    "is_recursive"  => $options['is_recursive'],
                ]));
            }
            return $group->add(Sanitizer::sanitize([
                "name"         => $group_infos["cn"][0],
                "ldap_field"   => $config_ldap->fields["group_field"],
                "ldap_value"   => $group_infos["dn"],
                "entities_id"  => $options['entities_id'],
                "is_recursive" => $options['is_recursive'],
            ]));
        }
        return false;
    }


    /**
     * Open LDAP connection to current server
     *
     * @return resource|boolean
     */
    public function connect()
    {
        if ($this->fields['is_active'] != 1) {
            return false;
        }
        return $this->connectToServer(
            $this->fields['host'],
            $this->fields['port'],
            $this->fields['rootdn'],
            (new GLPIKey())->decrypt($this->fields['rootdn_passwd']),
            $this->fields['use_tls'],
            $this->fields['deref_option'],
            $this->fields['tls_certfile'],
            $this->fields['tls_keyfile'],
            $this->fields['use_bind'],
            $this->fields['timeout']
        );
    }


    /**
     * Connect to a LDAP server
     *
     * @param string  $host                 LDAP host to connect
     * @param string  $port                 port to use
     * @param string  $login                login to use (default '')
     * @param string  $password             password to use (default '')
     * @param boolean $use_tls              use a TLS connection? (false by default)
     * @param integer $deref_options        deref options used
     * @param string  $tls_certfile         TLS CERT file name within config directory (default '')
     * @param string  $tls_keyfile          TLS KEY file name within config directory (default '')
     * @param boolean $use_bind             do we need to do an ldap_bind? (true by default)
     * @param bool    $silent_bind_errors   Indicates whether bind errors must be silented
     *
     * @return resource|false|\LDAP\Connection link to the LDAP server : false if connection failed
     */
    public static function connectToServer(
        $host,
        $port,
        $login = "",
        $password = "",
        $use_tls = false,
        $deref_options = 0,
        $tls_certfile = "",
        $tls_keyfile = "",
        $use_bind = true,
        $timeout = 0,
        bool $silent_bind_errors = false
    ) {
        $ldapuri = self::buildUri($host, (int) $port);

        if (!empty($tls_certfile)) {
            if (!Filesystem::isFilepathSafe($tls_certfile)) {
                trigger_error("TLS certificate path is not safe.", E_USER_WARNING);
            } elseif (!file_exists($tls_certfile)) {
                trigger_error("TLS certificate path is not valid.", E_USER_WARNING);
            } elseif (!@ldap_set_option(null, LDAP_OPT_X_TLS_CERTFILE, $tls_certfile)) {
                trigger_error("Unable to set LDAP option `LDAP_OPT_X_TLS_CERTFILE`", E_USER_WARNING);
            }
        }
        if (!empty($tls_keyfile)) {
            if (!Filesystem::isFilepathSafe($tls_keyfile)) {
                trigger_error("TLS key file path is not safe.", E_USER_WARNING);
            } elseif (!file_exists($tls_keyfile)) {
                trigger_error("TLS key file path is not valid.", E_USER_WARNING);
            } elseif (!@ldap_set_option(null, LDAP_OPT_X_TLS_KEYFILE, $tls_keyfile)) {
                trigger_error("Unable to set LDAP option `LDAP_OPT_X_TLS_KEYFILE`", E_USER_WARNING);
            }
        }

        $ds = @ldap_connect($ldapuri);

        if ($ds === false) {
            trigger_error(
                sprintf(
                    "Unable to connect to LDAP server %s:%s",
                    $host,
                    $port
                ),
                E_USER_WARNING
            );
            return false;
        }

        $ldap_options = [
            LDAP_OPT_PROTOCOL_VERSION => 3,
            LDAP_OPT_REFERRALS        => 0,
            LDAP_OPT_DEREF            => $deref_options,
        ];

        if ($timeout > 0) {
            // Apply the timeout unless it is "unlimited" ("unlimited" is the default value defined in `libldap`).
            // see https://linux.die.net/man/3/ldap_set_option
            $ldap_options[LDAP_OPT_NETWORK_TIMEOUT] = $timeout;
        }

        foreach ($ldap_options as $option => $value) {
            if (!@ldap_set_option($ds, $option, $value)) {
                trigger_error(
                    static::buildError(
                        $ds,
                        sprintf(
                            "Unable to set LDAP option `%s` to `%s`",
                            $option,
                            $value
                        )
                    ),
                    E_USER_WARNING
                );
            }
        }

        // Only use STARTTLS if TLS is requested and the connection is not already using LDAPS
        // LDAPS (ldaps://) is already encrypted, so ldap_start_tls() should not be called
        $scheme = parse_url($ldapuri, PHP_URL_SCHEME);
        $is_ldaps = ($scheme !== null && strtolower($scheme) === 'ldaps');
        if ($use_tls && !$is_ldaps) {
            if (!@ldap_start_tls($ds)) {
                trigger_error(
                    static::buildError(
                        $ds,
                        sprintf(
                            "Unable to start TLS connection to LDAP server `%s:%s`",
                            $host,
                            $port
                        )
                    ),
                    E_USER_WARNING
                );
                return false;
            }
        }

        if (!$use_bind) {
            return $ds;
        }

        if ($login != '') {
            // Auth bind
            $b = @ldap_bind($ds, $login, $password);
        } else {
            // Anonymous bind
            $b = @ldap_bind($ds);
        }
        if ($b === false) {
            if ($silent_bind_errors === false) {
                trigger_error(
                    static::buildError(
                        $ds,
                        sprintf(
                            "Unable to bind to LDAP server `%s:%s` %s",
                            $host,
                            $port,
                            ($login != '' ? "with RDN `$login`" : 'anonymously')
                        )
                    ),
                    E_USER_WARNING
                );
            }
            return false;
        }

        return $ds;
    }


    /**
     * Try to connect to a ldap server
     *
     * @param array  $ldap_method ldap_method array to use
     * @param string $login       User Login
     * @param string $password    User Password
     *
     * @return resource|boolean link to the LDAP server : false if connection failed
     */
    public static function tryToConnectToServer($ldap_method, $login, $password)
    {
        if (!function_exists('ldap_connect')) {
            trigger_error("ldap_connect function is missing. Did you miss install php-ldap extension?", E_USER_WARNING);
            return false;
        }
        $ds = self::connectToServer(
            $ldap_method['host'],
            $ldap_method['port'],
            $ldap_method['rootdn'],
            (new GLPIKey())->decrypt($ldap_method['rootdn_passwd']),
            $ldap_method['use_tls'],
            $ldap_method['deref_option'],
            $ldap_method['tls_certfile'] ?? '',
            $ldap_method['tls_keyfile'] ?? '',
            $ldap_method['use_bind'],
            $ldap_method['timeout']
        );

        // Test with login and password of the user if exists
        if (
            !$ds
            && !empty($login)
            && (bool) $ldap_method['use_bind']
        ) {
            $ds = self::connectToServer(
                $ldap_method['host'],
                $ldap_method['port'],
                $login,
                $password,
                $ldap_method['use_tls'],
                $ldap_method['deref_option'],
                $ldap_method['tls_certfile'] ?? '',
                $ldap_method['tls_keyfile'] ?? '',
                $ldap_method['use_bind'],
                $ldap_method['timeout'],
                true // silent bind error when trying to bind with user login/password
            );
        }

        //If connection is not successful on this directory, try replicates (if replicates exists)
        if (
            !$ds
            && ($ldap_method['id'] > 0)
        ) {
            foreach (self::getAllReplicateForAMaster($ldap_method['id']) as $replicate) {
                $ds = self::connectToServer(
                    $replicate["host"],
                    $replicate["port"],
                    $ldap_method['rootdn'],
                    (new GLPIKey())->decrypt($ldap_method['rootdn_passwd']),
                    $ldap_method['use_tls'],
                    $ldap_method['deref_option'],
                    $ldap_method['tls_certfile'] ?? '',
                    $ldap_method['tls_keyfile'] ?? '',
                    $ldap_method['use_bind'],
                    $ldap_method['timeout']
                );

                // Test with login and password of the user
                if (
                    !$ds
                    && !empty($login)
                    && (bool) $ldap_method['use_bind']
                ) {
                    $ds = self::connectToServer(
                        $replicate["host"],
                        $replicate["port"],
                        $login,
                        $password,
                        $ldap_method['use_tls'],
                        $ldap_method['deref_option'],
                        $ldap_method['tls_certfile'] ?? '',
                        $ldap_method['tls_keyfile'] ?? '',
                        $ldap_method['use_bind'],
                        $ldap_method['timeout'],
                        true // silent bind error when trying to bind with user login/password
                    );
                }
                if ($ds) {
                    return $ds;
                }
            }
        }
        return $ds;
    }

    /**
     * Get LDAP servers
     *
     * @return array
     */
    public static function getLdapServers()
    {
        return getAllDataFromTable('glpi_authldaps', ['ORDER' => 'is_default DESC']);
    }


    /**
     * Is the LDAP authentication used?
     *
     * @return boolean
     */
    public static function useAuthLdap()
    {
        return (countElementsInTable('glpi_authldaps', ['is_active' => 1]) > 0);
    }


    /**
     * Import a user from ldap
     * Check all the directories. When the user is found, then import it
     *
     * @param array $options array containing condition:
     *                 array('name'=>'glpi') or array('email' => 'test at test.com')
     *
     * @return array|boolean false if fail
     */
    public static function importUserFromServers($options = [])
    {

        $auth   = new Auth();
        $params = [];
        if (isset($options['name'])) {
            $params['value']  = $options['name'];
            $params['method'] = self::IDENTIFIER_LOGIN;
        }
        if (isset($options['email'])) {
            $params['value']  = $options['email'];
            $params['method'] = self::IDENTIFIER_EMAIL;
        }

        //If the user does not exist
        if ($auth->userExists($options) === Auth::USER_DOESNT_EXIST) {
            $auth->user_present = true;
            $auth->getAuthMethods();
            $ldap_methods = $auth->authtypes["ldap"];

            foreach ($ldap_methods as $ldap_method) {
                if ($ldap_method['is_active']) {
                    //we're looking for a user login
                    $params['identifier_field']   = $ldap_method['login_field'];
                    $params['user_field']         = 'name';
                    $result = self::ldapImportUserByServerId($params, 0, $ldap_method["id"], true);
                    if ($result != false) {
                        return $result;
                    }
                }
            }
            Session::addMessageAfterRedirect(__('User not found or several users found'), false, ERROR);
        } else {
            Session::addMessageAfterRedirect(
                __('Unable to add. The user already exist.'),
                false,
                ERROR
            );
        }
        return false;
    }


    /**
     * Authentify a user by checking a specific directory
     *
     * @param Auth      $auth        identification object
     * @param string    $login       user login
     * @param string    $password    user password
     * @param array     $ldap_method ldap_method array to use
     * @param string    $user_dn     user LDAP DN if present
     * @param bool|null $error       Boolean flag that will be set to `true` if a LDAP error occurs during connection
     *
     * @return object identification object
     */
    public static function ldapAuth($auth, $login, $password, $ldap_method, $user_dn, ?bool &$error = null)
    {

        $auth->auth_succeded = false;
        $auth->extauth       = 1;

        $infos  = $auth->connection_ldap($ldap_method, $login, $password, $error);

        if ($infos === false) {
            return $auth;
        }

        $user_dn = $infos['dn'];
        $user_sync = ($infos['sync_field'] ?? null);

        if ($user_dn) {
            $auth->auth_succeded            = true;
            // try by login+auth_id and next by dn
            if (
                $auth->user->getFromDBbyNameAndAuth($login, Auth::LDAP, $ldap_method['id'])
                || $auth->user->getFromDBbyDn(Sanitizer::sanitize($user_dn))
            ) {
                //There's already an existing user in DB with the same DN but its login field has changed
                $auth->user->fields['name'] = $login;
                $auth->user_present         = true;
                $auth->user_dn              = $user_dn;
            } elseif ($user_sync !== null && $auth->user->getFromDBbySyncField($user_sync)) {
                //user login/dn have changed
                $auth->user->fields['name']      = $login;
                $auth->user->fields['user_dn']   = $user_dn;
                $auth->user_present              = true;
                $auth->user_dn                   = $user_dn;
            } else { // The user is a new user
                $auth->user_present = false;
            }
            $auth->user->getFromLDAP(
                $auth->ldap_connection,
                $ldap_method,
                $user_dn,
                $login,
                !$auth->user_present
            );
            $auth->user->fields["authtype"] = Auth::LDAP;
            $auth->user->fields["auths_id"] = $ldap_method["id"];
        }
        return $auth;
    }


    /**
     * Try to authentify a user by checking all the directories
     *
     * @param Auth    $auth     identification object
     * @param string  $login    user login
     * @param string  $password user password
     * @param integer $auths_id auths_id already used for the user (default 0)
     * @param boolean $user_dn  user LDAP DN if present (false by default)
     * @param boolean $break    if user is not found in the first directory,
     *                          continue searching on the following ones (true by default)
     *
     * @return object identification object
     */
    public static function tryLdapAuth($auth, $login, $password, $auths_id = 0, $user_dn = false, $break = true)
    {
        /** @var \DBmysql $DB */
        global $DB;

        //If no specific source is given, test all ldap directories
        if ($auths_id <= 0) {
            $user_found = false;

            $ldap_methods = $auth->authtypes["ldap"];

            // Sort servers to first try on known servers for given login.
            // It is necessary to still necessary to try to connect on all servers to handle following cases:
            //  - there are multiple users having same login on different LDAP servers,
            //  - a user has been migrated from a LDAP server to another one, but GLPI is not yet aware of this.
            // Caveat: if user uses a wrong password, a login attempt will still be done on all active LDAP servers.
            $known_servers = $DB->request(
                [
                    'SELECT' => 'auths_id',
                    'FROM'   => User::getTable(),
                    'WHERE'  => ['name' => addslashes($login)],
                ]
            );
            $known_servers_id = array_column(iterator_to_array($known_servers), 'auths_id');
            usort(
                $ldap_methods,
                function (array $a, array $b) use ($known_servers_id) {
                    if (in_array($a['id'], $known_servers_id) && !in_array($b['id'], $known_servers_id)) {
                        return -1;
                    }
                    if (!in_array($a['id'], $known_servers_id) && in_array($b['id'], $known_servers_id)) {
                        return 1;
                    }
                    return $a['id'] <=> $b['id'];
                }
            );

            foreach ($ldap_methods as $ldap_method) {
                if ($ldap_method['is_active']) {
                    $error = false;
                    $auth = self::ldapAuth($auth, $login, $password, $ldap_method, $user_dn, $error);

                    if ($error === true && in_array($ldap_method['id'], $known_servers_id)) {
                        // Remember that an error occurs on the server on which we expect user to be find.
                        // This will prevent user to be considered as deleted from the LDAP server.
                        $auth->user_ldap_error = true;
                    }

                    if ($auth->user_found) {
                        $user_found = true;
                    }

                    if (
                        $auth->auth_succeded
                        && $break
                    ) {
                        break;
                    }
                }
            }

            $auth->user_found = $user_found;
        } elseif (array_key_exists($auths_id, $auth->authtypes["ldap"])) {
            // Check if the ldap server indicated as the last good one still exists !
            //A specific ldap directory is given, test it and only this one !
            $auth = self::ldapAuth(
                $auth,
                $login,
                $password,
                $auth->authtypes["ldap"][$auths_id],
                $user_dn
            );
        }
        return $auth;
    }


    /**
     * Get dn for a user
     *
     * @param resource $ds      LDAP link
     * @param array    $options array of possible options:
     *          - basedn : base dn used to search
     *          - login_field : attribute to store login
     *          - search_parameters array of search parameters
     *          - user_params  array of parameters : method (IDENTIFIER_LOGIN or IDENTIFIER_EMAIL) + value
     *          - condition : ldap condition used
     * @param bool|null $error  Boolean flag that will be set to `true` if a LDAP error occurs during operation
     *
     * @return array|boolean dn of the user, else false
     * @throws \RuntimeException
     */
    public static function searchUserDn($ds, $options = [], ?bool &$error = null)
    {

        $values = [
            'basedn'            => '',
            'login_field'       => '',
            'search_parameters' => [],
            'user_params'       => '',
            'condition'         => '',
            'user_dn'           => false,
        ];

        foreach ($options as $key => $value) {
            $values[$key] = $value;
        }

        //By default authenticate users by login
        $login_attr      = $values['search_parameters']['fields'][self::IDENTIFIER_LOGIN];
        $sync_attr       = (isset($values['search_parameters']['fields']['sync_field'])) ?
         $values['search_parameters']['fields']['sync_field'] : null;

        $attrs = ["dn"];
        foreach ($values['search_parameters']['fields'] as $attr) {
            $attrs[] = $attr;
        }

        //First : if a user dn is provided, look for it in the directory
        //Before trying to find the user using his login_field
        if ($values['user_dn']) {
            $info = self::getUserByDn($ds, $values['user_dn'], $attrs, true, $error);

            if ($error === true) {
                return false;
            }

            if ($info) {
                $ret = [
                    'dn'        => $values['user_dn'],
                    $login_attr => $info[$login_attr][0],
                ];
                if ($sync_attr !== null && isset($info[0][$sync_attr])) {
                    $ret['sync_field'] = self::getFieldValue($info[0], $sync_attr);
                }
                return $ret;
            }
        }

        // Try a search to find the DN
        $filter_value = $values['user_params']['value'];
        if ($values['login_field'] == 'objectguid' && self::isValidGuid($filter_value)) {
            $filter_value = self::guidToHex($filter_value);
        } else {
            $filter_value = ldap_escape($filter_value, '', LDAP_ESCAPE_FILTER);
        }
        $filter = "(" . $values['login_field'] . "=" . $filter_value . ")";

        if (!empty($values['condition'])) {
            $filter = "(& $filter " . Sanitizer::unsanitize($values['condition']) . ")";
        }

        $result = @ldap_search($ds, $values['basedn'], $filter, $attrs);
        if ($result === false) {
            // 32 = LDAP_NO_SUCH_OBJECT => This error can be silented as it just means that search produces no result.
            if (ldap_errno($ds) !== 32) {
                $error = true;
                trigger_error(
                    static::buildError(
                        $ds,
                        sprintf('LDAP search with base DN `%s` and filter `%s` failed', $values['basedn'], $filter)
                    ),
                    E_USER_WARNING
                );
            }
            return false;
        }

        //search has been done, let's check for found results
        $info = self::get_entries_clean($ds, $result, $error);

        if ($error === true) {
            return false;
        }

        if (is_array($info) && ($info['count'] == 1)) {
            $ret = [
                'dn'        => $info[0]['dn'],
                $login_attr => $info[0][$login_attr][0],
            ];
            if ($sync_attr !== null && isset($info[0][$sync_attr])) {
                $ret['sync_field'] = self::getFieldValue($info[0], $sync_attr);
            }
            return $ret;
        }
        return false;
    }


    /**
     * Get an object from LDAP by giving his DN
     *
     * @param resource  $ds         the active connection to the directory
     * @param string    $condition  the LDAP filter to use for the search
     * @param string    $dn         DN of the object
     * @param array     $attrs      Array of the attributes to retrieve
     * @param boolean   $clean      (true by default)
     * @param bool|null $error      Boolean flag that will be set to `true` if a LDAP error occurs during operation
     *
     * @return array|boolean false if failed
     */
    public static function getObjectByDn($ds, $condition, $dn, $attrs = [], $clean = true, ?bool &$error = null)
    {
        if (!$clean) {
            Toolbox::deprecated('Use of $clean = false is deprecated');
        }

        $result = @ldap_read($ds, Sanitizer::unsanitize($dn), $condition, $attrs);
        if ($result === false) {
            // 32 = LDAP_NO_SUCH_OBJECT => This error can be silented as it just means that search produces no result.
            if (ldap_errno($ds) !== 32) {
                $error = true;
                trigger_error(
                    static::buildError(
                        $ds,
                        sprintf('Unable to get LDAP object having DN `%s` with filter `%s`', $dn, $condition)
                    ),
                    E_USER_WARNING
                );
            }
            return false;
        }

        $info = self::get_entries_clean($ds, $result, $error);

        if ($error === true) {
            return false;
        }

        if (is_array($info) && ($info['count'] == 1)) {
            return $info[0];
        }

        return false;
    }


    /**
     * Get user by domain name
     *
     * @param resource  $ds         the active connection to the directory
     * @param string    $user_dn    domain name
     * @param array     $attrs      attributes
     * @param boolean   $clean      (true by default)
     * @param bool|null $error      Boolean flag that will be set to `true` if a LDAP error occurs during operation
     *
     * @return array|boolean false if failed
     */
    public static function getUserByDn($ds, $user_dn, $attrs, $clean = true, ?bool &$error = null)
    {
        if (!$clean) {
            Toolbox::deprecated('Use of $clean = false is deprecated');
        }

        return self::getObjectByDn($ds, "objectClass=*", $user_dn, $attrs, $clean, $error);
    }

    /**
     * Get infos for groups
     *
     * @param resource $ds       LDAP link
     * @param string   $group_dn dn of the group
     *
     * @return array|boolean group infos if found, else false
     */
    public static function getGroupByDn($ds, $group_dn)
    {
        return self::getObjectByDn($ds, "objectClass=*", $group_dn, ["cn"]);
    }


    /**
     * Manage values stored in session
     *
     * @param array   $options Options
     * @param boolean $delete  (false by default)
     *
     * @return void
     */
    public static function manageValuesInSession($options = [], $delete = false)
    {

        $fields = ['action', 'authldaps_id', 'basedn', 'begin_date', 'criterias',  'end_date',
            'entities_id', 'interface', 'ldap_filter', 'mode',
        ];

        //If form accessed via modal, do not show expert mode link
        // Manage new value is set : entity or mode
        if (
            isset($options['entity'])
            || isset($options['mode'])
        ) {
            if (isset($options['_in_modal']) && $options['_in_modal']) {
                //If coming form the helpdesk form : reset all criterias
                $_SESSION['ldap_import']['_in_modal']      = 1;
                $_SESSION['ldap_import']['no_expert_mode'] = 1;
                $_SESSION['ldap_import']['action']         = 'show';
                $_SESSION['ldap_import']['interface']      = self::SIMPLE_INTERFACE;
                $_SESSION['ldap_import']['mode']           = self::ACTION_IMPORT;
            } else {
                $_SESSION['ldap_import']['_in_modal']      = 0;
                $_SESSION['ldap_import']['no_expert_mode'] = 0;
            }
        }

        if (!$delete) {
            if (!isset($_SESSION['ldap_import']['entities_id'])) {
                $options['entities_id'] = $_SESSION['glpiactive_entity'];
            }

            if (isset($options['toprocess'])) {
                $_SESSION['ldap_import']['action'] = 'process';
            }

            if (isset($options['change_directory'])) {
                $options['ldap_filter'] = '';
            }

            if (!isset($_SESSION['ldap_import']['authldaps_id'])) {
                $_SESSION['ldap_import']['authldaps_id'] = NOT_AVAILABLE;
            }

            if (
                (!Config::canUpdate()
                && !Entity::canUpdate())
                || (!isset($_SESSION['ldap_import']['interface'])
                && !isset($options['interface']))
            ) {
                $options['interface'] = self::SIMPLE_INTERFACE;
            }

            foreach ($fields as $field) {
                if (isset($options[$field])) {
                    $_SESSION['ldap_import'][$field] = $options[$field];
                }
            }
            if (
                isset($_SESSION['ldap_import']['begin_date'])
                && ($_SESSION['ldap_import']['begin_date'] == 'NULL')
            ) {
                $_SESSION['ldap_import']['begin_date'] = '';
            }
            if (
                isset($_SESSION['ldap_import']['end_date'])
                && ($_SESSION['ldap_import']['end_date'] == 'NULL')
            ) {
                $_SESSION['ldap_import']['end_date'] = '';
            }
            if (!isset($_SESSION['ldap_import']['criterias'])) {
                $_SESSION['ldap_import']['criterias'] = [];
            }

            $authldap = new self();
            //Filter computation
            if ($_SESSION['ldap_import']['interface'] == self::SIMPLE_INTERFACE) {
                $entity = new Entity();

                if (
                    $entity->getFromDB($_SESSION['ldap_import']['entities_id'])
                    && ($entity->getField('authldaps_id') > 0)
                ) {
                    $authldap->getFromDB($_SESSION['ldap_import']['authldaps_id']);

                    if ($_SESSION['ldap_import']['authldaps_id'] == NOT_AVAILABLE) {
                        // authldaps_id wasn't submitted by the user -> take entity config
                        $_SESSION['ldap_import']['authldaps_id'] = $entity->getField('authldaps_id');
                    }

                    $_SESSION['ldap_import']['basedn']       = $entity->getField('ldap_dn');

                    // No dn specified in entity : use standard one
                    if (empty($_SESSION['ldap_import']['basedn'])) {
                        $_SESSION['ldap_import']['basedn'] = $authldap->getField('basedn');
                    }
                } else {
                    if (
                        $_SESSION['ldap_import']['authldaps_id'] == NOT_AVAILABLE
                        || !$_SESSION['ldap_import']['authldaps_id']
                    ) {
                        $_SESSION['ldap_import']['authldaps_id'] = self::getDefault();
                    }

                    if ($_SESSION['ldap_import']['authldaps_id'] > 0) {
                        $authldap->getFromDB($_SESSION['ldap_import']['authldaps_id']);
                        $_SESSION['ldap_import']['basedn'] = $authldap->getField('basedn');
                    }
                }

                if ($entity->getField('entity_ldapfilter') != NOT_AVAILABLE) {
                    $_SESSION['ldap_import']['entity_filter'] = $entity->getField('entity_ldapfilter');
                }

                if ($_SESSION['ldap_import']['authldaps_id'] > 0) {
                    $_SESSION['ldap_import']['ldap_filter'] = self::buildLdapFilter($authldap);
                }
            } else {
                if (
                    $_SESSION['ldap_import']['authldaps_id'] == NOT_AVAILABLE
                    || !$_SESSION['ldap_import']['authldaps_id']
                ) {
                    $_SESSION['ldap_import']['authldaps_id'] = self::getDefault();

                    if ($_SESSION['ldap_import']['authldaps_id'] > 0) {
                        $authldap->getFromDB($_SESSION['ldap_import']['authldaps_id']);
                        $_SESSION['ldap_import']['basedn'] = $authldap->getField('basedn');
                    }
                }
                if (
                    !isset($_SESSION['ldap_import']['ldap_filter'])
                    || $_SESSION['ldap_import']['ldap_filter'] == ''
                ) {
                    $authldap->getFromDB($_SESSION['ldap_import']['authldaps_id']);
                    $_SESSION['ldap_import']['basedn']      = $authldap->getField('basedn');
                    $_SESSION['ldap_import']['ldap_filter'] = self::buildLdapFilter($authldap);
                }
            }
        } else { // Unset all values in session
            unset($_SESSION['ldap_import']);
        }
    }


    /**
     * Show import user form
     *
     * @param AuthLDAP $authldap AuthLDAP object
     *
     * @return void
     */
    public static function showUserImportForm(AuthLDAP $authldap)
    {

        //Get data related to entity (directory and ldap filter)
        $authldap->getFromDB($_SESSION['ldap_import']['authldaps_id']);

        echo "<form method='post' action='" . $_SERVER['PHP_SELF'] . "'>";

        echo "<h2 class='center mb-3'>" . ($_SESSION['ldap_import']['mode'] ? __('Synchronizing already imported users')
                                                      : __('Import new users'));

        // Expert interface allow user to override configuration.
        // If not coming from the ticket form, then give expert/simple link
        if (
            (Config::canUpdate()
            || Entity::canUpdate())
            && (!isset($_SESSION['ldap_import']['no_expert_mode'])
              || $_SESSION['ldap_import']['no_expert_mode'] != 1)
        ) {
            echo "<a class='float-end btn btn-secondary' href='" . $_SERVER['PHP_SELF'] . "?action=" .
              $_SESSION['ldap_import']['action'] . "&amp;mode=" . $_SESSION['ldap_import']['mode'];

            if ($_SESSION['ldap_import']['interface'] == self::SIMPLE_INTERFACE) {
                echo "&amp;interface=" . self::EXPERT_INTERFACE . "'>" . __('Expert mode') . "</a>";
            } else {
                echo "&amp;interface=" . self::SIMPLE_INTERFACE . "'>" . __('Simple mode') . "</a>";
            }
        } else {
            $_SESSION['ldap_import']['interface'] = self::SIMPLE_INTERFACE;
        }
        echo "</h2>";

        echo "<div class='card'>";
        echo "<table class='table card-table'>";

        switch ($_SESSION['ldap_import']['interface']) {
            case self::EXPERT_INTERFACE:
                //If more than one directory configured
                //Display dropdown ldap servers
                if (
                    ($_SESSION['ldap_import']['authldaps_id'] !=  NOT_AVAILABLE)
                    && ($_SESSION['ldap_import']['authldaps_id'] > 0)
                ) {
                    if (self::getNumberOfServers() > 1) {
                        $rand = mt_rand();
                        echo "<tr><td class='text-end'><label for='dropdown_authldaps_id$rand'>" . __('LDAP directory choice') . "</label></td>";
                        echo "<td colspan='3'>";
                        self::dropdown(['name'                 => 'authldaps_id',
                            'value'                => $_SESSION['ldap_import']['authldaps_id'],
                            'condition'            => ['is_active' => 1],
                            'display_emptychoice'  => false,
                            'rand'                 => $rand,
                        ]);
                        echo "&nbsp;<input class='btn btn-secondary' type='submit' name='change_directory'
                        value=\"" . _sx('button', 'Change') . "\">";
                        echo "</td></tr>";
                    }

                    echo "<tr><td style='width: 250px' class='text-end'><label for='basedn'>" . __('BaseDN') . "</label></td><td colspan='3'>";
                    echo "<input type='text' class='form-control' id='basedn' name='basedn' value=\"" . htmlspecialchars($_SESSION['ldap_import']['basedn'], ENT_QUOTES) .
                     "\" " . (!$_SESSION['ldap_import']['basedn'] ? "disabled" : "") . ">";
                    echo "</td></tr>";

                    echo "<tr><td class='text-end'><label for='ldap_filter'>" . __('Search filter for users') . "</label></td><td colspan='3'>";
                    echo "<input type='text' class='form-control' id='ldap_filter' name='ldap_filter' value=\"" .
                      htmlspecialchars($_SESSION['ldap_import']['ldap_filter'], ENT_QUOTES) . "\">";
                    echo "</td></tr>";
                }
                break;

                //case self::SIMPLE_INTERFACE :
            default:
                if (self::getNumberOfServers() > 1) {
                    $rand = mt_rand();
                    echo "<tr><td style='width: 250px' class='text-end'>
                  <label for='dropdown_authldaps_id$rand'>" . __('LDAP directory choice') . "</label>
               </td>";
                    echo "<td>";
                    self::dropdown([
                        'name'                 => 'authldaps_id',
                        'value'                => $_SESSION['ldap_import']['authldaps_id'],
                        'condition'            => ['is_active' => 1],
                        'display_emptychoice'  => false,
                        'rand'                 => $rand,
                    ]);
                    echo "&nbsp;<input class='btn btn-secondary' type='submit' name='change_directory'
                     value=\"" . _sx('button', 'Change') . "\">";
                    echo "</td></tr>";
                }

                //If multi-entity mode and more than one entity visible
                //else no need to select entity
                if (
                    Session::isMultiEntitiesMode()
                    && (count($_SESSION['glpiactiveentities']) > 1)
                ) {
                    echo "<tr><td class='text-end'>" . __('Select the desired entity') . "</td>" .
                    "<td>";
                    Entity::dropdown([
                        'value'       => $_SESSION['ldap_import']['entities_id'],
                        'entity'      => $_SESSION['glpiactiveentities'],
                        'on_change'    => 'this.form.submit()',
                    ]);
                    echo "</td></tr>";
                } else {
                    //Only one entity is active, store it
                    echo "<tr><td><input type='hidden' name='entities_id' value='" .
                              $_SESSION['glpiactive_entity'] . "'></td></tr>";
                }

                if (
                    (isset($_SESSION['ldap_import']['begin_date'])
                    && !empty($_SESSION['ldap_import']['begin_date']))
                    || (isset($_SESSION['ldap_import']['end_date'])
                    && !empty($_SESSION['ldap_import']['end_date']))
                ) {
                    $enabled = 1;
                } else {
                    $enabled = 0;
                }
                Dropdown::showAdvanceDateRestrictionSwitch($enabled);

                echo "<table class='table card-table'>";

                if (
                    ($_SESSION['ldap_import']['authldaps_id'] !=  NOT_AVAILABLE)
                    && ($_SESSION['ldap_import']['authldaps_id'] > 0)
                ) {
                    $field_counter = 0;
                    $fields        = ['login_field'     => __('Login'),
                        'sync_field'      => __('Synchronization field') . ' (' . $authldap->fields['sync_field'] . ')',
                        'email1_field'    => _n('Email', 'Emails', 1),
                        'email2_field'    => sprintf(
                            __('%1$s %2$s'),
                            _n('Email', 'Emails', 1),
                            '2'
                        ),
                        'email3_field'    => sprintf(
                            __('%1$s %2$s'),
                            _n('Email', 'Emails', 1),
                            '3'
                        ),
                        'email4_field'    => sprintf(
                            __('%1$s %2$s'),
                            _n('Email', 'Emails', 1),
                            '4'
                        ),
                        'realname_field'  => __('Surname'),
                        'firstname_field' => __('First name'),
                        'phone_field'     => _x('ldap', 'Phone'),
                        'phone2_field'    => __('Phone 2'),
                        'mobile_field'    => __('Mobile phone'),
                        'title_field'     => _x('person', 'Title'),
                        'category_field'  => _n('Category', 'Categories', 1),
                        'picture_field'   => _n('Picture', 'Pictures', 1),
                    ];
                    $available_fields = [];
                    foreach ($fields as $field => $label) {
                        if (isset($authldap->fields[$field]) && ($authldap->fields[$field] != '')) {
                            $available_fields[$field] = $label;
                        }
                    }
                    echo "<tr><td colspan='4' class='border-bottom-0'><h4>" . __('Search criteria for users') . "</h4></td></tr>";
                    foreach ($available_fields as $field => $label) {
                        if ($field_counter == 0) {
                            echo "<tr>";
                        }
                        echo "<td style='width: 250px' class='text-end'><label for='criterias$field'>$label</label></td><td>";
                        $field_counter++;
                        $field_value = '';
                        if (isset($_SESSION['ldap_import']['criterias'][$field])) {
                            $field_value = Html::entities_deep(Sanitizer::unsanitize($_SESSION['ldap_import']['criterias'][$field]));
                        }
                        echo "<input type='text' class='form-control' id='criterias$field' name='criterias[$field]' value='$field_value'>";
                        echo "</td>";
                        if ($field_counter == 2) {
                            echo "</tr>";
                            $field_counter = 0;
                        }
                    }
                    if ($field_counter > 0) {
                        while ($field_counter < 2) {
                            echo "<td colspan='2'></td>";
                            $field_counter++;
                        }
                        $field_counter = 0;
                        echo "</tr>";
                    }
                }
                break;
        }

        if (
            ($_SESSION['ldap_import']['authldaps_id'] !=  NOT_AVAILABLE)
            && ($_SESSION['ldap_import']['authldaps_id'] > 0)
        ) {
            if ($_SESSION['ldap_import']['authldaps_id']) {
                echo "<tr class='tab_bg_2'><td colspan='4' class='center'>";
                echo "<input class='btn btn-primary' type='submit' name='search' value=\"" .
                   _sx('button', 'Search') . "\">";
                echo "</td></tr>";
            } else {
                echo "<tr class='tab_bg_2'><" .
                 "td colspan='4' class='center'>" . __('No directory selected') . "</td></tr>";
            }
        } else {
            echo "<tr class='tab_bg_2'><td colspan='4' class='center'>" .
                __('No directory associated to entity: impossible search') . "</td></tr>";
        }
        echo "</table>";
        echo "</div>";
        Html::closeForm();
    }

    /**
     * Get number of servers
     *
     * @return integer
     */
    public static function getNumberOfServers()
    {
        return countElementsInTable('glpi_authldaps', ['is_active' => 1]);
    }


    /**
     * Build LDAP filter
     *
     * @param AuthLDAP $authldap AuthLDAP object
     *
     * @return string
     */
    public static function buildLdapFilter(AuthLDAP $authldap)
    {
        //Build search filter
        $counter = 0;
        $filter  = '';

        if (
            !empty($_SESSION['ldap_import']['criterias'])
            && ($_SESSION['ldap_import']['interface'] == self::SIMPLE_INTERFACE)
        ) {
            foreach ($_SESSION['ldap_import']['criterias'] as $criteria => $value) {
                if ($value != '') {
                    $begin = 0;
                    $end   = 0;
                    if (($length = strlen($value)) > 0) {
                        if ($value[0] == '^') {
                            $begin = 1;
                        }
                        if ($value[$length - 1] == '$') {
                            $end = 1;
                        }
                    }
                    if ($begin || $end) {
                        // no Toolbox::substr, to be consistent with strlen result
                        $value = substr($value, $begin, $length - $end - $begin);
                    }
                    $counter++;
                    $filter .= '(' . $authldap->fields[$criteria] . '=' . ($begin ? '' : '*') . $value . ($end ? '' : '*') . ')';
                }
            }
        } else {
            $filter = "(" . $authldap->getField("login_field") . "=*)";
        }

        //If time restriction
        $begin_date = (isset($_SESSION['ldap_import']['begin_date'])
                     && !empty($_SESSION['ldap_import']['begin_date'])
                        ? $_SESSION['ldap_import']['begin_date'] : null);
        $end_date   = (isset($_SESSION['ldap_import']['end_date'])
                     && !empty($_SESSION['ldap_import']['end_date'])
                        ? $_SESSION['ldap_import']['end_date'] : null);
        $filter    .= self::addTimestampRestrictions($begin_date, $end_date);
        $ldap_condition = Sanitizer::unsanitize($authldap->getField('condition'));
        //Add entity filter and filter filled in directory's configuration form
        return  "(&" . ($_SESSION['ldap_import']['entity_filter']
                    ?? '') . " $filter $ldap_condition)";
    }


    /**
     * Add timestamp restriction
     *
     * @param string $begin_date datetime begin date to search (NULL if not take into account)
     * @param string $end_date   datetime end date to search (NULL if not take into account)
     *
     * @return string
     */
    public static function addTimestampRestrictions($begin_date, $end_date)
    {

        $condition = '';
        //If begin date
        if (!empty($begin_date)) {
            $stampvalue = self::date2ldapTimeStamp($begin_date);
            $condition .= "(modifyTimestamp>=" . $stampvalue . ")";
        }
        //If end date
        if (!empty($end_date)) {
            $stampvalue = self::date2ldapTimeStamp($end_date);
            $condition .= "(modifyTimestamp<=" . $stampvalue . ")";
        }
        return $condition;
    }


    /**
     * Search user
     *
     * @param AuthLDAP $authldap AuthLDAP object
     *
     * @return void
     */
    public static function searchUser(AuthLDAP $authldap)
    {

        if (
            self::connectToServer(
                $authldap->getField('host'),
                $authldap->getField('port'),
                $authldap->getField('rootdn'),
                (new GLPIKey())->decrypt($authldap->getField('rootdn_passwd')),
                $authldap->getField('use_tls'),
                $authldap->getField('deref_option'),
                $authldap->getField('tls_certfile'),
                $authldap->getField('tls_keyfile'),
                $authldap->getField('use_bind'),
                $authldap->getField('timeout')
            )
        ) {
            self::showLdapUsers();
        } else {
            echo "<div class='center b firstbloc'>" . __('Unable to connect to the LDAP directory');
        }
    }

    /**
     * Get default ldap
     *
     * @return integer
     */
    public static function getDefault()
    {
        /** @var \DBmysql $DB */
        global $DB;

        foreach ($DB->request(self::getTable(), ['is_default' => 1, 'is_active' => 1]) as $data) {
            return $data['id'];
        }
        return 0;
    }

    public function post_updateItem($history = true)
    {
        /** @var \DBmysql $DB */
        global $DB;

        if (in_array('is_default', $this->updates) && $this->input["is_default"] == 1) {
            $DB->update(
                $this->getTable(),
                ['is_default' => 0],
                ['id' => ['<>', $this->input['id']]]
            );
        }
    }

    public function post_addItem()
    {
        /** @var \DBmysql $DB */
        global $DB;

        if (isset($this->fields['is_default']) && $this->fields["is_default"] == 1) {
            $DB->update(
                $this->getTable(),
                ['is_default' => 0],
                ['id' => ['<>', $this->fields['id']]]
            );
        }
    }

    public function prepareInputForAdd($input)
    {

        //If it's the first ldap directory then set it as the default directory
        if (!self::getNumberOfServers()) {
            $input['is_default'] = 1;
        }

        if (empty($input['can_support_pagesize'] ?? '')) {
            $input['can_support_pagesize'] = 0;
        }

        if (isset($input["rootdn_passwd"]) && !empty($input["rootdn_passwd"])) {
            $input["rootdn_passwd"] = (new GLPIKey())->encrypt($input["rootdn_passwd"]);
        }

        $this->checkFilesExist($input);

        return $input;
    }


    /**
     * Get LDAP deleted user action options.
     *
     * @return array
     */
    public static function getLdapDeletedUserActionOptions()
    {

        return [
            self::DELETED_USER_PRESERVE                  => __('Preserve'),
            self::DELETED_USER_DELETE                    => __('Put in trashbin'),
            self::DELETED_USER_WITHDRAWDYNINFO           => __('Withdraw dynamic authorizations and groups'),
            self::DELETED_USER_DISABLE                   => __('Disable'),
            self::DELETED_USER_DISABLEANDWITHDRAWDYNINFO => __('Disable') . ' + ' . __('Withdraw dynamic authorizations and groups'),
            self::DELETED_USER_DISABLEANDDELETEGROUPS => __('Disable') . ' + ' . __('Withdraw groups'),
        ];
    }

    /**
     * Get LDAP restored user action options.
     *
     * @since 10.0.0
     * @return array
     */
    public static function getLdapRestoredUserActionOptions()
    {
        return [
            self::RESTORED_USER_PRESERVE  => __('Do nothing'),
            self::RESTORED_USER_RESTORE   => __('Restore (move out of trashbin)'),
            self::RESTORED_USER_ENABLE    => __('Enable'),
        ];
    }

    /**
     * Builds deleted actions dropdown
     *
     * @param integer $value (default 0)
     *
     * @return string
     */
    public static function dropdownUserDeletedActions($value = 0)
    {

        $options = self::getLdapDeletedUserActionOptions();
        asort($options);
        return Dropdown::showFromArray('user_deleted_ldap', $options, ['value' => $value]);
    }

    /**
     * Builds restored actions dropdown
     *
     * @param integer $value (default 0)
     *
     * @since 10.0.0
     * @return string
     */
    public static function dropdownUserRestoredActions($value = 0)
    {

        $options = self::getLdapRestoredUserActionOptions();
        asort($options);
        return Dropdown::showFromArray('user_restored_ldap', $options, ['value' => $value]);
    }

    /**
     * Return all the ldap servers where email field is configured
     *
     * @return array of LDAP server's ID
     */
    public static function getServersWithImportByEmailActive()
    {
        /** @var \DBmysql $DB */
        global $DB;

        $ldaps = [];
        // Always get default first

        $iterator = $DB->request([
            'SELECT' => ['id'],
            'FROM'  => 'glpi_authldaps',
            'WHERE' => [
                'is_active' => 1,
                'OR'        => [
                    'email1_field' => ['<>', ''],
                    'email2_field' => ['<>', ''],
                    'email3_field' => ['<>', ''],
                    'email4_field' => ['<>', ''],
                ],
            ],
            'ORDER'  => ['is_default DESC'],
        ]);
        foreach ($iterator as $data) {
            $ldaps[] = $data['id'];
        }
        return $ldaps;
    }


    /**
     * Show date restriction form
     *
     * @param array $options Options
     *
     * @return void
     */
    public static function showDateRestrictionForm($options = [])
    {

        echo "<table class='table'>";
        echo "<tr>";

        $enabled = ($options['enabled'] ?? false);
        if (!$enabled) {
            echo "<td colspan='4'>";
            echo "<a href='#' class='btn btn-outline-secondary' onClick='activateRestriction()'>
            <i class='fas fa-toggle-off me-1'></i>
            " . __('Enable filtering by date') . "
         </a>";
            echo "</td></tr>";
        }
        if ($enabled) {
            echo "<td style='width: 250px' class='text-end border-bottom-0'>" . __('View updated users') . "</td>";
            echo "<td class='border-bottom-0'>" . __('from') . "";
            $begin_date = ($_SESSION['ldap_import']['begin_date'] ?? '');
            Html::showDateTimeField("begin_date", ['value'    => $begin_date]);
            echo "</td>";
            echo "<td class='border-bottom-0'>" . __('to') . "";
            $end_date = ($_SESSION['ldap_import']['end_date']
                        ?? date('Y-m-d H:i:s', time() - DAY_TIMESTAMP));
            Html::showDateTimeField("end_date", ['value'    => $end_date]);
            echo "</td></tr>";
            echo "<tr><td colspan='4'>";
            echo "<a href='#' class='btn btn-outline-secondary' onClick='deactivateRestriction()'>
            <i class='fas fa-toggle-on me-1'></i>
            " . __('Disable filtering by date') . "
         </a>";
            echo "</td></tr>";
        }
        echo "</table>";
    }

    public function cleanDBonPurge()
    {
        Rule::cleanForItemCriteria($this, 'LDAP_SERVER');
    }

    public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
    {
        /** @var CommonDBTM $item */
        if (
            !$withtemplate
            && $item->can($item->getField('id'), READ)
        ) {
            $ong     = [];
            $ong[1]  = _sx('button', 'Test');                     // test connexion
            $ong[2]  = User::getTypeName(Session::getPluralNumber());
            $ong[3]  = Group::getTypeName(Session::getPluralNumber());
            // TODO clean fields entity_XXX if not used
            // $ong[4]  = Entity::getTypeName(1);                  // params for entity config
            $ong[5]  = __('Advanced information');   // params for entity advanced config
            $ong[6]  = _n('Replicate', 'Replicates', Session::getPluralNumber());

            return $ong;
        }
        return '';
    }

    /**
     * Choose which form to show
     *
     * @param CommonGLPI $item         Item instance
     * @param integer    $tabnum       Tab number
     * @param integer    $withtemplate Unused
     *
     * @return boolean (TRUE)
     */
    public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0)
    {
        /** @var AuthLDAP $item */
        switch ($tabnum) {
            case 1:
                $item->showFormTestLDAP();
                break;

            case 2:
                $item->showFormUserConfig();
                break;

            case 3:
                $item->showFormGroupsConfig();
                break;

            case 4:
                $item->showFormEntityConfig();
                break;

            case 5:
                $item->showFormAdvancedConfig();
                break;

            case 6:
                $item->showFormReplicatesConfig();
                break;
        }
        return true;
    }


    /**
     * Get ldap query results and clean them at the same time
     *
     * @param resource  $link   link to the directory connection
     * @param array     $result the query results
     * @param bool|null $error  Boolean flag that will be set to `true` if a LDAP error occurs during operation
     *
     * @return array which contains ldap query results
     */
    public static function get_entries_clean($link, $result, ?bool &$error = null)
    {
        $entries = @ldap_get_entries($link, $result);
        if ($entries === false) {
            $error = true;
            trigger_error(
                static::buildError(
                    $link,
                    'Error while getting LDAP entries'
                ),
                E_USER_WARNING
            );
        }
        return $entries;
    }


    /**
     * Get all replicate servers for a master one
     *
     * @param integer $master_id master ldap server ID
     *
     * @return array of the replicate servers
     */
    public static function getAllReplicateForAMaster($master_id)
    {
        /** @var \DBmysql $DB */
        global $DB;

        $replicates = [];
        $criteria = ['FIELDS' => ['id', 'host', 'port'],
            'FROM'   => 'glpi_authldapreplicates',
            'WHERE'  => ['authldaps_id' => $master_id],
        ];
        foreach ($DB->request($criteria) as $replicate) {
            $replicates[] = ["id"   => $replicate["id"],
                "host" => $replicate["host"],
                "port" => $replicate["port"],
            ];
        }
        return $replicates;
    }

    /**
     * Check if ldap results can be paged or not
     * This functionality is available for PHP 5.4 and higher
     *
     * @since 0.84
     *
     * @param object   $config_ldap        LDAP configuration
     * @param boolean  $check_config_value Whether to check config values
     *
     * @return boolean true if maxPageSize can be used, false otherwise
     */
    public static function isLdapPageSizeAvailable($config_ldap, $check_config_value = true)
    {
        return (extension_loaded('ldap') && (!$check_config_value
         || ($check_config_value && $config_ldap->fields['can_support_pagesize'])));
    }

    /**
     * Does LDAP user already exists in the database?
     *
     * @param string  $name          User login/name
     * @param integer $authldaps_id  LDAP authentication server ID
     * @param ?string $sync          Sync field
     *
     * @return false|User
     */
    public function getLdapExistingUser($name, $authldaps_id, $sync = null)
    {
        /** @var \DBmysql $DB */
        global $DB;
        $user = new User();

        if ($sync !== null && $user->getFromDBbySyncField($DB->escape($sync))) {
            return $user;
        }

        if ($user->getFromDBbyNameAndAuth($DB->escape($name), Auth::LDAP, $authldaps_id)) {
            return $user;
        }

        return false;
    }

    /**
     * Is synchronisation field used for current server
     *
     * @return boolean
     */
    public function isSyncFieldUsed()
    {
        if ($this->getID() <= 0) {
            return false;
        }
        $count = countElementsInTable(
            'glpi_users',
            [
                'auths_id'  => $this->getID(),
                'NOT'       => ['sync_field' => null],
            ]
        );
        return $count > 0;
    }

    /**
     * Get a LDAP field value
     *
     * @param array  $infos LDAP entry infos
     * @param string $field Field name to retrieve
     *
     * @return string
     */
    public static function getFieldValue($infos, $field)
    {
        $value = null;
        if (array_key_exists($field, $infos)) {
            if (is_array($infos[$field])) {
                $value = $infos[$field][0];
            } else {
                $value = $infos[$field];
            }
        }
        if ($field != 'objectguid') {
            return $value;
        }

        //handle special objectguid from AD directories
        try {
            //prevent double encoding
            if (!self::isValidGuid($value)) {
                $value = self::guidToString($value);
                if (!self::isValidGuid($value)) {
                    throw new \RuntimeException('Not an objectguid!');
                }
            }
        } catch (\Throwable $e) {
            //well... this is not an objectguid apparently
            $value = $infos[$field];
        }

        return $value;
    }

    /**
     * Converts a string representation of an objectguid to hexadecimal
     * Used to build filters
     *
     * @param string $guid_str String representation
     *
     * @return string
     */
    public static function guidToHex($guid_str)
    {
        $str_g = explode('-', $guid_str);

        $str_g[0] = strrev($str_g[0]);
        $str_g[1] = strrev($str_g[1]);
        $str_g[2] = strrev($str_g[2]);

        $guid_hex = '\\';
        $strrev = 0;
        foreach ($str_g as $str) {
            for ($i = 0; $i < strlen($str) + 2; $i++) {
                if ($strrev < 3) {
                    $guid_hex .= strrev(substr($str, 0, 2)) . '\\';
                } else {
                    $guid_hex .= substr($str, 0, 2) . '\\';
                }
                $str = substr($str, 2);
            }
            if ($strrev < 3) {
                $guid_hex .= strrev($str);
            } else {
                $guid_hex .= $str;
            }
            $strrev++;
        }
        return $guid_hex;
    }

    /**
     * Converts binary objectguid to string representation
     *
     * @param mixed $guid_bin Binary objectguid from AD
     *
     * @return string
     */
    public static function guidToString($guid_bin)
    {
        $guid_hex = unpack("H*hex", $guid_bin);
        $hex = $guid_hex["hex"];

        $hex1 = substr($hex, -26, 2) . substr($hex, -28, 2) . substr($hex, -30, 2) . substr($hex, -32, 2);
        $hex2 = substr($hex, -22, 2) . substr($hex, -24, 2);
        $hex3 = substr($hex, -18, 2) . substr($hex, -20, 2);
        $hex4 = substr($hex, -16, 4);
        $hex5 = substr($hex, -12, 12);

        $guid_str = $hex1 . "-" . $hex2 . "-" . $hex3 . "-" . $hex4 . "-" . $hex5;
        return $guid_str;
    }

    /**
     * Check if text representation of an objectguid is valid
     *
     * @param string $guid_str String representation
     *
     * @return boolean
     */
    public static function isValidGuid($guid_str)
    {
        return (bool) preg_match('/^([0-9a-fA-F]){8}(-([0-9a-fA-F]){4}){3}-([0-9a-fA-F]){12}$/', $guid_str);
    }

    /**
     * Get the list of LDAP users to add/synchronize
     * When importing, already existing users will be filtered
     *
     * @param array   $values        possible options:
     *          - authldaps_id ID of the server to use
     *          - mode user to synchronise or add?
     *          - ldap_filter ldap filter to use
     *          - basedn force basedn (default authldaps_id one)
     *          - order display order
     *          - begin_date begin date to time limit
     *          - end_date end date to time limit
     *          - script true if called by an external script
     * @param array   $results       result stats
     * @param boolean $limitexceeded limit exceeded exception
     *
     * @return array
     */
    public static function getUsers($values, &$results, &$limitexceeded)
    {
        $users = [];
        $ldap_users    = self::getAllUsers($values, $results, $limitexceeded);

        $config_ldap   = new AuthLDAP();
        $config_ldap->getFromDB($values['authldaps_id']);

        if (!is_array($ldap_users) || count($ldap_users) == 0) {
            return $users;
        }


        $sync_field = $config_ldap->isSyncFieldEnabled() ? $config_ldap->fields['sync_field'] : null;

        foreach ($ldap_users as $userinfos) {
            $user_to_add = [];
            $user = new User();

            $user_sync_field = $config_ldap->isSyncFieldEnabled() && isset($userinfos[$sync_field])
                ? self::getFieldValue($userinfos, $sync_field)
                : null;

            $user = $config_ldap->getLdapExistingUser(
                $userinfos['user'],
                $values['authldaps_id'],
                $user_sync_field
            );
            if (isset($_SESSION['ldap_import']) && !$_SESSION['ldap_import']['mode'] && $user) {
                continue;
            }
            $user_to_add['link'] = $userinfos["user"];
            if (isset($userinfos['id']) && User::canView()) {
                $user_to_add['id']   = $userinfos['id'];
                $user_to_add['name'] = $user->fields['name'];
                $user_to_add['link'] = Toolbox::getItemTypeFormURL('User') . '?id=' . $userinfos['id'];
            }

            $user_to_add['stamp']      = (isset($userinfos["timestamp"])) ? $userinfos["timestamp"] : '';
            $user_to_add['date_sync']  = (isset($userinfos["date_sync"])) ? $userinfos["date_sync"] : '';

            $user_to_add['uid'] = $userinfos['user'];
            if ($config_ldap->isSyncFieldEnabled()) {
                if (isset($userinfos[$sync_field])) {
                    $user_to_add['uid'] = self::getFieldValue($userinfos, $sync_field);
                }

                $field_for_sync = $config_ldap->getLdapIdentifierToUse();
                if (isset($userinfos[$field_for_sync])) {
                    $user_to_add['sync_field'] = $userinfos[$field_for_sync];
                }
            }

            $users[] = $user_to_add;
        }

        return $users;
    }

    public function checkFilesExist(&$input)
    {
        if (
            isset($input['tls_certfile'])
            && strlen($input['tls_certfile']) > 0
            && (!Filesystem::isFilepathSafe($input['tls_certfile']) || !file_exists($input['tls_certfile']))
        ) {
            Session::addMessageAfterRedirect(
                __('TLS certificate path is incorrect'),
                false,
                ERROR
            );
            return false;
        }

        if (
            isset($input['tls_keyfile'])
            && strlen($input['tls_keyfile']) > 0
            && (!Filesystem::isFilepathSafe($input['tls_keyfile']) || !file_exists($input['tls_keyfile']))
        ) {
            Session::addMessageAfterRedirect(
                __('TLS key file path is incorrect'),
                false,
                ERROR
            );
            return false;
        }

        return true;
    }


    public static function getIcon()
    {
        return "far fa-address-book";
    }

    final public static function buildError($ds, string $message): string
    {
        $diag_message = '';
        $err_message  = '';
        $message = sprintf(
            "%s\nerror: %s (%s)%s%s",
            $message,
            ldap_error($ds),
            ldap_errno($ds),
            (ldap_get_option($ds, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diag_message) ? "\nextended error: " . $diag_message : ''),
            (ldap_get_option($ds, LDAP_OPT_ERROR_STRING, $err_message) ? "\nerr string: " . $err_message : '')
        );
        return $message;
    }

    /**
     * Use an LDAP connection string
     *
     * @param string $host
     * @param int $port
     *
     * @return string
     */
    final public static function buildUri(string $host, int $port): string
    {
        return sprintf(
            '%s://%s:%s',
            strtolower(parse_url($host, PHP_URL_SCHEME) ?: 'ldap'),
            preg_replace('@^ldaps?://@i', '', $host),
            $port
        );
    }
}
