<?php
/**
 * ---------------------------------------------------------------------
 * GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2015-2021 Teclib' and contributors.
 *
 * http://glpi-project.org
 *
 * based on GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2003-2014 by the INDEPNET Development Team.
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * GLPI is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * GLPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GLPI. If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------
 */

if (!defined('GLPI_ROOT')) {
   die("Sorry. You can't access this file directly");
}

use Sabre\VObject;
use Glpi\Exception\ForgetPasswordException;
use Glpi\Exception\PasswordTooWeakException;

class User extends CommonDBTM {

   // From CommonDBTM
   public $dohistory         = true;
   public $history_blacklist = ['date_mod', 'date_sync', 'last_login',
                                     'publicbookmarkorder', 'privatebookmarkorder'];

   // NAME FIRSTNAME ORDER TYPE
   const REALNAME_BEFORE   = 0;
   const FIRSTNAME_BEFORE  = 1;

   const IMPORTEXTAUTHUSERS  = 1024;
   const READAUTHENT         = 2048;
   const UPDATEAUTHENT       = 4096;

   static $rightname = 'user';

   static $undisclosedFields = [
      'password',
      'personal_token',
      'api_token',
      'cookie_token',
   ];

   private $entities = null;


   static function getTypeName($nb = 0) {
      return _n('User', 'Users', $nb);
   }

   static function getMenuShorcut() {
      return 'u';
   }

   static function getAdditionalMenuOptions() {

      if (Session::haveRight('user', self::IMPORTEXTAUTHUSERS)) {
         return [
            'ldap' => [
               'title' => AuthLDAP::getTypeName(Session::getPluralNumber()),
               'page'  => '/front/ldap.php',
            ],
         ];
      }
      return false;
   }


   function canViewItem() {
      if (Session::canViewAllEntities()
          || Session::haveAccessToOneOfEntities($this->getEntities())) {
         return true;
      }
      return false;
   }


   function canCreateItem() {

      // Will be created from form, with selected entity/profile
      if (isset($this->input['_profiles_id']) && ($this->input['_profiles_id'] > 0)
          && Profile::currentUserHaveMoreRightThan([$this->input['_profiles_id']])
          && isset($this->input['_entities_id'])
          && Session::haveAccessToEntity($this->input['_entities_id'])) {
         return true;
      }
      // Will be created with default value
      if (Session::haveAccessToEntity(0) // Access to root entity (required when no default profile)
          || (Profile::getDefault() > 0)) {
         return true;
      }

      if (($_SESSION['glpiactive_entity'] > 0)
          && (Profile::getDefault() == 0)) {
         echo "<div class='tab_cadre_fixe warning'>".
                __('You must define a default profile to create a new user')."</div>";
      }

      return false;
   }


   function canUpdateItem() {

      $entities = Profile_User::getUserEntities($this->fields['id'], false);
      if (Session::canViewAllEntities()
          || Session::haveAccessToOneOfEntities($entities)) {
         return true;
      }
      return false;
   }


   function canDeleteItem() {
      if (Session::canViewAllEntities()
          || Session::haveAccessToAllOfEntities($this->getEntities())) {
         return true;
      }
      return false;
   }


   function canPurgeItem() {
      return $this->canDeleteItem();
   }


   function isEntityAssign() {
      // glpi_users.entities_id is only a pref.
      return false;
   }


   /**
    * Compute preferences for the current user mixing config and user data.
    *
    * @return void
    */
   function computePreferences() {
      global $CFG_GLPI;

      if (isset($this->fields['id'])) {
         foreach ($CFG_GLPI['user_pref_field'] as $f) {
            if (is_null($this->fields[$f])) {
               $this->fields[$f] = $CFG_GLPI[$f];
            }
         }
      }
      /// Specific case for show_count_on_tabs : global config can forbid
      if ($CFG_GLPI['show_count_on_tabs'] == -1) {
         $this->fields['show_count_on_tabs'] = 0;
      }
   }


   /**
    * Load minimal session for user.
    *
    * @param integer $entities_id  Entity to use
    * @param boolean $is_recursive Whether to load entities recursivly or not
    *
    * @return void
    *
    * @since 0.83.7
    */
   function loadMinimalSession($entities_id, $is_recursive) {
      global $CFG_GLPI;

      if (isset($this->fields['id']) && !isset($_SESSION["glpiID"])) {
         Session::destroy();
         Session::start();
         $_SESSION["glpiID"]                      = $this->fields['id'];
         $_SESSION["glpi_use_mode"]               = Session::NORMAL_MODE;
         Session::loadEntity($entities_id, $is_recursive);
         $this->computePreferences();
         foreach ($CFG_GLPI['user_pref_field'] as $field) {
            if (isset($this->fields[$field])) {
               $_SESSION["glpi$field"] = $this->fields[$field];
            }
         }
         Session::loadGroups();
         Session::loadLanguage();
      }
   }


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

      switch ($item->getType()) {
         case __CLASS__ :
            $ong    = [];
            $ong[1] = __('Used items');
            $ong[2] = __('Managed items');
            return $ong;

         case 'Preference' :
            return __('Main');
      }
      return '';
   }


   static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
      global $CFG_GLPI;

      switch ($item->getType()) {
         case __CLASS__ :
            $item->showItems($tabnum==2);
            return true;

         case 'Preference' :
            $user = new self();
            $user->showMyForm($CFG_GLPI['root_doc']."/front/preference.php",
                              Session::getLoginUserID());
            return true;
      }
      return false;
   }


   function defineTabs($options = []) {

      $ong = [];
      $this->addDefaultFormTab($ong);
      $this->addImpactTab($ong, $options);
      $this->addStandardTab('Profile_User', $ong, $options);
      $this->addStandardTab('Group_User', $ong, $options);
      $this->addStandardTab('Config', $ong, $options);
      $this->addStandardTab(__CLASS__, $ong, $options);
      $this->addStandardTab('Ticket', $ong, $options);
      $this->addStandardTab('Item_Problem', $ong, $options);
      $this->addStandardTab('Change_Item', $ong, $options);
      $this->addStandardTab('Document_Item', $ong, $options);
      $this->addStandardTab('Reservation', $ong, $options);
      $this->addStandardTab('Auth', $ong, $options);
      $this->addStandardTab('Link', $ong, $options);
      $this->addStandardTab('Certificate_Item', $ong, $options);
      $this->addStandardTab('Log', $ong, $options);

      return $ong;
   }


   function post_getEmpty() {
      global $CFG_GLPI;

      $this->fields["is_active"] = 1;
      if (isset($CFG_GLPI["language"])) {
         $this->fields['language'] = $CFG_GLPI["language"];
      } else {
         $this->fields['language'] = "en_GB";
      }
   }


   function pre_deleteItem() {
      global $DB;

      $entities = $this->getEntities();
      $view_all = Session::canViewAllEntities();
      // Have right on all entities ?
      $all      = true;
      if (!$view_all) {
         foreach ($entities as $ent) {
            if (!Session::haveAccessToEntity($ent)) {
               $all = false;
            }
         }
      }
      if ($all) { // Mark as deleted
         return true;
      }
      // only delete profile
      foreach ($entities as $ent) {
         if (Session::haveAccessToEntity($ent)) {
            $all   = false;
            $DB->delete(
               'glpi_profiles_users', [
                  'users_id'     => $this->fields['id'],
                  'entities_id'  => $ent
               ]
            );
         }
         return false;
      }
   }


   function cleanDBonPurge() {

      global $DB;

      // ObjectLock does not extends CommonDBConnexity
      $ol = new ObjectLock();
      $ol->deleteByCriteria(['users_id' => $this->fields['id']]);

      // Reminder does not extends CommonDBConnexity
      $r = new Reminder();
      $r->deleteByCriteria(['users_id' => $this->fields['id']]);

      // Delete private bookmark
      $ss = new SavedSearch();
      $ss->deleteByCriteria(
         [
            'users_id'   => $this->fields['id'],
            'is_private' => 1,
         ]
      );

      // Set no user to public bookmark
      $DB->update(
         SavedSearch::getTable(), [
            'users_id' => 0
         ], [
            'users_id' => $this->fields['id']
         ]
      );

      // Set no user to consumables
      $DB->update(
         'glpi_consumables', [
            'items_id' => 0,
            'itemtype' => 'NULL',
            'date_out' => 'NULL'
         ], [
            'items_id' => $this->fields['id'],
            'itemtype' => 'User'
         ]
      );

      $this->deleteChildrenAndRelationsFromDb(
         [
            Certificate_Item::class,
            Change_User::class,
            Group_User::class,
            KnowbaseItem_User::class,
            Problem_User::class,
            Profile_User::class,
            ProjectTaskTeam::class,
            ProjectTeam::class,
            Reminder_User::class,
            RSSFeed_User::class,
            SavedSearch_User::class,
            Ticket_User::class,
            UserEmail::class,
         ]
      );

      if ($this->fields['id'] > 0) { // Security
         // DisplayPreference does not extends CommonDBConnexity
         $dp = new DisplayPreference();
         $dp->deleteByCriteria(['users_id' => $this->fields['id']]);
      }

      $this->dropPictureFiles($this->fields['picture']);

      // Ticket rules use various _users_id_*
      Rule::cleanForItemAction($this, '_users_id%');
      Rule::cleanForItemCriteria($this, '_users_id%');

      // Alert does not extends CommonDBConnexity
      $alert = new Alert();
      $alert->cleanDBonItemDelete($this->getType(), $this->fields['id']);
   }


   /**
    * Retrieve a user from the database using its login.
    *
    * @param string $name Login of the user
    *
    * @return boolean
    */
   function getFromDBbyName($name) {
      return $this->getFromDBByCrit(['name' => $name]);
   }

   /**
    * Retrieve a user from the database using its login.
    *
    * @param string  $name     Login of the user
    * @param integer $authtype Auth type (see Auth constants)
    * @param integer $auths_id ID of auth server
    *
    * @return boolean
    */
   function getFromDBbyNameAndAuth($name, $authtype, $auths_id) {
      return $this->getFromDBByCrit([
         'name'     => $name,
         'authtype' => $authtype,
         'auths_id' => $auths_id
         ]);
   }

   /**
    * Retrieve a user from the database using value of the sync field.
    *
    * @param string $value Value of the sync field
    *
    * @return boolean
    */
   function getFromDBbySyncField($value) {
      return $this->getFromDBByCrit(['sync_field' => $value]);
   }

   /**
    * Retrieve a user from the database using it's dn.
    *
    * @since 0.84
    *
    * @param string $user_dn dn of the user
    *
    * @return boolean
    */
   function getFromDBbyDn($user_dn) {
      return $this->getFromDBByCrit(['user_dn' => $user_dn]);
   }


   /**
    * Retrieve a user from the database using its email.
    *
    * @since 9.3 Can pass condition as a parameter
    *
    * @param string $email     user email
    * @param array  $condition add condition
    *
    * @return boolean
    */
   function getFromDBbyEmail($email, $condition = []) {
      global $DB;

      $crit = [
         'SELECT'    => $this->getTable() . '.id',
         'FROM'      => $this->getTable(),
         'LEFT JOIN'  => [
            'glpi_useremails' => [
               'FKEY' => [
                  $this->getTable() => 'id',
                  'glpi_useremails' => 'users_id'
               ]
            ]
         ],
         'WHERE'     => ['glpi_useremails.email' => $email] + $condition
      ];

      $iter = $DB->request($crit);
      if ($iter->numrows()==1) {
         $row = $iter->next();
         return $this->getFromDB($row['id']);
      }
      return false;
   }


   /**
    * Get the default email of the user.
    *
    * @return string
    */
   function getDefaultEmail() {

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

      return UserEmail::getDefaultForUser($this->fields['id']);
   }


   /**
    * Get all emails of the user.
    *
    * @return string[]
    */
   function getAllEmails() {

      if (!isset($this->fields['id'])) {
         return [];
      }
      return UserEmail::getAllForUser($this->fields['id']);
   }


   /**
    * Check if the email is attached to the current user.
    *
    * @param string $email
    *
    * @return boolean
    */
   function isEmail($email) {

      if (!isset($this->fields['id'])) {
         return false;
      }
      return UserEmail::isEmailForUser($this->fields['id'], $email);
   }


   /**
    * Retrieve a user from the database using its personal token.
    *
    * @param string $token user token
    * @param string $field the field storing the token
    *
    * @return boolean
    */
   function getFromDBbyToken($token, $field = 'personal_token') {
      $fields = ['personal_token', 'api_token'];
      if (!in_array($field, $fields)) {
         Toolbox::logWarning('User::getFromDBbyToken() can only be called with $field parameter with theses values: \'' . implode('\', \'', $fields) . '\'');
         return false;
      }

      return $this->getFromDBByCrit([$this->getTable() . ".$field" => $token]);
   }


   function prepareInputForAdd($input) {
      global $DB;

      if (isset($input['_stop_import'])) {
         return false;
      }

      if (!Auth::isValidLogin(stripslashes($input['name']))) {
         Session::addMessageAfterRedirect(__('The login is not valid. Unable to add the user.'),
                                          false, ERROR);
         return false;
      }

      // avoid xss (picture field is autogenerated)
      if (isset($input['picture'])) {
         $input['picture'] = 'NULL';
      }

      if (!isset($input["authtype"])) {
         $input["authtype"] = Auth::DB_GLPI;
      }

      if (!isset($input["auths_id"])) {
         $input["auths_id"] = 0;
      }

      // Check if user does not exists
      $iterator = $DB->request([
         'FROM'   => $this->getTable(),
         'WHERE'  => [
            'name'      => $input['name'],
            'authtype'  => $input['authtype'],
            'auths_id'  => $input['auths_id']
         ],
         'LIMIT'  => 1
      ]);

      if (count($iterator)) {
         Session::addMessageAfterRedirect(__('Unable to add. The user already exists.'),
                                          false, ERROR);
         return false;
      }

      if (isset($input["password2"])) {
         if (empty($input["password"])) {
            unset ($input["password"]);

         } else {
            if ($input["password"] == $input["password2"]) {
               if (Config::validatePassword($input["password"])) {
                  $input["password"]
                     = Auth::getPasswordHash(Toolbox::unclean_cross_side_scripting_deep(stripslashes($input["password"])));

                  $input['password_last_update'] = $_SESSION['glpi_currenttime'];
               } else {
                  unset($input["password"]);
               }
               unset($input["password2"]);
            } else {
               Session::addMessageAfterRedirect(__('Error: the two passwords do not match'),
                                                false, ERROR);
               return false;
            }
         }
      }

      if (isset($input["_extauth"])) {
         $input["password"] = "";
      }

      // Force DB default values : not really needed
      if (!isset($input["is_active"])) {
         $input["is_active"] = 1;
      }

      if (!isset($input["is_deleted"])) {
         $input["is_deleted"] = 0;
      }

      if (!isset($input["entities_id"])) {
         $input["entities_id"] = 0;
      }

      if (!isset($input["profiles_id"])) {
         $input["profiles_id"] = 0;
      }

      return $input;
   }

   public function prepareInputForClone($input) {
      if (isset($input['name'])) {
         $suffix = 1;
         $possibleName = $input['name'].$suffix;
         while ($this->getFromDBbyName($possibleName)) {
            $suffix++;
            $possibleName = $input['name'].$suffix;
         }
         $input['name'] = $possibleName;
      }
      return $input;
   }


   function post_addItem() {

      $this->updateUserEmails();
      $this->syncLdapGroups();
      $this->syncDynamicEmails();

      $this->applyGroupsRules();
      $rulesplayed = $this->applyRightRules();
      $picture     = $this->syncLdapPhoto();

      //add picture in user fields
      if (!empty($picture)) {
         $this->update(['id'      => $this->fields['id'],
                             'picture' => $picture]);
      }

      // Add default profile
      if (!$rulesplayed) {
         $affectation = [];
         if (isset($this->input['_profiles_id']) && $this->input['_profiles_id']
            && Profile::currentUserHaveMoreRightThan([$this->input['_profiles_id']])
            ) {
            $profile                   = $this->input['_profiles_id'];
            // Choosen in form, so not dynamic
            $affectation['is_dynamic'] = 0;
         } else {
            $profile                   = Profile::getDefault();
            // Default right as dynamic. If dynamic rights are set it will disappear.
            $affectation['is_dynamic'] = 1;
            $affectation['is_default_profile'] = 1;
         }

         if ($profile) {
            if (isset($this->input["_entities_id"])) {
               // entities_id (user's pref) always set in prepareInputForAdd
               // use _entities_id for default right
               $affectation["entities_id"] = $this->input["_entities_id"];

            } else if (isset($_SESSION['glpiactive_entity'])) {
               $affectation["entities_id"] = $_SESSION['glpiactive_entity'];

            } else {
               $affectation["entities_id"] = 0;
            }
            if (isset($this->input["_is_recursive"])) {
               $affectation["is_recursive"] = $this->input["_is_recursive"];
            } else {
               $affectation["is_recursive"] = 0;
            }

            $affectation["profiles_id"]  = $profile;
            $affectation["users_id"]     = $this->fields["id"];
            $right                       = new Profile_User();
            $right->add($affectation);
         }
      }
   }


   function prepareInputForUpdate($input) {
      global $CFG_GLPI;

      // avoid xss (picture name is autogenerated when uploading/synchronising the picture)
      unset($input['picture']);

      //picture manually uploaded by user
      if (isset($input["_blank_picture"]) && $input["_blank_picture"]) {
         self::dropPictureFiles($this->fields['picture']);
         $input['picture'] = 'NULL';
      } else {
         $newPicture = false;
         if (!isAPI()) {
            if (isset($input["_picture"][0]) && !empty($input["_picture"][0])) {
               $input["_picture"] = $input["_picture"][0];
            }
         }
         if (isset($input["_picture"]) && !empty($input["_picture"])) {
            $newPicture = true;
         }
         if ($newPicture) {
            $fullpath = GLPI_TMP_DIR."/".$input["_picture"];
            if (Toolbox::getMime($fullpath, 'image')) {
               // Unlink old picture (clean on changing format)
               self::dropPictureFiles($this->fields['picture']);
               // Move uploaded file
               $filename     = uniqid($this->fields['id'].'_');
               $sub          = substr($filename, -2); /* 2 hex digit */

               // output images with possible transparency to png, other to jpg
               $extension = strtolower(pathinfo($fullpath, PATHINFO_EXTENSION));
               $extension = in_array($extension, ['png', 'gif'])
                  ? 'png'
                  : 'jpg';

               @mkdir(GLPI_PICTURE_DIR . "/$sub");
               $picture_path = GLPI_PICTURE_DIR  . "/$sub/${filename}.$extension";
               self::dropPictureFiles("$sub/${filename}.$extension");

               if (Document::isImage($fullpath)
                   && Document::renameForce($fullpath, $picture_path)) {
                  Session::addMessageAfterRedirect(__('The file is valid. Upload is successful.'));
                  // For display
                  $input['picture'] = "$sub/${filename}.$extension";

                  //prepare a thumbnail
                  $thumb_path = GLPI_PICTURE_DIR . "/$sub/${filename}_min.$extension";
                  Toolbox::resizePicture($picture_path, $thumb_path);
               } else {
                  Session::addMessageAfterRedirect(__('Potential upload attack or file too large. Moving temporary file failed.'),
                        false, ERROR);
               }
            } else {
               Session::addMessageAfterRedirect(__('The file is not an image file.'),
                     false, ERROR);
            }
         } else {
            //ldap jpegphoto synchronisation.
            $picture = $this->syncLdapPhoto();
            if (!empty($picture)) {
               $input['picture'] = $picture;
            }
         }
      }

      if (isset($input["password2"])) {
         // Empty : do not update
         if (empty($input["password"])) {
            unset($input["password"]);

         } else {
            if ($input["password"] == $input["password2"]) {
               // Check right : my password of user with lesser rights
               if (isset($input['id'])
                   && !Auth::checkPassword($input['password'], $this->fields['password']) // Validate that password is not same as previous
                   && Config::validatePassword($input["password"])
                   && (($input['id'] == Session::getLoginUserID())
                       || $this->currentUserHaveMoreRightThan($input['id'])
                       // Permit to change password with token and email
                       || (($input['password_forget_token'] == $this->fields['password_forget_token'])
                           && (abs(strtotime($_SESSION["glpi_currenttime"])
                               -strtotime($this->fields['password_forget_token_date'])) < DAY_TIMESTAMP)
                           && $this->isEmail($input['email'])))) {
                  $input["password"]
                     = Auth::getPasswordHash(Toolbox::unclean_cross_side_scripting_deep(stripslashes($input["password"])));

                  $input['password_last_update'] = $_SESSION["glpi_currenttime"];
               } else {
                  unset($input["password"]);
               }
               unset($input["password2"]);

            } else {
               Session::addMessageAfterRedirect(__('Error: the two passwords do not match'),
                                                false, ERROR);
               return false;
            }
         }

      } else if (isset($input["password"])) { // From login
         unset($input["password"]);
      }

      // blank password when authtype changes
      if (isset($input["authtype"])
          && $input["authtype"] != Auth::DB_GLPI
          && $input["authtype"] != $this->getField('authtype')) {
         $input["password"] = "";
      }

      // Update User in the database
      if (!isset($input["id"])
          && isset($input["name"])) {
         if ($this->getFromDBbyName($input["name"])) {
            $input["id"] = $this->fields["id"];
         }
      }

      if (isset($input["entities_id"])
          && (Session::getLoginUserID() == $input['id'])) {
         $_SESSION["glpidefault_entity"] = $input["entities_id"];
      }

      // Security on default profile update
      if (isset($input['profiles_id'])) {
         if (!in_array($input['profiles_id'], Profile_User::getUserProfiles($input['id']))) {
            unset($input['profiles_id']);
         }
      }

      // Security on default entity  update
      if (isset($input['entities_id'])) {
         if (!in_array($input['entities_id'], Profile_User::getUserEntities($input['id']))) {
            unset($input['entities_id']);
         }
      }

      // Security on default group  update
      if (isset($input['groups_id'])
         && !Group_User::isUserInGroup($input['id'], $input['groups_id'])) {
            unset($input['groups_id']);
      }

      if (isset($input['_reset_personal_token'])
          && $input['_reset_personal_token']) {
         $input['personal_token']      = self::getUniqueToken('personal_token');
         $input['personal_token_date'] = $_SESSION['glpi_currenttime'];
      }

      if (isset($input['_reset_api_token'])
          && $input['_reset_api_token']) {
         $input['api_token']      = self::getUniqueToken('api_token');
         $input['api_token_date'] = $_SESSION['glpi_currenttime'];
      }

      // Manage preferences fields
      if (Session::getLoginUserID() == $input['id']) {
         if (isset($input['use_mode'])
             && ($_SESSION['glpi_use_mode'] !=  $input['use_mode'])) {
            $_SESSION['glpi_use_mode'] = $input['use_mode'];
            unset($_SESSION['glpimenu']); // Force menu regeneration
            //Session::loadLanguage();
         }
      }

      foreach ($CFG_GLPI['user_pref_field'] as $f) {
         if (isset($input[$f])) {
            if (Session::getLoginUserID() == $input['id']) {
               if ($_SESSION["glpi$f"] != $input[$f]) {
                  $_SESSION["glpi$f"] = $input[$f];
                  // reinit translations
                  if ($f == 'language') {
                     $_SESSION['glpi_dropdowntranslations'] = DropdownTranslation::getAvailableTranslations($_SESSION["glpilanguage"]);
                     unset($_SESSION['glpimenu']);
                  }
               }
            }
            if ($input[$f] == $CFG_GLPI[$f]) {
               $input[$f] = "NULL";
            }
         }
      }

      if (isset($input['language']) && GLPI_DEMO_MODE) {
         unset($input['language']);
      }

      if (array_key_exists('timezone', $input) && empty($input['timezone'])) {
         $input['timezone'] = 'NULL';
      }

      return $input;
   }


   function post_updateItem($history = 1) {
      //handle timezone change for current user
      if ($this->fields['id'] == Session::getLoginUserID()) {
         if (null == $this->fields['timezone'] || 'null' === strtolower($this->fields['timezone'])) {
            unset($_SESSION['glpi_tz']);
         } else {
            $_SESSION['glpi_tz'] = $this->fields['timezone'];
         }
      }

      $this->updateUserEmails();
      $this->syncLdapGroups();
      $this->syncDynamicEmails();
      $this->applyGroupsRules();
      $this->applyRightRules();

      if (in_array('password', $this->updates)) {
         $alert = new Alert();
         $alert->deleteByCriteria(
            [
               'itemtype' => $this->getType(),
               'items_id' => $this->fields['id'],
            ],
            true
         );
      }
   }



   /**
    * Apply rules to determine dynamic rights of the user.
    *
    * @return boolean true if rules are applied, false otherwise
    */
   function applyRightRules() {

      $return = false;

      if (isset($this->fields['_ruleright_process'])
          || isset($this->input['_ruleright_process'])) {

         $dynamic_profiles = Profile_User::getForUser($this->fields["id"], true);

         if (isset($this->fields["id"])
             && ($this->fields["id"] > 0)
             && isset($this->input["_ldap_rules"])
             && count($this->input["_ldap_rules"])) {

            //and add/update/delete only if it's necessary !
            if (isset($this->input["_ldap_rules"]["rules_entities_rights"])) {
               $entities_rules = $this->input["_ldap_rules"]["rules_entities_rights"];
            } else {
               $entities_rules = [];
            }

            if (isset($this->input["_ldap_rules"]["rules_entities"])) {
               $entities = $this->input["_ldap_rules"]["rules_entities"];
            } else {
               $entities = [];
            }

            if (isset($this->input["_ldap_rules"]["rules_rights"])) {
               $rights = $this->input["_ldap_rules"]["rules_rights"];
            } else {
               $rights = [];
            }

            $retrieved_dynamic_profiles = [];

            //For each affectation -> write it in DB
            foreach ($entities_rules as $entity) {
               //Multiple entities assignation
               if (is_array($entity[0])) {
                  foreach ($entity[0] as $ent) {
                     $retrieved_dynamic_profiles[] = [
                        'entities_id'  => $ent,
                        'profiles_id'  => $entity[1],
                        'is_recursive' => $entity[2],
                        'users_id'     => $this->fields['id'],
                        'is_dynamic'   => 1,
                     ];
                  }
               } else {
                  $retrieved_dynamic_profiles[] = [
                     'entities_id'  => $entity[0],
                     'profiles_id'  => $entity[1],
                     'is_recursive' => $entity[2],
                     'users_id'     => $this->fields['id'],
                     'is_dynamic'   => 1,
                  ];
               }
            }

            if ((count($entities) > 0)
                && (count($rights) == 0)) {
               if ($def_prof = Profile::getDefault()) {
                  $rights[] = $def_prof;
               }
            }

            if ((count($rights) > 0)
                && (count($entities) > 0)) {
               foreach ($rights as $right) {
                  foreach ($entities as $entity) {
                     $retrieved_dynamic_profiles[] = [
                        'entities_id'  => $entity[0],
                        'profiles_id'  => $right,
                        'is_recursive' => $entity[1],
                        'users_id'     => $this->fields['id'],
                        'is_dynamic'   => 1,
                     ];
                  }
               }
            }

            // Compare retrived profiles to existing ones : clean arrays to do purge and add
            if (count($retrieved_dynamic_profiles)) {
               foreach ($retrieved_dynamic_profiles as $keyretr => $retr_profile) {
                  $found = false;

                  foreach ($dynamic_profiles as $keydb => $db_profile) {
                     // Found existing profile : unset values in array
                     if (!$found
                         && ($db_profile['entities_id']  == $retr_profile['entities_id'])
                         && ($db_profile['profiles_id']  == $retr_profile['profiles_id'])
                         && ($db_profile['is_recursive'] == $retr_profile['is_recursive'])) {

                        unset($retrieved_dynamic_profiles[$keyretr]);
                        unset($dynamic_profiles[$keydb]);
                     }
                  }
               }
            }

            // Add new dynamic profiles
            if (count($retrieved_dynamic_profiles)) {
               $right = new Profile_User();
               foreach ($retrieved_dynamic_profiles as $keyretr => $retr_profile) {
                  $right->add($retr_profile);
               }
            }

            //Unset all the temporary tables
            unset($this->input["_ldap_rules"]);

            $return = true;
         } else if (count($dynamic_profiles) == 1) {
            $dynamic_profile = reset($dynamic_profiles);

            // If no rule applied and only one dynamic profile found, check if
            // it is the default profile
            if ($dynamic_profile['is_default_profile'] == true) {
               $default_profile = Profile::getDefault();

               // Remove from to be deleted list
               $dynamic_profiles = [];

               // Update profile if need to match the current default profile
               if ($dynamic_profile['profiles_id'] !== $default_profile) {
                  $pu = new Profile_User();
                  $dynamic_profile['profiles_id'] = $default_profile;
                  $pu->add($dynamic_profile);
                  $pu->delete([
                     'id' => $dynamic_profile['id']
                  ]);
               }
            }
         }

         // Delete old dynamic profiles
         if (count($dynamic_profiles)) {
            $right = new Profile_User();
            foreach ($dynamic_profiles as $keydb => $db_profile) {
               $right->delete($db_profile);
            }
         }

      }
      return $return;
   }


   /**
    * Synchronise LDAP group of the user.
    *
    * @return void
    */
   function syncLdapGroups() {
      global $DB;

      // input["_groups"] not set when update from user.form or preference
      if (isset($this->fields["authtype"])
          && isset($this->input["_groups"])
          && (($this->fields["authtype"] == Auth::LDAP)
              || Auth::isAlternateAuth($this->fields['authtype']))) {

         if (isset($this->fields["id"]) && ($this->fields["id"] > 0)) {
            $authtype = Auth::getMethodsByID($this->fields["authtype"], $this->fields["auths_id"]);

            if (count($authtype)) {
               // Clean groups
               $this->input["_groups"] = array_unique ($this->input["_groups"]);

               // Delete not available groups like to LDAP
               $iterator = $DB->request([
                  'SELECT'    => [
                     'glpi_groups_users.id',
                     'glpi_groups_users.groups_id',
                     'glpi_groups_users.is_dynamic'
                  ],
                  'FROM'      => 'glpi_groups_users',
                  'LEFT JOIN' => [
                     'glpi_groups'  => [
                        'FKEY'   => [
                           'glpi_groups_users'  => 'groups_id',
                           'glpi_groups'        => 'id'
                        ]
                     ]
                  ],
                  'WHERE'     => [
                     'glpi_groups_users.users_id' => $this->fields['id']
                  ]
               ]);

               $groupuser = new Group_User();
               while ($data =  $iterator->next()) {

                  if (in_array($data["groups_id"], $this->input["_groups"])) {
                     // Delete found item in order not to add it again
                     unset($this->input["_groups"][array_search($data["groups_id"],
                           $this->input["_groups"])]);

                  } else if ($data['is_dynamic']) {
                     $groupuser->delete(['id' => $data["id"]]);
                  }
               }

               //If the user needs to be added to one group or more
               if (count($this->input["_groups"]) > 0) {
                  foreach ($this->input["_groups"] as $group) {
                     $groupuser->add(['users_id'   => $this->fields["id"],
                                           'groups_id'  => $group,
                                           'is_dynamic' => 1]);
                  }
                  unset ($this->input["_groups"]);
               }
            }
         }
      }
   }


   /**
    * Synchronize picture (photo) of the user.
    *
    * @since 0.85
    *
    * @return string|boolean Filename to be stored in user picture field, false if no picture found
    */
   function syncLdapPhoto() {

      if (isset($this->fields["authtype"])
          && (($this->fields["authtype"] == Auth::LDAP)
               || ($this->fields["authtype"] == Auth::NOT_YET_AUTHENTIFIED
                   && !empty($this->fields["auths_id"]))
               || Auth::isAlternateAuth($this->fields['authtype']))) {

         if (isset($this->fields["id"]) && ($this->fields["id"] > 0)) {
            $config_ldap = new AuthLDAP();
            $ds          = false;

            //connect ldap server
            if ($config_ldap->getFromDB($this->fields['auths_id'])) {
               $ds = $config_ldap->connect();
            }

            if ($ds) {
               //get picture fields
               $picture_field = $config_ldap->fields['picture_field'];
               if (empty($picture_field)) {
                  return false;
               }

               //get picture content in ldap
               $info = AuthLDAP::getUserByDn($ds, $this->fields['user_dn'],
                                             [$picture_field], false);

               //getUserByDn returns an array. If the picture is empty,
               //$info[$picture_field][0] is null
               if (!isset($info[$picture_field][0]) || empty($info[$picture_field][0])) {
                  return "";
               }
               //prepare paths
               $img       = array_pop($info[$picture_field]);
               $filename  = uniqid($this->fields['id'].'_');
               $sub       = substr($filename, -2); /* 2 hex digit */
               $file      = GLPI_PICTURE_DIR . "/$sub/${filename}.jpg";

               if (array_key_exists('picture', $this->fields)) {
                  $oldfile = GLPI_PICTURE_DIR . "/" . $this->fields["picture"];
               } else {
                  $oldfile = null;
               }

               // update picture if not exist or changed
               if (empty($this->fields["picture"])
                   || !file_exists($oldfile)
                   || sha1_file($oldfile) !== sha1($img)) {
                  if (!is_dir(GLPI_PICTURE_DIR . "/$sub")) {
                     mkdir(GLPI_PICTURE_DIR . "/$sub");
                  }

                  //save picture
                  $outjpeg = fopen($file, 'wb');
                  fwrite($outjpeg, $img);
                  fclose ($outjpeg);

                  //save thumbnail
                  $thumb = GLPI_PICTURE_DIR . "/$sub/${filename}_min.jpg";
                  Toolbox::resizePicture($file, $thumb);

                  return "$sub/${filename}.jpg";
               }
               return $this->fields["picture"];
            }
         }
      }

      return false;
   }


   /**
    * Update emails of the user.
    * Uses _useremails set from UI, not _emails set from LDAP.
    *
    * @return void
    */
   function updateUserEmails() {
      // Update emails  (use _useremails set from UI, not _emails set from LDAP)

      $userUpdated = false;

      if (isset($this->input['_useremails']) && count($this->input['_useremails'])) {
         $useremail = new UserEmail();
         foreach ($this->input['_useremails'] as $id => $email) {
            $email = trim($email);

            // existing email
            if ($id > 0) {
               $params = ['id' => $id];

               // empty email : delete
               if (strlen($email) == 0) {
                  $deleted = $useremail->delete($params);
                  $userUpdated = $userUpdated || $deleted;

               } else { // Update email
                  $params['email'] = $email;
                  $params['is_default'] = $this->input['_default_email'] == $id ? 1 : 0;

                  $existingUserEmail = new UserEmail();
                  $existingUserEmail->getFromDB($id);
                  if ($params['email'] == $existingUserEmail->fields['email']
                      && $params['is_default'] == $existingUserEmail->fields['is_default']) {
                     // Do not update if email has not changed
                     continue;
                  }

                  $updated = $useremail->update($params);
                  $userUpdated = $userUpdated || $updated;
               }

            } else { // New email
               $email_input = ['email'    => $email,
                               'users_id' => $this->fields['id']];
               if (isset($this->input['_default_email'])
                   && ($this->input['_default_email'] == $id)) {
                  $email_input['is_default'] = 1;
               } else {
                  $email_input['is_default'] = 0;
               }
               $added = $useremail->add($email_input);
               $userUpdated = $userUpdated || $added;
            }
         }
      }

      if ($userUpdated) {
         // calling $this->update() here leads to loss in $this->input
         $user = new User();
         $user->update(['id' => $this->fields['id'], 'date_mod' => $_SESSION['glpi_currenttime']]);
      }
   }


   /**
    * Synchronise Dynamics emails of the user.
    * Uses _emails (set from getFromLDAP), not _usermails set from UI.
    *
    * @return void
    */
   function syncDynamicEmails() {
      global $DB;

      $userUpdated = false;

      // input["_emails"] not set when update from user.form or preference
      if (isset($this->fields["authtype"])
          && isset($this->input["_emails"])
          && (($this->fields["authtype"] == Auth::LDAP)
              || Auth::isAlternateAuth($this->fields['authtype'])
              || ($this->fields["authtype"] == Auth::MAIL))) {

         if (isset($this->fields["id"]) && ($this->fields["id"] > 0)) {
            $authtype = Auth::getMethodsByID($this->fields["authtype"], $this->fields["auths_id"]);

            if (count($authtype)
                || $this->fields["authtype"] == Auth::EXTERNAL) {
               // Clean emails
               // Do a case insensitive comparison as it seems that some LDAP servers
               // may return same email with different case sensitivity.
               $unique_emails = [];
               foreach ($this->input["_emails"] as $email) {
                  if (!in_array(strtolower($email), array_map('strtolower', $unique_emails))) {
                     $unique_emails[] = $email;
                  }
               }
               $this->input["_emails"] = $unique_emails;

               // Delete not available groups like to LDAP
               $iterator = $DB->request([
                  'SELECT' => [
                     'id',
                     'users_id',
                     'email',
                     'is_dynamic'
                  ],
                  'FROM'   => 'glpi_useremails',
                  'WHERE'  => ['users_id' => $this->fields['id']]
               ]);

               $useremail = new UserEmail();
               while ($data = $iterator->next()) {
                  // Do a case insensitive comparison as email may be stored with a different case
                  $i = array_search(strtolower($data["email"]), array_map('strtolower', $this->input["_emails"]));
                  if ($i !== false) {
                     // Delete found item in order not to add it again
                     unset($this->input["_emails"][$i]);
                  } else if ($data['is_dynamic']) {
                     // Delete not found email
                     $deleted = $useremail->delete(['id' => $data["id"]]);
                     $userUpdated = $userUpdated || $deleted;
                  }
               }

               //If the email need to be added
               if (count($this->input["_emails"]) > 0) {
                  foreach ($this->input["_emails"] as $email) {
                     $added = $useremail->add(['users_id'   => $this->fields["id"],
                                               'email'      => $email,
                                               'is_dynamic' => 1]);
                     $userUpdated = $userUpdated || $added;
                  }
                  unset ($this->input["_emails"]);
               }
            }
         }
      }

      if ($userUpdated) {
         // calling $this->update() here leads to loss in $this->input
         $user = new User();
         $user->update(['id' => $this->fields['id'], 'date_mod' => $_SESSION['glpi_currenttime']]);
      }
   }

   protected function computeFriendlyName() {
      global $CFG_GLPI;

      if (isset($this->fields["id"]) && ($this->fields["id"] > 0)) {
         //computeFriendlyName should not add ID
         $bkp_conf = $CFG_GLPI['is_ids_visible'];
         $CFG_GLPI['is_ids_visible'] = 0;
         $bkp_sessconf = (isset($_SESSION['glpiis_ids_visible']) ? $_SESSION["glpiis_ids_visible"] : 0);
         $_SESSION["glpiis_ids_visible"] = 0;
         $name = formatUserName($this->fields["id"],
                               $this->fields["name"],
                               (isset($this->fields["realname"]) ? $this->fields["realname"] : ''),
                               (isset($this->fields["firstname"]) ? $this->fields["firstname"] : ''));

         $CFG_GLPI['is_ids_visible'] = $bkp_conf;
         $_SESSION["glpiis_ids_visible"] = $bkp_sessconf;
         return $name;
      }
      return '';
   }


   /**
    * Function that tries to load the user membership from LDAP
    * by searching in the attributes of the User.
    *
    * @param resource $ldap_connection LDAP connection
    * @param array    $ldap_method     LDAP method
    * @param string   $userdn          Basedn of the user
    * @param string   $login           User login
    *
    * @return string|boolean Basedn of the user / false if not found
    */
   private function getFromLDAPGroupVirtual($ldap_connection, array $ldap_method, $userdn, $login) {
      global $DB;

      // Search in DB the ldap_field we need to search for in LDAP
      $iterator = $DB->request([
         'SELECT'          => 'ldap_field',
         'DISTINCT'        => true,
         'FROM'            => 'glpi_groups',
         'WHERE'           => ['NOT' => ['ldap_field' => '']],
         'ORDER'           => 'ldap_field'
      ]);
      $group_fields = [];

      while ($data = $iterator->next()) {
         $group_fields[] = Toolbox::strtolower($data["ldap_field"]);
      }
      if (count($group_fields)) {
         //Need to sort the array because edirectory don't like it!
         sort($group_fields);

         // If the groups must be retrieve from the ldap user object
         $sr = @ ldap_read($ldap_connection, $userdn, "objectClass=*", $group_fields);
         $v  = AuthLDAP::get_entries_clean($ldap_connection, $sr);

         for ($i=0; $i < $v['count']; $i++) {
            //Try to find is DN in present and needed: if yes, then extract only the OU from it
            if ((($ldap_method["group_field"] == 'dn') || in_array('ou', $group_fields))
                && isset($v[$i]['dn'])) {

               $v[$i]['ou'] = [];
               for ($tmp=$v[$i]['dn']; count($tmptab = explode(',', $tmp, 2))==2; $tmp=$tmptab[1]) {
                  $v[$i]['ou'][] = $tmptab[1];
               }

               // Search in DB for group with ldap_group_dn
               if (($ldap_method["group_field"] == 'dn')
                   && (count($v[$i]['ou']) > 0)) {
                  $group_iterator = $DB->request([
                     'SELECT' => 'id',
                     'FROM'   => 'glpi_groups',
                     'WHERE'  => ['ldap_group_dn' => Toolbox::addslashes_deep($v[$i]['ou'])]
                  ]);

                  while ($group = $group_iterator->next()) {
                     $this->fields["_groups"][] = $group['id'];
                  }
               }

               // searching with ldap_field='OU' and ldap_value is also possible
               $v[$i]['ou']['count'] = count($v[$i]['ou']);
            }

            // For each attribute retrieve from LDAP, search in the DB
            foreach ($group_fields as $field) {
               if (isset($v[$i][$field])
                   && isset($v[$i][$field]['count'])
                   && ($v[$i][$field]['count'] > 0)) {

                  unset($v[$i][$field]['count']);
                  $lgroups = [];
                  foreach (Toolbox::addslashes_deep($v[$i][$field]) as $lgroup) {
                     $lgroups[] = [
                        new \QueryExpression($DB::quoteValue($lgroup).
                                             " LIKE ".
                                             $DB::quoteName('ldap_value'))
                     ];
                  }
                  $group_iterator = $DB->request([
                     'SELECT' => 'id',
                     'FROM'   => 'glpi_groups',
                     'WHERE'  => [
                        'ldap_field' => $field,
                        'OR'         => $lgroups
                     ]
                  ]);

                  while ($group = $group_iterator->next()) {
                     $this->fields["_groups"][] = $group['id'];
                  }
               }
            }
         } // for each ldapresult
      } // count($group_fields)
   }


   /**
    * Function that tries to load the user membership from LDAP
    * by searching in the attributes of the Groups.
    *
    * @param resource $ldap_connection    LDAP connection
    * @param array    $ldap_method        LDAP method
    * @param string   $userdn             Basedn of the user
    * @param string   $login              User login
    *
    * @return boolean true if search is applicable, false otherwise
    */
   private function getFromLDAPGroupDiscret($ldap_connection, array $ldap_method, $userdn, $login) {
      global $DB;

      // No group_member_field : unable to get group
      if (empty($ldap_method["group_member_field"])) {
         return false;
      }

      if ($ldap_method["use_dn"]) {
         $user_tmp = $userdn;
      } else {
         //Don't add $ldap_method["login_field"]."=", because sometimes it may not work (for example with posixGroup)
         $user_tmp = $login;
      }

      $v = $this->ldap_get_user_groups($ldap_connection, $ldap_method["basedn"],
                                       $user_tmp,
                                       $ldap_method["group_condition"],
                                       $ldap_method["group_member_field"],
                                       $ldap_method["use_dn"],
                                       $ldap_method["login_field"]);
      foreach ($v as $result) {
         if (isset($result[$ldap_method["group_member_field"]])
             && is_array($result[$ldap_method["group_member_field"]])
             && (count($result[$ldap_method["group_member_field"]]) > 0)) {

             $iterator = $DB->request([
               'SELECT' => 'id',
               'FROM'   => 'glpi_groups',
               'WHERE'  => ['ldap_group_dn' => Toolbox::addslashes_deep($result[$ldap_method["group_member_field"]])]
             ]);

            while ($group = $iterator->next()) {
               $this->fields["_groups"][] = $group['id'];
            }
         }
      }
      return true;
   }


   /**
    * Function that tries to load the user information from LDAP.
    *
    * @param resource $ldap_connection LDAP connection
    * @param array    $ldap_method     LDAP method
    * @param string   $userdn          Basedn of the user
    * @param string   $login           User Login
    * @param boolean  $import          true for import, false for update
    *
    * @return boolean true if found / false if not
    */
   function getFromLDAP($ldap_connection, array $ldap_method, $userdn, $login, $import = true) {
      global $DB, $CFG_GLPI;

      // we prevent some delay...
      if (empty($ldap_method["host"])) {
         return false;
      }

      if (is_resource($ldap_connection)) {
         //Set all the search fields
         $this->fields['password'] = "";

         $fields  = AuthLDAP::getSyncFields($ldap_method);

         //Hook to allow plugin to request more attributes from ldap
         $fields = Plugin::doHookFunction("retrieve_more_field_from_ldap", $fields);

         $fields  = array_filter($fields);
         $f       = self::getLdapFieldNames($fields);

         $sr      = @ ldap_read($ldap_connection, $userdn, "objectClass=*", $f);
         $v       = AuthLDAP::get_entries_clean($ldap_connection, $sr);

         if (!is_array($v)
             || ( count($v) == 0)
             || empty($v[0][$fields['name']][0])) {
            return false;
         }

         //Store user's dn
         $this->fields['user_dn']    = addslashes($userdn);
         //Store date_sync
         $this->fields['date_sync']  = $_SESSION['glpi_currenttime'];
         // Empty array to ensure than syncDynamicEmails will be done
         $this->fields["_emails"]    = [];
         // force authtype as we retrieve this user by ldap (we could have login with SSO)
         $this->fields["authtype"] = Auth::LDAP;

         foreach ($fields as $k => $e) {
            $val = AuthLDAP::getFieldValue(
               [$e => self::getLdapFieldValue($e, $v)],
               $e
            );
            if (empty($val)) {
               switch ($k) {
                  case "language" :
                     // Not set value : managed but user class
                     break;

                  case "usertitles_id" :
                  case "usercategories_id" :
                  case 'locations_id' :
                  case 'users_id_supervisor' :
                     $this->fields[$k] = 0;
                     break;

                  default :
                     $this->fields[$k] = "";
               }

            } else {
               $val = Toolbox::addslashes_deep($val);
               switch ($k) {
                  case "email1" :
                  case "email2" :
                  case "email3" :
                  case "email4" :
                     // Manage multivaluable fields
                     if (!empty($v[0][$e])) {
                        foreach ($v[0][$e] as $km => $m) {
                           if (!preg_match('/count/', $km)) {
                              $this->fields["_emails"][] = addslashes($m);
                           }
                        }
                        // Only get them once if duplicated
                        $this->fields["_emails"] = array_unique($this->fields["_emails"]);
                     }
                     break;

                  case "language" :
                     $language = Config::getLanguage($val);
                     if ($language != '') {
                        $this->fields[$k] = $language;
                     }
                     break;

                  case "usertitles_id" :
                     $this->fields[$k] = Dropdown::importExternal('UserTitle', $val);
                     break;

                  case 'locations_id' :
                     // use import to build the location tree
                     $this->fields[$k] = Dropdown::import('Location',
                                                          ['completename' => $val,
                                                           'entities_id'  => 0,
                                                           'is_recursive' => 1]);
                    break;

                  case "usercategories_id" :
                     $this->fields[$k] = Dropdown::importExternal('UserCategory', $val);
                     break;

                  case 'users_id_supervisor':
                     $this->fields[$k] = self::getIdByField('user_dn', $val, false);
                     break;

                  default :
                     $this->fields[$k] = $val;
               }
            }
         }

         // Empty array to ensure than syncLdapGroups will be done
         $this->fields["_groups"] = [];

         ///The groups are retrieved by looking into an ldap user object
         if (($ldap_method["group_search_type"] == 0)
             || ($ldap_method["group_search_type"] == 2)) {
            $this->getFromLDAPGroupVirtual($ldap_connection, $ldap_method, $userdn, $login);
         }

         ///The groups are retrived by looking into an ldap group object
         if (($ldap_method["group_search_type"] == 1)
             || ($ldap_method["group_search_type"] == 2)) {
            $this->getFromLDAPGroupDiscret($ldap_connection, $ldap_method, $userdn, $login);
         }

         ///Only process rules if working on the master database
         if (!$DB->isSlave()) {
            //Instanciate the affectation's rule
            $rule = new RuleRightCollection();

            //Process affectation rules :
            //we don't care about the function's return because all
            //the datas are stored in session temporary
            if (isset($this->fields["_groups"])) {
               $groups = $this->fields["_groups"];
            } else {
               $groups = [];
            }

            $this->fields = $rule->processAllRules($groups, Toolbox::stripslashes_deep($this->fields), [
               'type'        => Auth::LDAP,
               'ldap_server' => $ldap_method["id"],
               'connection'  => $ldap_connection,
               'userdn'      => $userdn,
               'login'       => $this->fields['name'],
               'mail_email'  => $this->fields['_emails']
            ]);

            $this->fields['_ruleright_process'] = true;

            //If rule  action is ignore import
            if ($import
                && isset($this->fields["_stop_import"])) {
               return false;
            }
            //or no rights found & do not import users with no rights
            if ($import
                && !$CFG_GLPI["use_noright_users_add"]) {
               $ok = false;
               if (isset($this->fields["_ldap_rules"])
                   && count($this->fields["_ldap_rules"])) {
                  if (isset($this->fields["_ldap_rules"]["rules_entities_rights"])
                      && count($this->fields["_ldap_rules"]["rules_entities_rights"])) {
                     $ok = true;
                  }
                  if (!$ok) {
                     $entity_count = 0;
                     $right_count  = 0;
                     if (Profile::getDefault()) {
                        $right_count++;
                     }
                     if (isset($this->fields["_ldap_rules"]["rules_entities"])) {
                        $entity_count += count($this->fields["_ldap_rules"]["rules_entities"]);
                     }
                     if (isset($this->input["_ldap_rules"]["rules_rights"])) {
                        $right_count += count($this->fields["_ldap_rules"]["rules_rights"]);
                     }
                     if ($entity_count && $right_count) {
                        $ok = true;
                     }
                  }
               }
               if (!$ok) {
                  $this->fields["_stop_import"] = true;
                  return false;
               }
            }

            // Add ldap result to data send to the hook
            $this->fields['_ldap_result'] = $v;
            $this->fields['_ldap_conn']   = $ldap_connection;
            //Hook to retrieve more information for ldap
            $this->fields = Plugin::doHookFunction("retrieve_more_data_from_ldap", $this->fields);
            unset($this->fields['_ldap_result']);
         }
         return true;
      }
      return false;

   } // getFromLDAP()


   /**
    * Get all groups a user belongs to.
    *
    * @param resource $ds                 ldap connection
    * @param string   $ldap_base_dn       Basedn used
    * @param string   $user_dn            Basedn of the user
    * @param string   $group_condition    group search condition
    * @param string   $group_member_field group field member in a user object
    * @param boolean  $use_dn             search dn of user ($login_field=$user_dn) in group_member_field
    * @param string   $login_field        user login field
    *
    * @return array Groups of the user located in [0][$group_member_field] in returned array
    */
   function ldap_get_user_groups($ds, $ldap_base_dn, $user_dn, $group_condition,
                                 $group_member_field, $use_dn, $login_field) {

      $groups     = [];
      $listgroups = [];

      //User dn may contain ( or ), need to espace it!
      $user_dn = str_replace(["(", ")", "\,", "\+"], ["\(", "\)", "\\\,", "\\\+"],
                             $user_dn);

      //Only retrive cn and member attributes from groups
      $attrs = ['dn'];

      if (!$use_dn) {
         $filter = "(& $group_condition (|($group_member_field=$user_dn)
                                          ($group_member_field=$login_field=$user_dn)))";
      } else {
         $filter = "(& $group_condition ($group_member_field=$user_dn))";
      }

      //Perform the search
      $filter = Toolbox::unclean_cross_side_scripting_deep($filter);
      $sr     = ldap_search($ds, $ldap_base_dn, $filter, $attrs);

      //Get the result of the search as an array
      $info = AuthLDAP::get_entries_clean($ds, $sr);
      //Browse all the groups
      $info_count = count($info);
      for ($i = 0; $i < $info_count; $i++) {
         //Get the cn of the group and add it to the list of groups
         if (isset($info[$i]["dn"]) && ($info[$i]["dn"] != '')) {
            $listgroups[$i] = $info[$i]["dn"];
         }
      }

      //Create an array with the list of groups of the user
      $groups[0][$group_member_field] = $listgroups;
      //Return the groups of the user
      return $groups;
   }


   /**
    * Function that tries to load the user information from IMAP.
    *
    * @param array  $mail_method  mail method description array
    * @param string $name         login of the user
    *
    * @return boolean true if method is applicable, false otherwise
    */
   function getFromIMAP(array $mail_method, $name) {
      global $DB;

      // we prevent some delay..
      if (empty($mail_method["host"])) {
         return false;
      }

      // some defaults...
      $this->fields['password']  = "";
      // Empty array to ensure than syncDynamicEmails will be done
      $this->fields["_emails"]   = [];
      $email                     = '';
      if (strpos($name, "@")) {
         $email = $name;
      } else {
         $email = $name . "@" . $mail_method["host"];
      }
      $this->fields["_emails"][] = $email;

      $this->fields['name']      = $name;
      //Store date_sync
      $this->fields['date_sync'] = $_SESSION['glpi_currenttime'];
      // force authtype as we retrieve this user by imap (we could have login with SSO)
      $this->fields["authtype"] = Auth::MAIL;

      if (!$DB->isSlave()) {
         //Instanciate the affectation's rule
         $rule = new RuleRightCollection();

         //Process affectation rules :
         //we don't care about the function's return because all the datas are stored in session temporary
         if (isset($this->fields["_groups"])) {
            $groups = $this->fields["_groups"];
         } else {
            $groups = [];
         }
         $this->fields = $rule->processAllRules($groups, Toolbox::stripslashes_deep($this->fields), [
            'type'        => Auth::MAIL,
            'mail_server' => $mail_method["id"],
            'login'       => $name,
            'email'       => $email]
         );
         $this->fields['_ruleright_process'] = true;
      }
      return true;
   }


   /**
    * Function that tries to load the user information from the SSO server.
    *
    * @since 0.84
    *
    * @return boolean true if method is applicable, false otherwise
    */
   function getFromSSO() {
      global $DB, $CFG_GLPI;

      $a_field = [];
      foreach ($CFG_GLPI as $key=>$value) {
         if (!is_array($value) && !empty($value)
             && strstr($key, "_ssofield")) {
            $key = str_replace('_ssofield', '', $key);
            $a_field[$key] = $value;
         }
      }

      if (count($a_field) == 0) {
         return true;
      }
      $this->fields['_ruleright_process'] = true;
      foreach ($a_field as $field=>$value) {
         if (!isset($_SERVER[$value])
             || empty($_SERVER[$value])) {

            switch ($field) {
               case "title" :
                  $this->fields['usertitles_id'] = 0;
                  break;

               case "category" :
                  $this->fields['usercategories_id'] = 0;
                  break;

               default :
                  $this->fields[$field] = "";
            }

         } else {
            switch ($field) {
               case "email1" :
               case "email2" :
               case "email3" :
               case "email4" :
                  // Manage multivaluable fields
                  if (!preg_match('/count/', $_SERVER[$value])) {
                     $this->fields["_emails"][] = addslashes($_SERVER[$value]);
                  }
                  // Only get them once if duplicated
                  $this->fields["_emails"] = array_unique($this->fields["_emails"]);
                  break;

               case "language" :
                  $language = Config::getLanguage($_SERVER[$value]);
                  if ($language != '') {
                     $this->fields[$field] = $language;
                  }
                  break;

               case "title" :
                  $this->fields['usertitles_id']
                        = Dropdown::importExternal('UserTitle', addslashes($_SERVER[$value]));
                  break;

               case "category" :
                  $this->fields['usercategories_id']
                        = Dropdown::importExternal('UserCategory', addslashes($_SERVER[$value]));
                  break;

               default :
                  $this->fields[$field] = $_SERVER[$value];
                  break;

            }
         }
      }
       ///Only process rules if working on the master database
      if (!$DB->isSlave()) {
         //Instanciate the affectation's rule
         $rule = new RuleRightCollection();

         $this->fields = $rule->processAllRules([], Toolbox::stripslashes_deep($this->fields), [
            'type'   => Auth::EXTERNAL,
            'email'  => $this->fields["_emails"],
            'login'  => $this->fields["name"]
         ]);

         //If rule  action is ignore import
         if (isset($this->fields["_stop_import"])) {
            return false;
         }
      }
      return true;
   }


   /**
    * Blank passwords field of a user in the DB.
    * Needed for external auth users.
    *
    * @return void
    */
   function blankPassword() {
      global $DB;

      if (!empty($this->fields["name"])) {
         $DB->update(
            $this->getTable(), [
               'password' => ''
            ], [
               'name' => $this->fields['name']
            ]
         );
      }
   }


   /**
    * Print a good title for user pages.
    *
    * @return void
    */
   function title() {
      global $CFG_GLPI;

      $buttons = [];
      $title   = self::getTypeName(Session::getPluralNumber());

      if (static::canCreate()) {
         $buttons["user.form.php"] = __('Add user...');
         $title                    = "";

         if (Auth::useAuthExt()
             && Session::haveRight("user", self::IMPORTEXTAUTHUSERS)) {
            // This requires write access because don't use entity config.
            $buttons["user.form.php?new=1&amp;ext_auth=1"] = __('... From an external source');
         }
      }
      if (Session::haveRight("user", self::IMPORTEXTAUTHUSERS)
         && (static::canCreate() || static::canUpdate())) {
         if (AuthLDAP::useAuthLdap()) {
            $buttons["ldap.php"] = __('LDAP directory link');
         }
      }
      Html::displayTitle($CFG_GLPI["root_doc"] . "/pics/users.png", self::getTypeName(Session::getPluralNumber()), $title,
                         $buttons);
   }


   /**
    * Check if current user have more right than the specified one.
    *
    * @param integer $ID ID of the user
    *
    * @return boolean
    */
   function currentUserHaveMoreRightThan($ID) {

      $user_prof = Profile_User::getUserProfiles($ID);
      return Profile::currentUserHaveMoreRightThan($user_prof);
   }


   /**
    * Print the user form.
    *
    * @param integer $ID    ID of the user
    * @param array $options Options
    *     - string   target        Form target
    *     - boolean  withtemplate  Template or basic item
    *
    * @return boolean true if user found, false otherwise
    */
   function showForm($ID, array $options = []) {
      global $CFG_GLPI, $DB;

      // Affiche un formulaire User
      if (($ID != Session::getLoginUserID()) && !self::canView()) {
         return false;
      }

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

      $ismyself = $ID == Session::getLoginUserID();
      $higherrights = $this->currentUserHaveMoreRightThan($ID);
      if ($ID) {
         $caneditpassword = $higherrights || ($ismyself && Session::haveRight('password_update', 1));
      } else {
         // can edit on creation form
         $caneditpassword = true;
      }

      $extauth = !(($this->fields["authtype"] == Auth::DB_GLPI)
                   || (($this->fields["authtype"] == Auth::NOT_YET_AUTHENTIFIED)
                       && !empty($this->fields["password"])));

      $formtitle = $this->getTypeName(1);

      if ($ID > 0) {
         $formtitle .= "<a class='pointer far fa-address-card fa-lg' target='_blank' href='".
                       User::getFormURLWithID($ID)."&amp;getvcard=1' title='".__s('Download user VCard').
                       "'><span class='sr-only'>". __('Vcard')."</span></a>";
         if (Session::canImpersonate($ID)) {
            $formtitle .= '<button type="button" class="pointer btn-linkstyled btn-impersonate" name="impersonate" value="1">'
               . '<i class="fas fa-user-secret fa-lg" title="' . __s('Impersonate') . '"></i> '
               . '<span class="sr-only">' . __s('Impersonate') . '</span>'
               . '</button>';

            // "impersonate" button type is set to "button" on form display to prevent it to be used
            // by default (as it is the first found in current form) when pressing "enter" key.
            // When clicking it, switch to "submit" type to make it submit current user form.
            $impersonate_js = <<<JAVASCRIPT
               (function($) {
                  $('button[type="button"][name="impersonate"]').click(
                     function () {
                        $(this).attr('type', 'submit');
                     }
                  );
               })(jQuery);
JAVASCRIPT;
            $formtitle .= Html::scriptBlock($impersonate_js);
         }
      }

      $options['formtitle']   = $formtitle;
      $options['formoptions'] = ($options['formoptions'] ?? '') . " enctype='multipart/form-data'";
      $this->showFormHeader($options);
      $rand = mt_rand();

      echo "<tr class='tab_bg_1'>";
      echo "<td><label for='name'>" . __('Login') . "</label></td>";
      if ($this->fields["name"] == "" ||
          !empty($this->fields["password"])
          || ($this->fields["authtype"] == Auth::DB_GLPI)) {
         //display login field for new records, or if this is not external auth
         echo "<td><input name='name' id='name' value=\"" . $this->fields["name"] . "\"></td>";
      } else {
         echo "<td class='b'>" . $this->fields["name"];
         echo "<input type='hidden' name='name' value=\"" . $this->fields["name"] . "\"></td>";
      }

      if (!empty($this->fields["name"])) {
         echo "<td rowspan='7'>" . __('Picture') . "</td>";
         echo "<td rowspan='7'>";
         echo "<div class='user_picture_border_small' id='picture$rand'>";
         echo "<img class='user_picture_small' alt=\"".__s('Picture')."\" src='".
                User::getThumbnailURLForPicture($this->fields['picture'])."'>";
         // echo "<img src='".self::getURLForPicture($this->fields["picture"])."' class='user_picture'/>";
         echo "</div>";
         $full_picture = "<div class='user_picture_border'>";
         $full_picture .= "<img class='user_picture' alt=\"".__s('Picture')."\" src='".
                            User::getURLForPicture($this->fields['picture'])."'>";
         $full_picture .= "</div>";

         Html::showTooltip($full_picture, ['applyto' => "picture$rand"]);
         echo Html::file(['name' => 'picture', 'display' => false, 'onlyimages' => true]);
         echo "<input type='checkbox' name='_blank_picture'>&nbsp;".__('Clear');
         echo "</td>";
      } else {
         echo "<td rowspan='7'></td>";
         echo "<td rowspan='7'></td>";
      }
      echo "</tr>";

      //If it's an external auth, check if the sync_field must be displayed
      if ($extauth
         && $this->fields['auths_id']
            && AuthLDAP::isSyncFieldConfigured($this->fields['auths_id'])) {
         $syncrand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='textfield_sync_field$syncrand'>" . __('Synchronization field') . "</label></td><td>";
         if (self::canUpdate()
             && (!$extauth || empty($ID))) {
                Html::autocompletionTextField($this, "sync_field", ['rand' => $syncrand]);
         } else {
            if (empty($this->fields['sync_field'])) {
               echo Dropdown::EMPTY_VALUE;
            } else {
               echo $this->fields['sync_field'];
            }
         }
         echo "</td></tr>";
      } else {
         echo "<tr class='tab_bg_1'><td colspan='2'></td></tr>";
      }

      $surnamerand = mt_rand();
      echo "<tr class='tab_bg_1'><td><label for='textfield_realname$surnamerand'>" . __('Surname') . "</label></td><td>";
      Html::autocompletionTextField($this, "realname", ['rand' => $surnamerand]);
      echo "</td></tr>";

      $firstnamerand = mt_rand();
      echo "<tr class='tab_bg_1'><td><label for='textfield_firstname$firstnamerand'>" . __('First name') . "</label></td><td>";
      Html::autocompletionTextField($this, "firstname", ['rand' => $firstnamerand]);
      echo "</td></tr>";

      //do some rights verification
      if (self::canUpdate()
          && (!$extauth || empty($ID))
          && $caneditpassword) {
         echo "<tr class='tab_bg_1'>";
         echo "<td><label for='password'>" . __('Password')."</label></td>";
         echo "<td><input id='password' type='password' name='password' value='' size='20'
                    autocomplete='new-password' onkeyup=\"return passwordCheck();\"></td>";

         echo "<tr class='tab_bg_1'>";
         echo "<td><label for='password2'>" . __('Password confirmation') . "</label></td>";
         echo "<td><input type='password' id='password2' name='password2' value='' size='20' autocomplete='new-password'>";
         echo "</td></tr>";

         if ($CFG_GLPI["use_password_security"]) {
            echo "<tr class='tab_bg_1'>";
            echo "<td rowspan='2'>";
            echo __('Password security policy');
            echo "</td>";
            echo "<td rowspan='2'>";
            Config::displayPasswordSecurityChecks();
            echo "</td>";
            echo "</tr>";
         }

      } else {
         echo "<tr class='tab_bg_1'><td></td><td></td></tr>";
         echo "<tr class='tab_bg_1'><td></td><td></td></tr>";
      }

      $tz_warning = '';
      $tz_available = $DB->areTimezonesAvailable($tz_warning);
      if ($tz_available || Session::haveRight("config", READ)) {
         echo "<tr class='tab_bg_1'>";
         echo "<td><label for='timezone'>".__('Time zone')."</label></td><td>";
         if ($tz_available) {
            $timezones = $DB->getTimezones();
            Dropdown::showFromArray(
               'timezone',
               $timezones, [
                  'value'                 => $this->fields["timezone"],
                  'display_emptychoice'   => true,
                  'emptylabel'            => __('Use server configuration')
               ]
            );
         } else if (Session::haveRight("config", READ)) {
            // Display a warning but only if user is more or less an admin
            echo "<img src=\"{$CFG_GLPI['root_doc']}/pics/warning_min.png\">";
            echo $tz_warning;
         }
         echo "</td></tr>";
      }

      echo "<tr class='tab_bg_1'>";
      if (!GLPI_DEMO_MODE) {
         $activerand = mt_rand();
         echo "<td><label for='dropdown_is_active$activerand'>".__('Active')."</label></td><td>";
         Dropdown::showYesNo('is_active', $this->fields['is_active'], -1, ['rand' => $activerand]);
         echo "</td>";
      } else {
         echo "<td colspan='2'></td>";
      }
      echo "<td>" . _n('Email', 'Emails', Session::getPluralNumber());
      UserEmail::showAddEmailButton($this);
      echo "</td><td>";
      UserEmail::showForUser($this);
      echo "</td>";
      echo "</tr>";

      if (!GLPI_DEMO_MODE) {
         $sincerand = mt_rand();
         echo "<tr class='tab_bg_1'>";
         echo "<td><label for='showdate$sincerand'>".__('Valid since')."</label></td><td>";
         Html::showDateTimeField("begin_date", ['value'       => $this->fields["begin_date"],
                                                'rand'        => $sincerand,
                                                'maybeempty'  => true]);
         echo "</td>";

         $untilrand = mt_rand();
         echo "<td><label for='showdate$untilrand'>".__('Valid until')."</label></td><td>";
         Html::showDateTimeField("end_date", ['value'       => $this->fields["end_date"],
                                              'rand'        => $untilrand,
                                              'maybeempty'  => true]);
         echo "</td></tr>";
      }

      $phonerand = mt_rand();
      echo "<tr class='tab_bg_1'>";
      echo "<td><label for='textfield_phone$phonerand'>" .  Phone::getTypeName(1) . "</label></td><td>";
      Html::autocompletionTextField($this, "phone", ['rand' => $phonerand]);
      echo "</td>";
      //Authentications information : auth method used and server used
      //don't display is creation of a new user'
      if (!empty($ID)) {
         if (Session::haveRight(self::$rightname, self::READAUTHENT)) {
            echo "<td>" . __('Authentication') . "</td><td>";
            echo Auth::getMethodName($this->fields["authtype"], $this->fields["auths_id"]);
            if (!empty($this->fields["date_sync"])) {
               //TRANS: %s is the date of last sync
               echo '<br>'.sprintf(__('Last synchronization on %s'),
                                   Html::convDateTime($this->fields["date_sync"]));
            }
            if (!empty($this->fields["user_dn"])) {
               //TRANS: %s is the user dn
               echo '<br>'.sprintf(__('%1$s: %2$s'), __('User DN'), $this->fields["user_dn"]);
            }
            if ($this->fields['is_deleted_ldap']) {
               echo '<br>'.__('User missing in LDAP directory');
            }

            echo "</td>";
         } else {
            echo "<td colspan='2'>&nbsp;</td>";
         }
      } else {
         echo "<td colspan='2'><input type='hidden' name='authtype' value='1'></td>";
      }

      echo "</tr>";

      $mobilerand = mt_rand();
      echo "<tr class='tab_bg_1'>";
      echo "<td><label for='textfield_mobile$mobilerand'>" . __('Mobile phone') . "</label></td><td>";
      Html::autocompletionTextField($this, "mobile", ['rand' => $mobilerand]);
      echo "</td>";
      $catrand = mt_rand();
      echo "<td><label for='dropdown_usercategories_id$catrand'>" . __('Category') . "</label></td><td>";
      UserCategory::dropdown(['value' => $this->fields["usercategories_id"], 'rand' => $catrand]);
      echo "</td></tr>";

      $phone2rand = mt_rand();
      echo "<tr class='tab_bg_1'>";
      echo "<td><label for='textfield_phone2$phone2rand'>" .  __('Phone 2') . "</label></td><td>";
      Html::autocompletionTextField($this, "phone2", ['rand' => $phone2rand]);
      echo "</td>";
      echo "<td rowspan='4' class='middle'><label for='comment'>" . __('Comments') . "</label></td>";
      echo "<td class='center middle' rowspan='4'>";
      echo "<textarea cols='45' rows='6' id='comment' name='comment' >".$this->fields["comment"]."</textarea>";
      echo "</td></tr>";

      $admnumrand = mt_rand();
      echo "<tr class='tab_bg_1'><td><label for='textfield_registration_number$admnumrand'>" . __('Administrative number') . "</label></td><td>";
      Html::autocompletionTextField($this, "registration_number", ['rand' => $admnumrand]);
      echo "</td></tr>";

      $titlerand = mt_rand();
      echo "<tr class='tab_bg_1'><td><label for='dropdown_usertitles_id$titlerand'>" . _x('person', 'Title') . "</label></td><td>";
      UserTitle::dropdown(['value' => $this->fields["usertitles_id"], 'rand' => $titlerand]);
      echo "</td></tr>";

      echo "<tr class='tab_bg_1'>";
      if (!empty($ID)) {
         $locrand = mt_rand();
         echo "<td><label for='dropdown_locations_id$locrand'>" . Location::getTypeName(1) . "</label></td><td>";
         $entities = $this->getEntities();
         if (count($entities) <= 0) {
            $entities = -1;
         }
         Location::dropdown(['value'  => $this->fields["locations_id"],
                             'rand'   => $locrand,
                             'entity' => $entities]);
         echo "</td>";
      }
      echo "</tr>";

      if (empty($ID)) {
         echo "<tr class='tab_bg_1'>";
         echo "<th colspan='2'>"._n('Authorization', 'Authorizations', 1)."</th>";
         $recurrand = mt_rand();
         echo "<td><label for='dropdown__is_recursive$recurrand'>" .  __('Recursive') . "</label></td><td>";
         Dropdown::showYesNo("_is_recursive", 0, -1, ['rand' => $recurrand]);
         echo "</td></tr>";
         $profilerand = mt_rand();
         echo "<tr class='tab_bg_1'>";
         echo "<td><label for='dropdown__profiles_id$profilerand'>" .  Profile::getTypeName(1) . "</label></td><td>";
         Profile::dropdownUnder(['name'  => '_profiles_id',
                                 'rand'  => $profilerand,
                                 'value' => Profile::getDefault()]);

         $entrand = mt_rand();
         echo "</td><td><label for='dropdown__entities_id$entrand'>" .  Entity::getTypeName(1) . "</label></td><td>";
         Entity::dropdown(['name'                => '_entities_id',
                           'display_emptychoice' => false,
                           'rand'                => $entrand,
                           'entity'              => $_SESSION['glpiactiveentities']]);
         echo "</td></tr>";
      } else {
         if ($higherrights || $ismyself) {
            $profilerand = mt_rand();
            echo "<tr class='tab_bg_1'>";
            echo "<td><label for='dropdown_profiles_id$profilerand'>" .  __('Default profile') . "</label></td><td>";

            $options   = Dropdown::getDropdownArrayNames('glpi_profiles',
                                                         Profile_User::getUserProfiles($this->fields['id']));

            Dropdown::showFromArray("profiles_id", $options,
                                    ['value'               => $this->fields["profiles_id"],
                                     'rand'                => $profilerand,
                                     'display_emptychoice' => true]);
         }
         if ($higherrights) {
            $entrand = mt_rand();
            echo "</td><td><label for='dropdown_entities_id$entrand'>" .  __('Default entity') . "</label></td><td>";
            $entities = $this->getEntities();
            Entity::dropdown(['value'  => $this->fields["entities_id"],
                              'rand'   => $entrand,
                              'entity' => $entities]);
            echo "</td></tr>";

            $grouprand = mt_rand();
            echo "<tr class='tab_bg_1'>";
            echo "<td><label for='dropdown_profiles_id$grouprand'>" .  __('Default group') . "</label></td><td>";

            $options = [];
            foreach (Group_User::getUserGroups($this->fields['id']) as $group) {
               $options[$group['id']] = $group['completename'];
            }

            Dropdown::showFromArray("groups_id", $options,
                                    ['value'               => $this->fields["groups_id"],
                                     'rand'                => $grouprand,
                                     'display_emptychoice' => true]);

            echo "</td>";
            $userrand = mt_rand();
            echo "<td><label for='dropdown_users_id_supervisor_$userrand'>" .  __('Responsible') . "</label></td><td>";

            User::dropdown(['name'   => 'users_id_supervisor',
                            'value'  => $this->fields["users_id_supervisor"],
                            'rand'   => $userrand,
                            'entity' => $_SESSION["glpiactive_entity"],
                            'right'  => 'all']);
            echo "</td></tr>";
         }

         if ($this->can($ID, UPDATE)) {
            echo "<tr class='tab_bg_1'><th colspan='4'>". __('Remote access keys') ."</th></tr>";

            echo "<tr class='tab_bg_1'><td>";
            echo __("Personal token");
            echo "</td><td colspan='2'>";

            if (!empty($this->fields["personal_token"])) {
               echo "<div class='copy_to_clipboard_wrapper'>";
               echo Html::input('_personal_token', [
                                    'value'    => $this->fields["personal_token"],
                                    'style'    => 'width:90%'
                                ]);
               echo "</div>";
               echo "(".sprintf(__('generated on %s'),
                                   Html::convDateTime($this->fields["personal_token_date"])).")";
            }
            echo "</td><td>";
            Html::showCheckbox(['name'  => '_reset_personal_token',
                                'title' => __('Regenerate')]);
            echo "&nbsp;&nbsp;".__('Regenerate');
            echo "</td></tr>";

            echo "<tr class='tab_bg_1'><td>";
            echo __("API token");
            echo "</td><td colspan='2'>";
            if (!empty($this->fields["api_token"])) {
               echo "<div class='copy_to_clipboard_wrapper'>";
               echo Html::input('_api_token', [
                                    'value'    => $this->fields["api_token"],
                                    'style'    => 'width:90%'
                                ]);
               echo "</div>";
               echo "(".sprintf(__('generated on %s'),
                                   Html::convDateTime($this->fields["api_token_date"])).")";
            }
            echo "</td><td>";
            Html::showCheckbox(['name'  => '_reset_api_token',
                                'title' => __('Regenerate')]);
            echo "&nbsp;&nbsp;".__('Regenerate');
            echo "</td></tr>";
         }

         echo "<tr class='tab_bg_1'>";
         echo "<td colspan='2' class='center'>";
         if ($this->fields["last_login"]) {
            printf(__('Last login on %s'), Html::convDateTime($this->fields["last_login"]));
         }
         echo "</td><td colspan='2'class='center'>";

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

      $this->showFormButtons($options);

      return true;
   }


   /** Print the user personnal information for check.
    *
    * @param integer $userid ID of the user
    *
    * @return void|boolean false if user is not the current user, otherwise print form
    *
    * @since 0.84
    */
   static function showPersonalInformation($userid) {
      global $CFG_GLPI;

      $user = new self();
      if (!$user->can($userid, READ)
          && ($userid != Session::getLoginUserID())) {
         return false;
      }
      echo "<table class='tab_glpi left' width='100%'>";
      echo "<tr class='tab_bg_1'>";
      echo "<td class='b' width='20%'>";
      echo __('Name');
      echo "</td><td width='30%'>";
      echo getUserName($userid);
      echo "</td>";
      echo "<td class='b'  width='20%'>";
      echo Phone::getTypeName(1);
      echo "</td><td width='30%'>";
      echo $user->getField('phone');
      echo "</td>";
      echo "</tr>";

      echo "<tr class='tab_bg_1'>";
      echo "<td class='b'>";
      echo __('Phone 2');
      echo "</td><td>";
      echo $user->getField('phone2');
      echo "</td>";
      echo "<td class='b'>";
      echo __('Mobile phone');
      echo "</td><td>";
      echo $user->getField('mobile');
      echo "</td>";
      echo "</tr>";

      echo "<tr class='tab_bg_1'>";
      echo "<td class='b'>";
      echo _n('Email', 'Emails', 1);
      echo "</td><td>";
      echo $user->getDefaultEmail();
      echo "</td></tr>";

      echo "<tr class='tab_bg_1'>";
      echo "<td class='b'>";
      echo Location::getTypeName(1);
      echo "</td><td>";
      echo Dropdown::getDropdownName('glpi_locations', $user->getField('locations_id'));
      echo "</td>";
      echo "<td colspan='2' class='center'>";
      if ($userid == Session::getLoginUserID()) {
         echo "<a href='".$CFG_GLPI['root_doc']."/front/preference.php' class='vsubmit'>".
               __('Edit')."</a>";
      } else {
         echo "&nbsp;";
      }
      echo "</td>";
      echo "</tr>";
      echo "</table>";
   }


   /**
    * Print the user preference form.
    *
    * @param string  $target Form target
    * @param integer $ID     ID of the user
    *
    * @return boolean true if user found, false otherwise
    */
   function showMyForm($target, $ID) {
      global $CFG_GLPI, $DB;

      // Affiche un formulaire User
      if (($ID != Session::getLoginUserID())
          && !$this->currentUserHaveMoreRightThan($ID)) {
         return false;
      }
      if ($this->getFromDB($ID)) {
         $rand     = mt_rand();
         $authtype = $this->getAuthMethodsByID();

         $extauth  = !(($this->fields["authtype"] == Auth::DB_GLPI)
                       || (($this->fields["authtype"] == Auth::NOT_YET_AUTHENTIFIED)
                           && !empty($this->fields["password"])));

         // No autocopletion :
         $save_autocompletion                 = $CFG_GLPI["use_ajax_autocompletion"];
         $CFG_GLPI["use_ajax_autocompletion"] = false;

         echo "<div class='center'>";
         echo "<form method='post' name='user_manager' enctype='multipart/form-data' action='".$target."' autocomplete='off'>";
         echo "<table class='tab_cadre_fixe'>";
         echo "<tr><th colspan='4'>".sprintf(__('%1$s: %2$s'), __('Login'), $this->fields["name"]);
         echo "<input type='hidden' name='name' value='" . $this->fields["name"] . "'>";
         echo "<input type='hidden' name='id' value='" . $this->fields["id"] . "'>";
         echo "</th></tr>";

         $surnamerand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='textfield_realname$surnamerand'>" . __('Surname') . "</label></td><td>";

         if ($extauth
             && isset($authtype['realname_field'])
             && !empty($authtype['realname_field'])) {

            echo $this->fields["realname"];
         } else {
            Html::autocompletionTextField($this, "realname", ['rand' => $surnamerand]);
         }
         echo "</td>";

         if (!empty($this->fields["name"])) {
            echo "<td rowspan='7'>" . __('Picture') . "</td>";
            echo "<td rowspan='7'>";
            echo "<div class='user_picture_border_small' id='picture$rand'>";
            echo "<img class='user_picture_small' alt=\"".__s('Picture')."\" src='".
                   User::getThumbnailURLForPicture($this->fields['picture'])."'>";
            echo "</div>";
            $full_picture  = "<div class='user_picture_border'>";
            $full_picture .= "<img class='user_picture' alt=\"".__s('Picture')."\" src='".
                              User::getURLForPicture($this->fields['picture'])."'>";
            $full_picture .= "</div>";

            Html::showTooltip($full_picture, ['applyto' => "picture$rand"]);
            echo Html::file(['name' => 'picture', 'display' => false, 'onlyimages' => true]);

            echo "&nbsp;";
            Html::showCheckbox(['name' => '_blank_picture', 'title' => __('Clear')]);
            echo "&nbsp;".__('Clear');

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

         $firstnamerand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='textfield_firstname$firstnamerand'>" . __('First name') . "</label></td><td>";
         if ($extauth
             && isset($authtype['firstname_field'])
             && !empty($authtype['firstname_field'])) {

            echo $this->fields["firstname"];
         } else {
            Html::autocompletionTextField($this, "firstname", ['rand' => $firstnamerand]);
         }
         echo "</td></tr>";

         if ($extauth
            && $this->fields['auths_id']
               && AuthLDAP::isSyncFieldConfigured($this->fields['auths_id'])) {
            echo "<tr class='tab_bg_1'><td>" . __('Synchronization field') . "</td><td>";
            if (empty($this->fields['sync_field'])) {
               echo Dropdown::EMPTY_VALUE;
            } else {
               echo $this->fields['sync_field'];
            }
            echo "</td></tr>";
         } else {
            echo "<tr class='tab_bg_1'><td colspan='2'></td></tr>";
         }

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

         if (!GLPI_DEMO_MODE) {
            $langrand = mt_rand();
            echo "<td><label for='dropdown_language$langrand'>" . __('Language') . "</label></td><td>";
            // Language is stored as null in DB if value is same as the global config.
            $language = $this->fields["language"];
            if (null === $this->fields["language"]) {
               $language = $CFG_GLPI['language'];
            }
            Dropdown::showLanguages(
               "language",
               [
                  'rand'  => $langrand,
                  'value' => $language,
               ]
            );
            echo "</td>";
         } else {
            echo "<td colspan='2'>&nbsp;</td>";
         }
         echo "</tr>";

         //do some rights verification
         if (!$extauth
             && Session::haveRight("password_update", "1")) {

            echo "<tr class='tab_bg_1'>";
            echo "<td><label for='password'>" . __('Password') . "</label></td>";
            echo "<td><input id='password' type='password' name='password' value='' size='30' autocomplete='new-password' onkeyup=\"return passwordCheck();\">";
            echo "</td>";
            echo "</tr>";

            echo "<tr class='tab_bg_1'>";
            echo "<td><label for='password2'>" . __('Password confirmation') . "</label></td>";
            echo "<td><input type='password' name='password2' id='password2' value='' size='30' autocomplete='new-password'>";
            echo "</td></tr>";

            if ($CFG_GLPI["use_password_security"]) {
               echo "<tr class='tab_bg_1'>";
               echo "<td rowspan='2'>";
               echo __('Password security policy');
               echo "</td>";
               echo "<td rowspan='2'>";
               Config::displayPasswordSecurityChecks();
               echo "</td>";
               echo "</tr>";
            }
         } else {
            echo "<tr class='tab_bg_1'><td colspan='2'></td></tr>";
            echo "<tr class='tab_bg_1'><td colspan='2'></td></tr>";
            echo "<tr class='tab_bg_1'><td colspan='2'></td></tr>";
         }

         $tz_warning = '';
         $tz_available = $DB->areTimezonesAvailable($tz_warning);
         if ($tz_available || Session::haveRight("config", READ)) {
            echo "<tr class='tab_bg_1'>";
            echo "<td><label for='timezone'>".__('Time zone')."</label></td><td>";
            if ($tz_available) {
               $timezones = $DB->getTimezones();
               Dropdown::showFromArray(
                  'timezone',
                  $timezones, [
                     'value'                 => $this->fields["timezone"],
                     'display_emptychoice'   => true,
                     'emptylabel'            => __('Use server configuration')
                  ]
               );
            } else if (Session::haveRight("config", READ)) {
               // Display a warning but only if user is more or less an admin
               echo "<img src=\"{$CFG_GLPI['root_doc']}/pics/warning_min.png\">";
               echo $tz_warning;
            }
            echo "</td>";
            if ($extauth
                || !Session::haveRight("password_update", "1")) {
               echo "<td colspan='2'></td>";
            }
            echo "</tr>";
         }

         $phonerand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='textfield_phone$phonerand'>" .  Phone::getTypeName(1) . "</label></td><td>";

         if ($extauth
             && isset($authtype['phone_field']) && !empty($authtype['phone_field'])) {
            echo $this->fields["phone"];
         } else {
            Html::autocompletionTextField($this, "phone", ['rand' => $phonerand]);
         }
         echo "</td>";
         echo "<td class='top'>" . _n('Email', 'Emails', Session::getPluralNumber());
         UserEmail::showAddEmailButton($this);
         echo "</td><td>";
         UserEmail::showForUser($this);
         echo "</td>";
         echo "</tr>";

         $mobilerand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='textfield_mobile$mobilerand'>" . __('Mobile phone') . "</label></td><td>";

         if ($extauth
             && isset($authtype['mobile_field']) && !empty($authtype['mobile_field'])) {
            echo $this->fields["mobile"];
         } else {
            Html::autocompletionTextField($this, "mobile", ['rand' => $mobilerand]);
         }
         echo "</td>";

         if (count($_SESSION['glpiprofiles']) >1) {
            $profilerand = mt_rand();
            echo "<td><label for='dropdown_profiles_id$profilerand'>" . __('Default profile') . "</label></td><td>";

            $options = Dropdown::getDropdownArrayNames('glpi_profiles',
                                                       Profile_User::getUserProfiles($this->fields['id']));
            Dropdown::showFromArray("profiles_id", $options,
                                    ['value'               => $this->fields["profiles_id"],
                                     'rand'                => $profilerand,
                                     'display_emptychoice' => true]);
            echo "</td>";

         } else {
            echo "<td colspan='2'>&nbsp;</td>";
         }
         echo "</tr>";

         $phone2rand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='textfield_phone2$phone2rand'>" .  __('Phone 2') . "</label></td><td>";

         if ($extauth
             && isset($authtype['phone2_field']) && !empty($authtype['phone2_field'])) {
            echo $this->fields["phone2"];
         } else {
            Html::autocompletionTextField($this, "phone2", ['rand' => $phone2rand]);
         }
         echo "</td>";

         $entities = $this->getEntities();
         if (!GLPI_DEMO_MODE
             && (count($_SESSION['glpiactiveentities']) > 1)) {
            $entrand = mt_rand();
            echo "<td><label for='dropdown_entities_id$entrand'>" . __('Default entity') . "</td><td>";
            Entity::dropdown(['value'  => $this->fields['entities_id'],
                              'rand'   => $entrand,
                              'entity' => $entities]);
         } else {
            echo "<td colspan='2'>&nbsp;";
         }
         echo "</td></tr>";

         $admnumrand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='textfield_registration_number$admnumrand'>" . __('Administrative number') . "</label></td><td>";
         if ($extauth
             && isset($authtype['registration_number_field']) && !empty($authtype['registration_number_field'])) {
            echo $this->fields["registration_number"];
         } else {
            Html::autocompletionTextField($this, "registration_number", ['rand' => $admnumrand]);
         }
         echo "</td><td colspan='2'></td></tr>";

         $locrand = mt_rand();
         echo "<tr class='tab_bg_1'><td><label for='dropdown_locations_id$locrand'>" . Location::getTypeName(1) . "</label></td><td>";
         Location::dropdown(['value'  => $this->fields['locations_id'],
                             'rand'   => $locrand,
                             'entity' => $entities]);

         if (Config::canUpdate()) {
            $moderand = mt_rand();
            echo "<td><label for='dropdown_use_mode$moderand'>" . __('Use GLPI in mode') . "</label></td><td>";
            $modes = [
               Session::NORMAL_MODE => __('Normal'),
               Session::DEBUG_MODE  => __('Debug'),
            ];
            Dropdown::showFromArray('use_mode', $modes, ['value' => $this->fields["use_mode"], 'rand' => $moderand]);
         } else {
            echo "<td colspan='2'>&nbsp;";
         }
         echo "</td></tr>";

         echo "<tr class='tab_bg_1'><th colspan='4'>". __('Remote access keys') ."</th></tr>";

         echo "<tr class='tab_bg_1'><td>";
         echo __("Personal token");
         echo "</td><td colspan='2'>";

         if (!empty($this->fields["personal_token"])) {
            echo "<div class='copy_to_clipboard_wrapper'>";
            echo Html::input('_personal_token', [
                                 'value'    => $this->fields["personal_token"],
                                 'style'    => 'width:90%'
                             ]);
            echo "</div>";
            echo "(".sprintf(__('generated on %s'),
                                Html::convDateTime($this->fields["personal_token_date"])).")";
         }
         echo "</td><td>";
         Html::showCheckbox(['name'  => '_reset_personal_token',
                             'title' => __('Regenerate')]);
         echo "&nbsp;&nbsp;".__('Regenerate');
         echo "</td></tr>";

         echo "<tr class='tab_bg_1'><td>";
         echo __("API token");
         echo "</td><td colspan='2'>";
         if (!empty($this->fields["api_token"])) {
            echo "<div class='copy_to_clipboard_wrapper'>";
            echo Html::input('_api_token', [
                                 'value'    => $this->fields["api_token"],
                                 'style'    => 'width:90%'
                             ]);
            echo "</div>";
            echo "(".sprintf(__('generated on %s'),
                                Html::convDateTime($this->fields["api_token_date"])).")";
         }
         echo "</td><td>";
         Html::showCheckbox(['name'  => '_reset_api_token',
                             'title' => __('Regenerate')]);
         echo "&nbsp;&nbsp;".__('Regenerate');
         echo "</td></tr>";

         echo "<tr><td class='tab_bg_2 center' colspan='4'>";
         echo "<input type='submit' name='update' value=\""._sx('button', 'Save')."\" class='submit'>";
         echo "</td></tr>";

         echo "</table>";
         Html::closeForm();
         echo "</div>";
         $CFG_GLPI["use_ajax_autocompletion"] = $save_autocompletion;
         return true;
      }
      return false;
   }


   /**
    * Get all the authentication method parameters for the current user.
    *
    * @return array
    */
   function getAuthMethodsByID() {
      return Auth::getMethodsByID($this->fields["authtype"], $this->fields["auths_id"]);
   }


   function pre_updateInDB() {
      global $DB;

      if (($key = array_search('name', $this->updates)) !== false) {
         /// Check if user does not exists
         $iterator = $DB->request([
            'FROM'   => $this->getTable(),
            'WHERE'  => [
               'name'   => $this->input['name'],
               'id'     => ['<>', $this->input['id']]
            ]
         ]);

         if (count($iterator)) {
            //To display a message
            $this->fields['name'] = $this->oldvalues['name'];
            unset($this->updates[$key]);
            unset($this->oldvalues['name']);
            Session::addMessageAfterRedirect(__('Unable to update login. A user already exists.'),
                                             false, ERROR);
         }

         if (!Auth::isValidLogin(stripslashes($this->input['name']))) {
            $this->fields['name'] = $this->oldvalues['name'];
            unset($this->updates[$key]);
            unset($this->oldvalues['name']);
            Session::addMessageAfterRedirect(__('The login is not valid. Unable to update login.'),
                                             false, ERROR);
         }

      }

      // ## Security system except for login update:
      //
      // An **external** (ldap, mail) user without User::UPDATE right
      // should not be able to update its own fields
      // (for example, fields concerned by ldap synchronisation)
      // except on login action (which triggers synchronisation).
      if (Session::getLoginUserID() === (int)$this->input['id']
          && !Session::haveRight("user", UPDATE)
          && !strpos($_SERVER['PHP_SELF'], "/front/login.php")
          && isset($this->fields["authtype"])) {

         // extauth ldap case
         if ($_SESSION["glpiextauth"]
             && ($this->fields["authtype"] == Auth::LDAP
                 || Auth::isAlternateAuth($this->fields["authtype"]))) {

            $authtype = Auth::getMethodsByID($this->fields["authtype"],
                                             $this->fields["auths_id"]);
            if (count($authtype)) {
               $fields = AuthLDAP::getSyncFields($authtype);
               foreach ($fields as $key => $val) {
                  if (!empty($val)
                        && (($key2 = array_search($key, $this->updates)) !== false)) {

                     unset ($this->updates[$key2]);
                     unset($this->oldvalues[$key]);

                  }
               }
            }
         }

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

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

   function getSpecificMassiveActions($checkitem = null) {

      $isadmin = static::canUpdate();
      $actions = parent::getSpecificMassiveActions($checkitem);
      if ($isadmin) {
         $actions['Group_User'.MassiveAction::CLASS_ACTION_SEPARATOR.'add']
                                                         = "<i class='ma-icon fas fa-users'></i>".
                                                           __('Associate to a group');
         $actions['Group_User'.MassiveAction::CLASS_ACTION_SEPARATOR.'remove']
                                                         = __('Dissociate from a group');
         $actions['Profile_User'.MassiveAction::CLASS_ACTION_SEPARATOR.'add']
                                                         = "<i class='ma-icon fas fa-user-shield'></i>".
                                                           __('Associate to a profile');
         $actions['Profile_User'.MassiveAction::CLASS_ACTION_SEPARATOR.'remove']
                                                         = __('Dissociate from a profile');
         $actions['Group_User'.MassiveAction::CLASS_ACTION_SEPARATOR.'change_group_user']
                                                         = "<i class='ma-icon fas fa-users-cog'></i>".
                                                           __("Move to group");
      }

      if (Session::haveRight(self::$rightname, self::UPDATEAUTHENT)) {
         $prefix                                    = __CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR;
         $actions[$prefix.'change_authtype']        = "<i class='ma-icon fas fa-user-cog'></i>".
                                                      _x('button', 'Change the authentication method');
         $actions[$prefix.'force_user_ldap_update'] = "<i class='ma-icon fas fa-sync'></i>".
                                                      __('Force synchronization');
      }
      return $actions;
   }

   static function showMassiveActionsSubForm(MassiveAction $ma) {
      global $CFG_GLPI;

      switch ($ma->getAction()) {
         case 'change_authtype' :
            $rand             = Auth::dropdown(['name' => 'authtype']);
            $paramsmassaction = ['authtype' => '__VALUE__'];
            Ajax::updateItemOnSelectEvent("dropdown_authtype$rand", "show_massiveaction_field",
                                          $CFG_GLPI["root_doc"].
                                             "/ajax/dropdownMassiveActionAuthMethods.php",
                                          $paramsmassaction);
            echo "<span id='show_massiveaction_field'><br><br>";
            echo Html::submit(_x('button', 'Post'), ['name' => 'massiveaction'])."</span>";
            return true;
      }
      return parent::showMassiveActionsSubForm($ma);
   }

   static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item,
                                                       array $ids) {

      switch ($ma->getAction()) {
         case 'force_user_ldap_update' :
            foreach ($ids as $id) {
               if ($item->can($id, UPDATE)) {
                  if (($item->fields["authtype"] == Auth::LDAP)
                      || ($item->fields["authtype"] == Auth::EXTERNAL)) {
                     if (AuthLDAP::forceOneUserSynchronization($item, false)) {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                     } else {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                        $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                     }
                  } else {
                     $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                     $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                  }
               } else {
                  $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT);
                  $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
               }
            }
            return;

         case 'change_authtype' :
            $input = $ma->getInput();
            if (!isset($input["authtype"])
                || !isset($input["auths_id"])) {
               $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
               $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
               return;
            }
            if (Session::haveRight(self::$rightname, self::UPDATEAUTHENT)) {
               if (User::changeAuthMethod($ids, $input["authtype"], $input["auths_id"])) {
                  $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_OK);
               } else {
                  $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
               }
            } else {
               $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_NORIGHT);
               $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
            }
            return;
      }
      parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
   }


   function rawSearchOptions() {
      // forcegroup by on name set force group by for all items
      $tab = [];

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

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

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

      $tab[] = [
         'id'                 => '34',
         'table'              => $this->getTable(),
         'field'              => 'realname',
         'name'               => __('Last name'),
         'datatype'           => 'string',
         'autocomplete'       => true,
      ];

      $tab[] = [
         'id'                 => '9',
         'table'              => $this->getTable(),
         'field'              => 'firstname',
         'name'               => __('First name'),
         'datatype'           => 'string',
         'autocomplete'       => true,
      ];

      $tab[] = [
         'id'                 => '5',
         'table'              => 'glpi_useremails',
         'field'              => 'email',
         'name'               => _n('Email', 'Emails', Session::getPluralNumber()),
         'datatype'           => 'email',
         'joinparams'         => [
            'jointype'           => 'child'
         ],
         'forcegroupby'       => true,
         'massiveaction'      => false
      ];

      $tab[] = [
         'id'                 => '150',
         'table'              => $this->getTable(),
         'field'              => 'picture',
         'name'               => __('Picture'),
         'datatype'           => 'specific',
         'nosearch'           => true,
         'massiveaction'      => false
      ];

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

      $tab = array_merge($tab, Location::rawSearchOptionsToAdd());

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

      $tab[] = [
         'id'                 => '6',
         'table'              => $this->getTable(),
         'field'              => 'phone',
         'name'               => Phone::getTypeName(1),
         'datatype'           => 'string',
         'autocomplete'       => true,
      ];

      $tab[] = [
         'id'                 => '10',
         'table'              => $this->getTable(),
         'field'              => 'phone2',
         'name'               => __('Phone 2'),
         'datatype'           => 'string',
         'autocomplete'       => true,
      ];

      $tab[] = [
         'id'                 => '11',
         'table'              => $this->getTable(),
         'field'              => 'mobile',
         'name'               => __('Mobile phone'),
         'datatype'           => 'string',
         'autocomplete'       => true,
      ];

      $tab[] = [
         'id'                 => '13',
         'table'              => 'glpi_groups',
         'field'              => 'completename',
         'name'               => Group::getTypeName(Session::getPluralNumber()),
         'forcegroupby'       => true,
         'datatype'           => 'itemlink',
         'massiveaction'      => false,
         'joinparams'         => [
            'beforejoin'         => [
               'table'              => 'glpi_groups_users',
               'joinparams'         => [
                  'jointype'           => 'child'
               ]
            ]
         ]
      ];

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

      $tab[] = [
         'id'                 => '15',
         'table'              => $this->getTable(),
         'field'              => 'authtype',
         'name'               => __('Authentication'),
         'massiveaction'      => false,
         'datatype'           => 'specific',
         'searchtype'         => 'equals',
         'additionalfields'   => [
            '0'                  => 'auths_id'
         ]
      ];

      $tab[] = [
         'id'                 => '30',
         'table'              => 'glpi_authldaps',
         'field'              => 'name',
         'linkfield'          => 'auths_id',
         'name'               => __('LDAP directory for authentication'),
         'massiveaction'      => false,
         'joinparams'         => [
             'condition'          => 'AND REFTABLE.`authtype` = ' . Auth::LDAP
         ],
         'datatype'           => 'dropdown'
      ];

      $tab[] = [
         'id'                 => '31',
         'table'              => 'glpi_authmails',
         'field'              => 'name',
         'linkfield'          => 'auths_id',
         'name'               => __('Email server for authentication'),
         'massiveaction'      => false,
         'joinparams'         => [
            'condition'          => 'AND REFTABLE.`authtype` = ' . Auth::MAIL
         ],
         'datatype'           => 'dropdown'
      ];

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

      $tab[] = [
         'id'                 => '17',
         'table'              => $this->getTable(),
         'field'              => 'language',
         'name'               => __('Language'),
         'datatype'           => 'language',
         'display_emptychoice' => true,
         'emptylabel'         => 'Default value'
      ];

      $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'              => 'glpi_profiles',
         'field'              => 'name',
         'name'               => sprintf(__('%1$s (%2$s)'), Profile::getTypeName(Session::getPluralNumber()),
                                                 Entity::getTypeName(1)),
         'forcegroupby'       => true,
         'massiveaction'      => false,
         'datatype'           => 'dropdown',
         'joinparams'         => [
            'beforejoin'         => [
               'table'              => 'glpi_profiles_users',
               'joinparams'         => [
                  'jointype'           => 'child'
               ]
            ]
         ]
      ];

      $tab[] = [
         'id'                 => '21',
         'table'              => $this->getTable(),
         'field'              => 'user_dn',
         'name'               => __('User DN'),
         'massiveaction'      => false,
         'datatype'           => 'text'
      ];

      $tab[] = [
         'id'                 => '22',
         'table'              => $this->getTable(),
         'field'              => 'registration_number',
         'name'               => __('Administrative number'),
         'datatype'           => 'string',
         'autocomplete'       => true,
      ];

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

      $tab[] = [
         'id'                 => '24',
         'table'              => $this->getTable(),
         'field'              => 'is_deleted_ldap',
         'name'               => __('Deleted user in LDAP directory'),
         'datatype'           => 'bool',
         'massiveaction'      => false
      ];

      $tab[] = [
         'id'                 => '80',
         'table'              => 'glpi_entities',
         'linkfield'          => 'entities_id',
         'field'              => 'completename',
         'name'               => sprintf(__('%1$s (%2$s)'), Entity::getTypeName(Session::getPluralNumber()),
                                                 Profile::getTypeName(1)),
         'forcegroupby'       => true,
         'datatype'           => 'dropdown',
         'massiveaction'      => false,
         'joinparams'         => [
            'beforejoin'         => [
               'table'              => 'glpi_profiles_users',
               'joinparams'         => [
                  'jointype'           => 'child'
               ]
            ]
         ]
      ];

      $tab[] = [
         'id'                 => '81',
         'table'              => 'glpi_usertitles',
         'field'              => 'name',
         'name'               => __('Title'),
         'datatype'           => 'dropdown'
      ];

      $tab[] = [
         'id'                 => '82',
         'table'              => 'glpi_usercategories',
         'field'              => 'name',
         'name'               => __('Category'),
         'datatype'           => 'dropdown'
      ];

      $tab[] = [
         'id'                 => '79',
         'table'              => 'glpi_profiles',
         'field'              => 'name',
         'name'               => __('Default profile'),
         'datatype'           => 'dropdown'
      ];

      $tab[] = [
         'id'                 => '77',
         'table'              => 'glpi_entities',
         'field'              => 'name',
         'massiveaction'      => true,
         'name'               => __('Default entity'),
         'datatype'           => 'dropdown'
      ];

      $tab[] = [
         'id'                 => '62',
         'table'              => $this->getTable(),
         'field'              => 'begin_date',
         'name'               => __('Begin date'),
         'datatype'           => 'datetime'
      ];

      $tab[] = [
         'id'                 => '63',
         'table'              => $this->getTable(),
         'field'              => 'end_date',
         'name'               => __('End date'),
         'datatype'           => 'datetime'
      ];

      $tab[] = [
         'id'                 => '60',
         'table'              => 'glpi_tickets',
         'field'              => 'id',
         'name'               => __('Number of tickets as requester'),
         'forcegroupby'       => true,
         'usehaving'          => true,
         'datatype'           => 'count',
         'massiveaction'      => false,
         'joinparams'         => [
            'beforejoin'         => [
               'table'              => 'glpi_tickets_users',
               'joinparams'         => [
                  'jointype'           => 'child',
                  'condition'          => 'AND NEWTABLE.`type` = ' . CommonITILActor::REQUESTER
               ]
            ]
         ]
      ];

      $tab[] = [
         'id'                 => '61',
         'table'              => 'glpi_tickets',
         'field'              => 'id',
         'name'               => __('Number of written tickets'),
         'forcegroupby'       => true,
         'usehaving'          => true,
         'datatype'           => 'count',
         'massiveaction'      => false,
         'joinparams'         => [
            'jointype'           => 'child',
            'linkfield'          => 'users_id_recipient'
         ]
      ];

      $tab[] = [
         'id'                 => '64',
         'table'              => 'glpi_tickets',
         'field'              => 'id',
         'name'               => __('Number of assigned tickets'),
         'forcegroupby'       => true,
         'usehaving'          => true,
         'datatype'           => 'count',
         'massiveaction'      => false,
         'joinparams'         => [
            'beforejoin'         => [
               'table'              => 'glpi_tickets_users',
               'joinparams'         => [
                  'jointype'           => 'child',
                  'condition'          => 'AND NEWTABLE.`type` = '.CommonITILActor::ASSIGN
               ]
            ]
         ]
      ];

      $tab[] = [
         'id'                 => '99',
         'table'              => 'glpi_users',
         'field'              => 'name',
         'linkfield'          => 'users_id_supervisor',
         'name'               => __('Responsible'),
         'datatype'           => 'dropdown',
         'massiveaction'      => false,
      ];

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

      return $tab;
   }

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

      if (!is_array($values)) {
         $values = [$field => $values];
      }
      switch ($field) {
         case 'authtype':
            $auths_id = 0;
            if (isset($values['auths_id']) && !empty($values['auths_id'])) {
               $auths_id = $values['auths_id'];
            }
            return Auth::getMethodName($values[$field], $auths_id);
         case 'picture':
            if (isset($options['html']) && $options['html']) {
               return Html::image(self::getThumbnailURLForPicture($values['picture']),
                                  ['class' => 'user_picture_small', 'alt' => __('Picture')]);
            }
      }
      return parent::getSpecificValueToDisplay($field, $values, $options);
   }

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

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


   /**
    * Get all groups where the current user have delegating.
    *
    * @since 0.83
    *
    * @param integer|string $entities_id ID of the entity to restrict
    *
    * @return integer[]
    */
   static function getDelegateGroupsForUser($entities_id = '') {
      global $DB;

      $iterator = $DB->request([
         'SELECT'          => 'glpi_groups_users.groups_id',
         'DISTINCT'        => true,
         'FROM'            => 'glpi_groups_users',
         'INNER JOIN'      => [
            'glpi_groups'  => [
               'FKEY'   => [
                  'glpi_groups_users'  => 'groups_id',
                  'glpi_groups'        => 'id'
               ]
            ]
         ],
         'WHERE'           => [
            'glpi_groups_users.users_id'        => Session::getLoginUserID(),
            'glpi_groups_users.is_userdelegate' => 1
         ] + getEntitiesRestrictCriteria('glpi_groups', '', $entities_id, 1)
      ]);

      $groups = [];
      while ($data = $iterator->next()) {
         $groups[$data['groups_id']] = $data['groups_id'];
      }
      return $groups;
   }


   /**
    * Execute the query to select box with all glpi users where select key = name
    *
    * Internaly used by showGroup_Users, dropdownUsers and ajax/getDropdownUsers.php
    *
    * @param boolean         $count            true if execute an count(*) (true by default)
    * @param string|string[] $right            limit user who have specific right (default 'all')
    * @param integer         $entity_restrict  Restrict to a defined entity (default -1)
    * @param integer         $value            default value (default 0)
    * @param integer[]       $used             Already used items ID: not to display in dropdown
    * @param string          $search           pattern (default '')
    * @param integer         $start            start LIMIT value (default 0)
    * @param integer         $limit            limit LIMIT value (default -1 no limit)
    * @param boolean         $inactive_deleted true to retreive also inactive or deleted users
    *
    * @return mysqli_result|boolean
    */
   static function getSqlSearchResult ($count = true, $right = "all", $entity_restrict = -1, $value = 0,
                                       array $used = [], $search = '', $start = 0, $limit = -1,
                                       $inactive_deleted = 0, $with_no_right = 0) {
      global $DB;

      // No entity define : use active ones
      if ($entity_restrict < 0) {
         $entity_restrict = $_SESSION["glpiactiveentities"];
      }

      $joinprofile      = false;
      $joinprofileright = false;
      $WHERE = [];

      switch ($right) {
         case "interface" :
            $joinprofile = true;
            $WHERE = [
               'glpi_profiles.interface' => 'central'
            ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1);
            break;

         case "id" :
            $WHERE = ['glpi_users.id' => Session::getLoginUserID()];
            break;

         case "delegate" :
            $groups = self::getDelegateGroupsForUser($entity_restrict);
            $users  = [];
            if (count($groups)) {
               $iterator = $DB->request([
                  'SELECT'    => 'glpi_users.id',
                  'FROM'      => 'glpi_groups_users',
                  'LEFT JOIN' => [
                     'glpi_users'   => [
                        'FKEY'   => [
                           'glpi_groups_users'  => 'users_id',
                           'glpi_users'         => 'id'
                        ]
                     ]
                  ],
                  'WHERE'     => [
                     'glpi_groups_users.groups_id' => $groups,
                     'glpi_groups_users.users_id'  => ['<>', Session::getLoginUserID()]
                  ]
               ]);
               while ($data = $iterator->next()) {
                     $users[$data["id"]] = $data["id"];
               }
            }
            // Add me to users list for central
            if (Session::getCurrentInterface() == 'central') {
               $users[Session::getLoginUserID()] = Session::getLoginUserID();
            }

            if (count($users)) {
               $WHERE = ['glpi_users.id' => $users];
            }
            break;

         case "groups" :
            $groups = [];
            if (isset($_SESSION['glpigroups'])) {
               $groups = $_SESSION['glpigroups'];
            }
            $users  = [];
            if (count($groups)) {
               $iterator = $DB->request([
                  'SELECT'    => 'glpi_users.id',
                  'FROM'      => 'glpi_groups_users',
                  'LEFT JOIN' => [
                     'glpi_users'   => [
                        'FKEY'   => [
                           'glpi_groups_users'  => 'users_id',
                           'glpi_users'         => 'id'
                        ]
                     ]
                  ],
                  'WHERE'     => [
                     'glpi_groups_users.groups_id' => $groups,
                     'glpi_groups_users.users_id'  => ['<>', Session::getLoginUserID()]
                  ]
               ]);
               while ($data = $iterator->next()) {
                  $users[$data["id"]] = $data["id"];
               }
            }
            // Add me to users list for central
            if (Session::getCurrentInterface() == 'central') {
               $users[Session::getLoginUserID()] = Session::getLoginUserID();
            }

            if (count($users)) {
               $WHERE = ['glpi_users.id' => $users];
            }

            break;

         case "all" :
            $WHERE = [
               'glpi_users.id' => ['>', 0],
               'OR' => getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
            ];

            if ($with_no_right) {
               $WHERE['OR'][] = ['glpi_profiles_users.entities_id' => null];
            }
            break;

         default :
            $joinprofile = true;
            $joinprofileright = true;
            if (!is_array($right)) {
               $right = [$right];
            }
            $forcecentral = true;

            $ORWHERE = [];
            foreach ($right as $r) {
               switch ($r) {
                  case  'own_ticket' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => 'ticket',
                           'glpi_profilerights.rights'   => ['&', Ticket::OWN]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
                     break;

                  case 'create_ticket_validate' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'  => 'ticketvalidation',
                           'OR'                       => [
                              ['glpi_profilerights.rights'   => ['&', TicketValidation::CREATEREQUEST]],
                              ['glpi_profilerights.rights'   => ['&', TicketValidation::CREATEINCIDENT]]
                           ]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
                     $forcecentral = false;
                     break;

                  case 'validate_request' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => 'ticketvalidation',
                           'glpi_profilerights.rights'   => ['&', TicketValidation::VALIDATEREQUEST]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
                     $forcecentral = false;
                     break;

                  case 'validate_incident' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => 'ticketvalidation',
                           'glpi_profilerights.rights'   => ['&', TicketValidation::VALIDATEINCIDENT]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
                     $forcecentral = false;
                     break;

                  case 'validate' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => 'changevalidation',
                           'glpi_profilerights.rights'   => ['&', ChangeValidation::VALIDATE]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
                     break;

                  case 'create_validate' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => 'changevalidation',
                           'glpi_profilerights.rights'   => ['&', ChangeValidation::CREATE]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
                     break;

                  case 'see_project' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => 'project',
                           'glpi_profilerights.rights'   => ['&', Project::READMY]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
                     break;

                  case 'faq' :
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => 'knowbase',
                           'glpi_profilerights.rights'   => ['&', KnowbaseItem::READFAQ]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];

                  default :
                     // Check read or active for rights
                     $ORWHERE[] = [
                        [
                           'glpi_profilerights.name'     => $r,
                           'glpi_profilerights.rights'   => [
                              '&',
                              READ | CREATE | UPDATE | DELETE | PURGE
                           ]
                        ] + getEntitiesRestrictCriteria('glpi_profiles_users', '', $entity_restrict, 1)
                     ];
               }
               if (in_array($r, Profile::$helpdesk_rights)) {
                  $forcecentral = false;
               }
            }

            if (count($ORWHERE)) {
               $WHERE[] = ['OR' => $ORWHERE];
            }

            if ($forcecentral) {
               $WHERE['glpi_profiles.interface'] = 'central';
            }
      }

      if (!$inactive_deleted) {
         $WHERE = array_merge(
            $WHERE, [
               'glpi_users.is_deleted' => 0,
               'glpi_users.is_active'  => 1,
               [
                  'OR' => [
                     ['glpi_users.begin_date' => null],
                     ['glpi_users.begin_date' => ['<', new QueryExpression('NOW()')]]
                  ]
               ],
               [
                  'OR' => [
                     ['glpi_users.end_date' => null],
                     ['glpi_users.end_date' => ['>', new QueryExpression('NOW()')]]
                  ]
               ]

            ]
         );
      }

      if ((is_numeric($value) && $value)
          || count($used)) {

         $WHERE[] = [
            'NOT' => [
               'glpi_users.id' => $used
            ]
         ];
      }

      $criteria = [
         'FROM'            => 'glpi_users',
         'LEFT JOIN'       => [
            'glpi_useremails'       => [
               'ON' => [
                  'glpi_useremails' => 'users_id',
                  'glpi_users'      => 'id'
               ]
            ],
            'glpi_profiles_users'   => [
               'ON' => [
                  'glpi_profiles_users'   => 'users_id',
                  'glpi_users'            => 'id'
               ]
            ]
         ]
      ];
      if ($count) {
         $criteria['SELECT'] = ['COUNT' => 'glpi_users.id AS CPT'];
         $criteria['DISTINCT'] = true;
      } else {
         $criteria['SELECT'] = 'glpi_users.*';
         $criteria['DISTINCT'] = true;
      }

      if ($joinprofile) {
         $criteria['LEFT JOIN']['glpi_profiles'] = [
            'ON' => [
               'glpi_profiles_users'   => 'profiles_id',
               'glpi_profiles'         => 'id'
            ]
         ];
         if ($joinprofileright) {
            $criteria['LEFT JOIN']['glpi_profilerights'] = [
               'ON' => [
                  'glpi_profilerights' => 'profiles_id',
                  'glpi_profiles'      => 'id'
               ]
            ];
         }
      }

      if (!$count) {
         if ((strlen($search) > 0)) {
            $txt_search = Search::makeTextSearchValue($search);

            $firstname_field = $DB->quoteName(self::getTableField('firstname'));
            $realname_field = $DB->quoteName(self::getTableField('realname'));
            $fields = $_SESSION["glpinames_format"] == self::FIRSTNAME_BEFORE
               ? [$firstname_field, $realname_field]
               : [$realname_field, $firstname_field];

            $concat = new \QueryExpression(
               'CONCAT(' . implode(',' . $DB->quoteValue(' ') . ',', $fields) . ')'
               . ' LIKE ' . $DB->quoteValue($txt_search)
            );
            $WHERE[] = [
               'OR' => [
                  'glpi_users.name'       => ['LIKE', $txt_search],
                  'glpi_users.realname'   => ['LIKE', $txt_search],
                  'glpi_users.firstname'  => ['LIKE', $txt_search],
                  'glpi_users.phone'      => ['LIKE', $txt_search],
                  'glpi_useremails.email' => ['LIKE', $txt_search],
                  $concat
               ]
            ];
         }

         if ($_SESSION["glpinames_format"] == self::FIRSTNAME_BEFORE) {
            $criteria['ORDERBY'] = [
               'glpi_users.firstname',
               'glpi_users.realname',
               'glpi_users.name'
            ];
         } else {
            $criteria['ORDERBY'] = [
               'glpi_users.realname',
               'glpi_users.firstname',
               'glpi_users.name'
            ];
         }

         if ($limit > 0) {
            $criteria['LIMIT'] = $limit;
            $criteria['START'] = $start;
         }
      }
      $criteria['WHERE'] = $WHERE;
      return $DB->request($criteria);
   }


   /**
    * Make a select box with all glpi users where select key = name
    *
    * @param $options array of possible options:
    *    - name             : string / name of the select (default is users_id)
    *    - value
    *    - values           : in case of select[multiple], pass the array of multiple values
    *    - right            : string / limit user who have specific right :
    *                             id -> only current user (default case);
    *                             interface -> central;
    *                             all -> all users;
    *                             specific right like Ticket::READALL, CREATE.... (is array passed one of all passed right is needed)
    *    - comments         : boolean / is the comments displayed near the dropdown (default true)
    *    - entity           : integer or array / restrict to a defined entity or array of entities
    *                          (default -1 : no restriction)
    *    - entity_sons      : boolean / if entity restrict specified auto select its sons
    *                          only available if entity is a single value not an array(default false)
    *    - all              : Nobody or All display for none selected
    *                             all=0 (default) -> Nobody
    *                             all=1 -> All
    *                             all=-1-> nothing
    *    - rand             : integer / already computed rand value
    *    - toupdate         : array / Update a specific item on select change on dropdown
    *                          (need value_fieldname, to_update, url
    *                          (see Ajax::updateItemOnSelectEvent for information)
    *                          and may have moreparams)
    *    - used             : array / Already used items ID: not to display in dropdown (default empty)
    *    - ldap_import
    *    - on_change        : string / value to transmit to "onChange"
    *    - display          : boolean / display or get string (default true)
    *    - width            : specific width needed (default 80%)
    *    - specific_tags    : array of HTML5 tags to add to the field
    *    - url              : url of the ajax php code which should return the json data to show in
    *                         the dropdown (default /ajax/getDropdownUsers.php)
    *    - inactive_deleted : retreive also inactive or deleted users
    *
    * @return integer|string Random value if displayed, string otherwise
    */
   static function dropdown($options = []) {
      global $CFG_GLPI;

      // Default values
      $p = [
         'name'                => 'users_id',
         'value'               => '',
         'values'              => [],
         'right'               => 'id',
         'all'                 => 0,
         'display_emptychoice' => true,
         'placeholder'         => '',
         'on_change'           => '',
         'comments'            => 1,
         'width'               => '80%',
         'entity'              => -1,
         'entity_sons'         => false,
         'used'                => [],
         'ldap_import'         => false,
         'toupdate'            => '',
         'rand'                => mt_rand(),
         'display'             => true,
         '_user_index'         => 0,
         'specific_tags'       => [],
         'url'                 => $CFG_GLPI['root_doc'] . "/ajax/getDropdownUsers.php",
         'inactive_deleted'    => 0,
         'with_no_right'       => 0,
      ];

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

      // check default value (in case of multiple observers)
      if (is_array($p['value'])) {
         $p['value'] = $p['value'][$p['_user_index']] ?? 0;
      }

      // Check default value for dropdown : need to be a numeric
      if ((strlen($p['value']) == 0) || !is_numeric($p['value'])) {
         $p['value'] = 0;
      }

      $output = '';
      if (!($p['entity'] < 0) && $p['entity_sons']) {
         if (is_array($p['entity'])) {
            $output .= "entity_sons options is not available with array of entity";
         } else {
            $p['entity'] = getSonsOf('glpi_entities', $p['entity']);
         }
      }

      // Make a select box with all glpi users
      $user = getUserName($p['value'], 2);

      $view_users = self::canView();

      if (!empty($p['value']) && ($p['value'] > 0)) {
          $default = $user["name"];
      } else {
         if ($p['all']) {
            $default = __('All');
         } else {
            $default = Dropdown::EMPTY_VALUE;
         }
      }

      // get multiple values name
      $valuesnames = [];
      foreach ($p['values'] as $value) {
         if (!empty($value) && ($value > 0)) {
            $user = getUserName($value, 2);
            $valuesnames[] = $user["name"];
         }
      }

      $field_id = Html::cleanId("dropdown_" . $p['name'] . $p['rand']);
      $param    = [
         'value'               => $p['value'],
         'values'              => $p['values'],
         'valuename'           => $default,
         'valuesnames'         => $valuesnames,
         'width'               => $p['width'],
         'all'                 => $p['all'],
         'display_emptychoice' => $p['display_emptychoice'],
         'placeholder'         => $p['placeholder'],
         'right'               => $p['right'],
         'on_change'           => $p['on_change'],
         'used'                => $p['used'],
         'inactive_deleted'    => $p['inactive_deleted'],
         'with_no_right'       => $p['with_no_right'],
         'entity_restrict'     => ($entity_restrict = (is_array($p['entity']) ? json_encode(array_values($p['entity'])) : $p['entity'])),
         'specific_tags'       => $p['specific_tags'],
         '_idor_token'         => Session::getNewIDORToken(__CLASS__, [
            'right'           => $p['right'],
            'entity_restrict' => $entity_restrict,
         ]),
      ];

      $output   = Html::jsAjaxDropdown($p['name'], $field_id,
                                       $p['url'],
                                       $param);

      // Display comment
      if ($p['comments']) {
         $comment_id = Html::cleanId("comment_".$p['name'].$p['rand']);
         $link_id = Html::cleanId("comment_link_".$p["name"].$p['rand']);
         if (!$view_users) {
            $user["link"] = '';
         } else if (empty($user["link"])) {
            $user["link"] = $CFG_GLPI['root_doc']."/front/user.php";
         }

         if (empty($user['comment'])) {
            $user['comment'] = Toolbox::ucfirst(
               sprintf(
                  __('Show %1$s'),
                  self::getTypeName(Session::getPluralNumber())
               )
            );
         }
         $output .= "&nbsp;".Html::showToolTip($user["comment"],
                                      ['contentid' => $comment_id,
                                            'display'   => false,
                                            'link'      => $user["link"],
                                            'linkid'    => $link_id]);

         $paramscomment = [
            'value'    => '__VALUE__',
            'itemtype' => User::getType()
         ];

         if ($view_users) {
            $paramscomment['withlink'] = $link_id;
         }
         $output .= Ajax::updateItemOnSelectEvent($field_id, $comment_id,
                                                  $CFG_GLPI["root_doc"]."/ajax/comments.php",
                                                  $paramscomment, false);
      }
      $output .= Ajax::commonDropdownUpdateItem($p, false);

      if (Session::haveRight('user', self::IMPORTEXTAUTHUSERS)
          && $p['ldap_import']
          && Entity::isEntityDirectoryConfigured($_SESSION['glpiactive_entity'])) {

         $output .= "<span title=\"".__s('Import a user')."\" class='fa fa-plus pointer'".
                     " onClick=\"".Html::jsGetElementbyID('userimport'.$p['rand']).".dialog('open');\">
                     <span class='sr-only'>" . __s('Import a user') . "</span></span>";
         $output .= Ajax::createIframeModalWindow('userimport'.$p['rand'],
                                                  $CFG_GLPI["root_doc"].
                                                      "/front/ldap.import.php?entity=".
                                                      $_SESSION['glpiactive_entity'],
                                                  ['title'   => __('Import a user'),
                                                        'display' => false]);
      }

      if ($p['display']) {
         echo $output;
         return $p['rand'];
      }
      return $output;
   }


   /**
    * Show simple add user form for external auth.
    *
    * @return void|boolean false if user does not have rights to import users from external sources,
    *    print form otherwise
    */
   static function showAddExtAuthForm() {

      if (!Session::haveRight("user", self::IMPORTEXTAUTHUSERS)) {
         return false;
      }

      echo "<div class='center'>\n";
      echo "<form method='post' action='".Toolbox::getItemTypeFormURL('User')."'>\n";

      echo "<table class='tab_cadre'>\n";
      echo "<tr><th colspan='4'>".__('Automatically add a user of an external source')."</th></tr>\n";

      echo "<tr class='tab_bg_1'><td>".__('Login')."</td>\n";
      echo "<td><input type='text' name='login'></td></tr>";

      echo "<tr class='tab_bg_1'>";
      echo "<td class='tab_bg_2 center' colspan='2'>\n";
      echo "<input type='submit' name='add_ext_auth_ldap' value=\"".__s('Import from directories')."\"
             class='submit'>\n";
      echo "</td></tr>";

      echo "<tr class='tab_bg_1'>";
      echo "<td class='tab_bg_2 center' colspan='2'>\n";
      echo "<input type='submit' name='add_ext_auth_simple' value=\"".__s('Import from other sources')."\"
             class='submit'>\n";
      echo "</td></tr>\n";

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


   /**
    * Change auth method for given users.
    *
    * @param integer[] $IDs      IDs of users
    * @param integer   $authtype Auth type (see Auth constants)
    * @param integer   $server   ID of auth server
    *
    * @return boolean
    */
   static function changeAuthMethod(array $IDs = [], $authtype = 1, $server = -1) {
      global $DB;

      if (!Session::haveRight(self::$rightname, self::UPDATEAUTHENT)) {
         return false;
      }

      if (!empty($IDs)
          && in_array($authtype, [Auth::DB_GLPI, Auth::LDAP, Auth::MAIL, Auth::EXTERNAL])) {

         $result = $DB->update(
            self::getTable(), [
               'authtype'        => $authtype,
               'auths_id'        => $server,
               'password'        => '',
               'is_deleted_ldap' => 0
            ], [
               'id' => $IDs
            ]
         );
         if ($result) {
            foreach ($IDs as $ID) {
               $changes = [
                  0,
                  '',
                  addslashes(
                     sprintf(
                        __('%1$s: %2$s'),
                        __('Update authentification method to'),
                        Auth::getMethodName($authtype, $server)
                     )
                  )
               ];
               Log::history($ID, __CLASS__, $changes, '', Log::HISTORY_LOG_SIMPLE_MESSAGE);
            }

            return true;
         }
      }
      return false;
   }


   /**
    * Generate vcard for the current user.
    *
    * @return void
    */
   function generateVcard() {

      // prepare properties for the Vcard
      if (!empty($this->fields["realname"])
          || !empty($this->fields["firstname"])) {
         $name = [$this->fields["realname"], $this->fields["firstname"], "", "", ""];
      } else {
         $name = [$this->fields["name"], "", "", "", ""];
      }

      // create vcard
      $vcard = new VObject\Component\VCard([
         'N'     => $name,
         'EMAIL' => $this->getDefaultEmail(),
         'NOTE'  => $this->fields["comment"],
      ]);
      $vcard->add('TEL', $this->fields["phone"], ['type' => 'PREF;WORK;VOICE']);
      $vcard->add('TEL', $this->fields["phone2"], ['type' => 'HOME;VOICE']);
      $vcard->add('TEL', $this->fields["mobile"], ['type' => 'WORK;CELL']);

      // send the  VCard
      $output   = $vcard->serialize();
      $filename = implode("_", array_filter($name)).".vcf";

      @header("Content-Disposition: attachment; filename=\"$filename\"");
      @header("Content-Length: ".Toolbox::strlen($output));
      @header("Connection: close");
      @header("content-type: text/x-vcard; charset=UTF-8");

      echo $output;
   }


   /**
    * Show items of the current user.
    *
    * @param boolean $tech false to display items owned by user, true to display items managed by user
    *
    * @return void
    */
   function showItems($tech) {
      global $DB, $CFG_GLPI;

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

      if ($tech) {
         $type_user   = $CFG_GLPI['linkuser_tech_types'];
         $type_group  = $CFG_GLPI['linkgroup_tech_types'];
         $field_user  = 'users_id_tech';
         $field_group = 'groups_id_tech';
      } else {
         $type_user   = $CFG_GLPI['linkuser_types'];
         $type_group  = $CFG_GLPI['linkgroup_types'];
         $field_user  = 'users_id';
         $field_group = 'groups_id';
      }

      $group_where = "";
      $groups      = [];

      $iterator = $DB->request([
         'SELECT'    => [
            'glpi_groups_users.groups_id',
            'glpi_groups.name'
         ],
         'FROM'      => 'glpi_groups_users',
         'LEFT JOIN' => [
            'glpi_groups' => [
               'FKEY' => [
                  'glpi_groups_users'  => 'groups_id',
                  'glpi_groups'        => 'id'
               ]
            ]
         ],
         'WHERE'     => ['glpi_groups_users.users_id' => $ID]
      ]);
      $number = count($iterator);

      $group_where = [];
      while ($data = $iterator->next()) {
         $group_where[$field_group][] = $data['groups_id'];
         $groups[$data["groups_id"]] = $data["name"];
      }

      echo "<div class='spaced'><table class='tab_cadre_fixehov'>";
      $header = "<tr><th>"._n('Type', 'Types', 1)."</th>";
      $header .= "<th>".Entity::getTypeName(1)."</th>";
      $header .= "<th>".__('Name')."</th>";
      $header .= "<th>".__('Serial number')."</th>";
      $header .= "<th>".__('Inventory number')."</th>";
      $header .= "<th>".__('Status')."</th>";
      $header .= "<th>&nbsp;</th></tr>";
      echo $header;

      foreach ($type_user as $itemtype) {
         if (!($item = getItemForItemtype($itemtype))) {
            continue;
         }
         if ($item->canView()) {
            $itemtable = getTableForItemType($itemtype);
            $iterator_params = [
               'FROM'   => $itemtable,
               'WHERE'  => [$field_user => $ID]
            ];

            if ($item->maybeTemplate()) {
               $iterator_params['WHERE']['is_template'] = 0;
            }
            if ($item->maybeDeleted()) {
               $iterator_params['WHERE']['is_deleted'] = 0;
            }

            $item_iterator = $DB->request($iterator_params);

            $type_name = $item->getTypeName();

            while ($data = $item_iterator->next()) {
               $cansee = $item->can($data["id"], READ);
               $link   = $data["name"];
               if ($cansee) {
                  $link_item = $item::getFormURLWithID($data['id']);
                  if ($_SESSION["glpiis_ids_visible"] || empty($link)) {
                     $link = sprintf(__('%1$s (%2$s)'), $link, $data["id"]);
                  }
                  $link = "<a href='".$link_item."'>".$link."</a>";
               }
               $linktype = "";
               if ($data[$field_user] == $ID) {
                  $linktype = self::getTypeName(1);
               }
               echo "<tr class='tab_bg_1'><td class='center'>$type_name</td>";
               echo "<td class='center'>".Dropdown::getDropdownName("glpi_entities",
                                                                     $data["entities_id"])."</td>";
               echo "<td class='center'>$link</td>";
               echo "<td class='center'>";
               if (isset($data["serial"]) && !empty($data["serial"])) {
                  echo $data["serial"];
               } else {
                  echo '&nbsp;';
               }
               echo "</td><td class='center'>";
               if (isset($data["otherserial"]) && !empty($data["otherserial"])) {
                  echo $data["otherserial"];
               } else {
                  echo '&nbsp;';
               }
               echo "</td><td class='center'>";
               if (isset($data["states_id"])) {
                  echo Dropdown::getDropdownName("glpi_states", $data['states_id']);
               } else {
                  echo '&nbsp;';
               }

               echo "</td><td class='center'>$linktype</td></tr>";
            }
         }
      }
      if ($number) {
         echo $header;
      }
      echo "</table></div>";

      if (count($group_where)) {
         echo "<div class='spaced'><table class='tab_cadre_fixehov'>";
         $header = "<tr>".
               "<th>"._n('Type', 'Types', 1)."</th>".
               "<th>".Entity::getTypeName(1)."</th>".
               "<th>".__('Name')."</th>".
               "<th>".__('Serial number')."</th>".
               "<th>".__('Inventory number')."</th>".
               "<th>".__('Status')."</th>".
               "<th>&nbsp;</th></tr>";
         echo $header;
         $nb = 0;
         foreach ($type_group as $itemtype) {
            if (!($item = getItemForItemtype($itemtype))) {
               continue;
            }
            if ($item->canView() && $item->isField($field_group)) {
               $itemtable = getTableForItemType($itemtype);
               $iterator_params = [
                  'FROM'   => $itemtable,
                  'WHERE'  => ['OR' => $group_where]
               ];

               if ($item->maybeTemplate()) {
                  $iterator_params['WHERE']['is_template'] = 0;
               }
               if ($item->maybeDeleted()) {
                  $iterator_params['WHERE']['is_deleted'] = 0;
               }

               $group_iterator = $DB->request($iterator_params);

               $type_name = $item->getTypeName();

               while ($data = $group_iterator->next()) {
                  $nb++;
                  $cansee = $item->can($data["id"], READ);
                  $link   = $data["name"];
                  if ($cansee) {
                     $link_item = $item::getFormURLWithID($data['id']);
                     if ($_SESSION["glpiis_ids_visible"] || empty($link)) {
                        $link = sprintf(__('%1$s (%2$s)'), $link, $data["id"]);
                     }
                     $link = "<a href='".$link_item."'>".$link."</a>";
                  }
                  $linktype = "";
                  if (isset($groups[$data[$field_group]])) {
                     $linktype = sprintf(__('%1$s = %2$s'), Group::getTypeName(1),
                                          $groups[$data[$field_group]]);
                  }
                  echo "<tr class='tab_bg_1'><td class='center'>$type_name</td>";
                  echo "<td class='center'>".Dropdown::getDropdownName("glpi_entities",
                                                                        $data["entities_id"]);
                  echo "</td><td class='center'>$link</td>";
                  echo "<td class='center'>";
                  if (isset($data["serial"]) && !empty($data["serial"])) {
                     echo $data["serial"];
                  } else {
                     echo '&nbsp;';
                  }
                  echo "</td><td class='center'>";
                  if (isset($data["otherserial"]) && !empty($data["otherserial"])) {
                     echo $data["otherserial"];
                  } else {
                     echo '&nbsp;';
                  }
                  echo "</td><td class='center'>";
                  if (isset($data["states_id"])) {
                     echo Dropdown::getDropdownName("glpi_states", $data['states_id']);
                  } else {
                     echo '&nbsp;';
                  }

                  echo "</td><td class='center'>$linktype</td></tr>";
               }
            }
         }
         if ($nb) {
            echo $header;
         }
         echo "</table></div>";
      }
   }


   /**
    * Get user by email, importing it from LDAP if not existing.
    *
    * @param string $email
    *
    * @return integer ID of user, 0 if not found nor imported
    */
   static function getOrImportByEmail($email = '') {
      global $DB, $CFG_GLPI;

      $iterator = $DB->request([
         'SELECT'    => 'users_id AS id',
         'FROM'      => 'glpi_useremails',
         'LEFT JOIN' => [
            'glpi_users' => [
               'FKEY' => [
                  'glpi_useremails' => 'users_id',
                  'glpi_users'      => 'id'
               ]
            ]
         ],
         'WHERE'     => [
            'glpi_useremails.email' => $DB->escape(stripslashes($email))
         ],
         'ORDER'     => ['glpi_users.is_active DESC', 'is_deleted ASC']
      ]);

      //User still exists in DB
      if (count($iterator)) {
         $result = $iterator->next();
         return $result['id'];
      } else {
         if ($CFG_GLPI["is_users_auto_add"]) {
            //Get all ldap servers with email field configured
            $ldaps = AuthLDAP::getServersWithImportByEmailActive();
            //Try to find the user by his email on each ldap server

            foreach ($ldaps as $ldap) {
               $params = [
                  'method' => AuthLDAP::IDENTIFIER_EMAIL,
                  'value'  => $email,
               ];
               $res = AuthLDAP::ldapImportUserByServerId($params,
                                                         AuthLDAP::ACTION_IMPORT,
                                                         $ldap);

               if (isset($res['id'])) {
                  return $res['id'];
               }
            }
         }
      }
      return 0;
   }


   /**
    * Handle user deleted in LDAP using configured policy.
    *
    * @param integer $users_id
    *
    * @return void
    */
   static function manageDeletedUserInLdap($users_id) {
      global $CFG_GLPI;

      //The only case where users_id can be null if when a user has been imported into GLPI
      //it's dn still exists, but doesn't match the connection filter anymore
      //In this case, do not try to process the user
      if (!$users_id) {
         return;
      }

      //User is present in DB but not in the directory : it's been deleted in LDAP
      $tmp = [
         'id'              => $users_id,
         'is_deleted_ldap' => 1,
      ];
      $myuser = new self();
      $myuser->getFromDB($users_id);

      //User is already considered as delete from ldap
      if ($myuser->fields['is_deleted_ldap'] == 1) {
         return;
      }

      switch ($CFG_GLPI['user_deleted_ldap']) {
         //DO nothing
         default :
         case AuthLDAP::DELETED_USER_PRESERVE:
            $myuser->update($tmp);
            break;

         //Put user in trashbin
         case AuthLDAP::DELETED_USER_DELETE:
            $myuser->delete($tmp);
            break;

         //Delete all user dynamic habilitations and groups
         case AuthLDAP::DELETED_USER_WITHDRAWDYNINFO:
            Profile_User::deleteRights($users_id, true);
            Group_User::deleteGroups($users_id, true);
            $myuser->update($tmp);
            break;

         //Deactivate the user
         case AuthLDAP::DELETED_USER_DISABLE:
            $tmp['is_active'] = 0;
            $myuser->update($tmp);
            break;

         //Deactivate the user+ Delete all user dynamic habilitations and groups
         case AuthLDAP::DELETED_USER_DISABLEANDWITHDRAWDYNINFO:
            $tmp['is_active'] = 0;
            $myuser->update($tmp);
            Profile_User::deleteRights($users_id, true);
            Group_User::deleteGroups($users_id, true);
            break;

      }
      /*
      $changes[0] = '0';
      $changes[1] = '';
      $changes[2] = __('Deleted user in LDAP directory');
      Log::history($users_id, 'User', $changes, 0, Log::HISTORY_LOG_SIMPLE_MESSAGE);*/
   }

   /**
    * Get user ID from its name.
    *
    * @param string $name User name
    *
    * @return integer
    */
   static function getIdByName($name) {
      return self::getIdByField('name', $name);
   }


   /**
    * Get user ID from a field
    *
    * @since 0.84
    *
    * @param string $field Field name
    * @param string $value Field value
    *
    * @return integer
    */
   static function getIdByField($field, $value, $escape = true) {
      global $DB;

      if ($escape) {
         $value = addslashes($value);
      }

      $iterator = $DB->request([
         'SELECT' => 'id',
         'FROM'   => self::getTable(),
         'WHERE'  => [$field => $value]
      ]);

      if (count($iterator) == 1) {
         $row = $iterator->next();
         return (int)$row['id'];
      }
      return false;
   }


   /**
    * Show password update form for current user.
    *
    * @param array $error_messages
    *
    * @return void
    */
   public function showPasswordUpdateForm(array $error_messages = []) {
      global $CFG_GLPI;

      echo '<form method="post" action="' . $CFG_GLPI['root_doc'] . '/front/updatepassword.php">';
      echo '<table class="tab_cadre">';
      echo '<tr><th colspan="2">' . __('Password update') . '</th></tr>';

      if (Session::mustChangePassword()) {
         echo '<tr class="tab_bg_2 center">';
         echo '<td colspan="2" class="red b">';
         echo __('Your password has expired. You must change it to be able to login.');
         echo '</td>';
         echo '</tr>';
      }

      echo '<tr class="tab_bg_1">';
      echo '<td>';
      echo __('Login');
      echo '</td>';
      echo '<td>';
      echo '<input type="text" name="name" value="' . $this->fields['name'] . '" readonly="readonly" />';
      echo '</td>';
      echo '</tr>';

      echo '<tr class="tab_bg_1">';
      echo '<td>';
      echo '<label for="current_password">' . __('Current password') . '</label>';
      echo '</td>';
      echo '<td>';
      echo '<input type="password" id="current_password" name="current_password" />';
      echo '</td>';
      echo '</tr>';

      echo '<tr class="tab_bg_1">';
      echo '<td>';
      echo '<label for="password">' . __('New password') . '</label>';
      echo '</td>';
      echo '<td>';
      echo '<input type="password" id="password" name="password" autocomplete="new-password" onkeyup="return passwordCheck();" />';
      echo '</td>';
      echo '</tr>';

      echo '<tr class="tab_bg_1">';
      echo '<td>';
      echo '<label for="password2">' . __('New password confirmation') . '</label>';
      echo '</td>';
      echo '<td>';
      echo '<input type="password" id="password2" name="password2" autocomplete="new-password" />';
      echo '</td>';
      echo '</tr>';

      if ($CFG_GLPI['use_password_security']) {
         echo '<tr class="tab_bg_1">';
         echo '<td>' . __('Password security policy') . '</td>';
         echo '<td>';
         Config::displayPasswordSecurityChecks();
         echo '</td>';
         echo '</tr>';
      }

      echo '<tr class="tab_bg_2 center">';
      echo '<td colspan="2">';
      echo '<input type="submit" name="update" value="' . __s('Save') . '" class="submit" />';
      echo '</td>';
      echo '</tr>';

      if (!empty($error_messages)) {
         echo '<tr class="tab_bg_2 center">';
         echo '<td colspan="2" class="red b">';
         echo implode('<br/>', $error_messages);
         echo '</td>';
         echo '</tr>';
      }

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


   /**
    * Show new password form of password recovery process.
    *
    * @param $token
    *
    * @return void
    */
   static function showPasswordForgetChangeForm($token) {
      global $CFG_GLPI, $DB;

      // Verif token.
      $token_ok = false;
      $iterator = $DB->request([
         'FROM'   => self::getTable(),
         'WHERE'  => [
            'password_forget_token'       => $token,
            new \QueryExpression('NOW() < ADDDATE(' . $DB->quoteName('password_forget_token_date') . ', INTERVAL 1 DAY)')
         ]
      ]);

      if (count($iterator) == 1) {
         $token_ok = true;
      }
      echo "<div class='center'>";

      if ($token_ok) {
         echo "<form method='post' name='forgetpassword' action='".$CFG_GLPI['root_doc'].
                "/front/lostpassword.php'>";
         echo "<table class='tab_cadre'>";
         echo "<tr><th colspan='2'>" . __('Forgotten password?')."</th></tr>";

         echo "<tr class='tab_bg_1'>";
         echo "<td colspan='2'>". __('Please confirm your email address and enter your new password.').
              "</td></tr>";

         echo "<tr class='tab_bg_1'><td>" . _n('Email', 'Emails', 1)."</td>";
         echo "<td><input type='text' name='email' value='' size='60'></td></tr>";

         echo "<tr class='tab_bg_1'><td>" . __('Password')."</td>";
         echo "<td><input id='password' type='password' name='password' value='' size='20'
                    autocomplete='new-password' onkeyup=\"return passwordCheck();\">";
         echo "</td></tr>";

         echo "<tr class='tab_bg_1'><td>" . __('Password confirmation')."</td>";
         echo "<td><input type='password' name='password2' value='' size='20' autocomplete='new-password'>";
         echo "</td></tr>";

         echo "<tr class='tab_bg_1'><td>".__('Password security policy')."</td>";
         echo "<td>";
         Config::displayPasswordSecurityChecks();
         echo "</td></tr>";

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

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

      } else {
         echo __('Your password reset request has expired or is invalid. Please renew it.');
      }
      echo "</div>";
   }


   /**
    * Show request form of password recovery process.
    *
    * @return void
    */
   static function showPasswordForgetRequestForm() {
      global $CFG_GLPI;

      echo "<div class='center'>";
      echo "<form method='post' name='forgetpassword' action='".$CFG_GLPI['root_doc'].
             "/front/lostpassword.php'>";
      echo "<table class='tab_cadre'>";
      echo "<tr><th colspan='2'>" . __('Forgotten password?')."</th></tr>";

      echo "<tr class='tab_bg_1'><td colspan='2'>" .
            __('Please enter your email address. An email will be sent to you and you will be able to choose a new password.').
           "</td></tr>";

      echo "<tr class='tab_bg_2 center'>";
      echo "<td><input type='text' size='60' name='email' value=''></td>";
      echo "<td><input type='submit' name='update' value=\"".__s('Save')."\" class='submit'>";
      echo "</td></tr>";

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


   /**
    * Handle password recovery form submission.
    *
    * @param array $input
    *
    * @throws ForgetPasswordException when requirements are not met
    *
    * @return boolean true if password successfully changed, false otherwise
    */
   public function updateForgottenPassword(array $input) {
      $condition = [
         'glpi_users.is_active'  => 1,
         'glpi_users.is_deleted' => 0, [
            'OR' => [
               ['glpi_users.begin_date' => null],
               ['glpi_users.begin_date' => ['<', new QueryExpression('NOW()')]]
            ],
         ], [
            'OR'  => [
               ['glpi_users.end_date'   => null],
               ['glpi_users.end_date'   => ['>', new QueryExpression('NOW()')]]
            ]
         ]
      ];
      if ($this->getFromDBbyEmail($input['email'], $condition)) {
         if (($this->fields["authtype"] == Auth::DB_GLPI)
             || !Auth::useAuthExt()) {

            if (($input['password_forget_token'] == $this->fields['password_forget_token'])
                && (abs(strtotime($_SESSION["glpi_currenttime"])
                        -strtotime($this->fields['password_forget_token_date'])) < DAY_TIMESTAMP)) {

               $input['id'] = $this->fields['id'];
               Config::validatePassword($input["password"], false); // Throws exception if password is invalid
               if (!$this->update($input)) {
                  return false;
               }
               $input2 = [
                  'password_forget_token'      => '',
                  'password_forget_token_date' => 'NULL',
                  'id'                         => $this->fields['id']
               ];
               $this->update($input2);
               return true;

            } else {
               throw new ForgetPasswordException(__('Your password reset request has expired or is invalid. Please renew it.'));
            }

         } else {
            throw new ForgetPasswordException(__("The authentication method configuration doesn't allow you to change your password."));
         }

      } else {
         throw new ForgetPasswordException(__('Email address not found.'));
      }

      return false;
   }


   /**
    * Displays password recovery result.
    *
    * @param array $input
    *
    * @return void
    */
   public function showUpdateForgottenPassword(array $input) {
      global $CFG_GLPI;

      echo "<div class='center'>";
      try {
         if (!$this->updateForgottenPassword($input)) {
            Html::displayMessageAfterRedirect();
         } else {
            echo __('Reset password successful.');
         }
      } catch (ForgetPasswordException $e) {
         echo $e->getMessage();
      } catch (PasswordTooWeakException $e) {
         // Force display on error
         foreach ($e->getMessages() as $message) {
            Session::addMessageAfterRedirect($message);
         }
         Html::displayMessageAfterRedirect();
      }

      echo "<br>";
      echo "<a href=\"".$CFG_GLPI['root_doc']."/index.php\">".__s('Back')."</a>";
      echo "</div>";
   }


   /**
    * Send password recovery for a user and display result message.
    *
    * @param string $email email of the user
    *
    * @return void
    */
   public function showForgetPassword($email) {

      echo "<div class='center'>";
      try {
         $this->forgetPassword($email);
      } catch (ForgetPasswordException $e) {
         echo $e->getMessage();
         return;
      }
      echo __('An email has been sent to your email address. The email contains information for reset your password.');
   }

   /**
    * Send password recovery email for a user.
    *
    * @param string $email
    *
    * @throws ForgetPasswordException when requirements are not met
    *
    * @return boolean true if notification successfully created, false if user not found
    */
   public function forgetPassword($email) {
      $condition = [
         'glpi_users.is_active'  => 1,
         'glpi_users.is_deleted' => 0, [
            'OR' => [
               ['glpi_users.begin_date' => null],
               ['glpi_users.begin_date' => ['<', new QueryExpression('NOW()')]]
            ],
         ], [
            'OR'  => [
               ['glpi_users.end_date'   => null],
               ['glpi_users.end_date'   => ['>', new QueryExpression('NOW()')]]
            ]
         ]
      ];

      if ($this->getFromDBbyEmail($email, $condition)) {

         // Send token if auth DB or not external auth defined
         if (($this->fields["authtype"] == Auth::DB_GLPI)
             || !Auth::useAuthExt()) {

            if (NotificationMailing::isUserAddressValid($email)) {
               $input = [
                  'password_forget_token'      => sha1(Toolbox::getRandomString(30)),
                  'password_forget_token_date' => $_SESSION["glpi_currenttime"],
                  'id'                         => $this->fields['id'],
               ];
               $this->update($input);
               // Notication on root entity (glpi_users.entities_id is only a pref)
               NotificationEvent::raiseEvent('passwordforget', $this, ['entities_id' => 0]);
               QueuedNotification::forceSendFor($this->getType(), $this->fields['id']);
               return true;
            } else {
               throw new ForgetPasswordException(__('Invalid email address'));
            }

         } else {
            throw new ForgetPasswordException(__("The authentication method configuration doesn't allow you to change your password."));
         }

      }

      throw new ForgetPasswordException(__('Email address not found.'));
   }


   /**
    * Display information from LDAP server for user.
    *
    * @return void
    */
   private function showLdapDebug() {

      if ($this->fields['authtype'] != Auth::LDAP) {
         return false;
      }
      echo "<div class='spaced'>";
      echo "<table class='tab_cadre_fixe'>";
      echo "<tr><th colspan='4'>".AuthLDAP::getTypeName(1)."</th></tr>";

      echo "<tr class='tab_bg_2'><td>".__('User DN')."</td>";
      echo "<td>".$this->fields['user_dn']."</td></tr>\n";

      if ($this->fields['user_dn']) {
         echo "<tr class='tab_bg_2'><td>".__('User information')."</td><td>";
         $config_ldap = new AuthLDAP();
         $ds          = false;

         if ($config_ldap->getFromDB($this->fields['auths_id'])) {
            $ds = $config_ldap->connect();
         }

         if ($ds) {
            $info = AuthLDAP::getUserByDn($ds, $this->fields['user_dn'],
                                          ['*', 'createTimeStamp', 'modifyTimestamp']);
            if (is_array($info)) {
               Html::printCleanArray($info);
            } else {
               echo __('No item to display');
            }

         } else {
            echo __('Connection failed');
         }

         echo "</td></tr>\n";
      }

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


   /**
    * Display debug information for current object.
    *
    * @return void
    */
   function showDebug() {

      NotificationEvent::debugEvent($this);
      $this->showLdapDebug();
   }

   function getUnicityFieldsToDisplayInErrorMessage() {

      return ['id'          => __('ID'),
                   'entities_id' => Entity::getTypeName(1)];
   }


   function getUnallowedFieldsForUnicity() {

      return array_merge(parent::getUnallowedFieldsForUnicity(),
                         ['auths_id', 'date_sync', 'entities_id', 'last_login', 'profiles_id']);
   }


   /**
    * Get a unique generated token.
    *
    * @param string $field Field storing the token
    *
    * @return string
    */
   static function getUniqueToken($field = 'personal_token') {
      global $DB;

      $ok = false;
      do {
         $key    = Toolbox::getRandomString(40);
         $row = $DB->request([
            'COUNT'  => 'cpt',
            'FROM'   => self::getTable(),
            'WHERE'  => [$field => $key]
         ])->next();

         if ($row['cpt'] == 0) {
            return $key;
         }
      } while (!$ok);

   }


   /**
    * Get token of a user. If not exists generate it.
    *
    * @param integer $ID    User ID
    * @param string  $field Field storing the token
    *
    * @return string|boolean User token, false if user does not exist
    */
   static function getToken($ID, $field = 'personal_token') {

      $user = new self();
      if ($user->getFromDB($ID)) {
         return $user->getAuthToken($field);
      }

      return false;
   }

   /**
    * Get token of a user. If it does not exists  then generate it.
    *
    * @since 9.4
    *
    * @param string $field the field storing the token
    * @param boolean $force_new force generation of a new token
    *
    * @return string|false token or false in case of error
    */
   public function getAuthToken($field = 'personal_token', $force_new = false) {
      global $CFG_GLPI;

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

      // check date validity for cookie token
      $outdated = false;
      if ($field === 'cookie_token') {
         $date_create = new DateTime($this->fields[$field."_date"]);
         $date_expir  = $date_create->add(new DateInterval('PT'.$CFG_GLPI["login_remember_time"].'S'));

         if ($date_expir < new DateTime()) {
            $outdated = true;
         }
      }

      // token exists, is not oudated, and we may use it
      if (!empty($this->fields[$field]) && !$force_new && !$outdated) {
         return $this->fields[$field];
      }

      // else get a new token
      $token = self::getUniqueToken($field);

      // for cookie token, we need to store it hashed
      $hash = $token;
      if ($field === 'cookie_token') {
         $hash = Auth::getPasswordHash($token);
      }

      // save this token in db
      $this->update(['id'             => $this->getID(),
                     $field           => $hash,
                     $field . "_date" => $_SESSION['glpi_currenttime']]);

      return $token;
   }


   /**
    * Get name of users using default passwords
    *
    * @return string[]
    */
   static function checkDefaultPasswords() {
      global $DB;

      $passwords = ['glpi'      => 'glpi',
                         'tech'      => 'tech',
                         'normal'    => 'normal',
                         'post-only' => 'postonly'];
      $default_password_set = [];

      $crit = ['FIELDS'     => ['name', 'password'],
                    'is_active'  => 1,
                    'is_deleted' => 0,
                    'name'       => array_keys($passwords)];

      foreach ($DB->request('glpi_users', $crit) as $data) {
         if (Auth::checkPassword($passwords[strtolower($data['name'])], $data['password'])) {
            $default_password_set[] = $data['name'];
         }
      }

      return $default_password_set;
   }


   /**
    * Get picture URL from picture field.
    *
    * @since 0.85
    *
    * @param string $picture Picture field value
    *
    * @return string
    */
   static function getURLForPicture($picture) {
      global $CFG_GLPI;

      $url = Toolbox::getPictureUrl($picture);
      if (null !== $url) {
         return $url;
      }

      return $CFG_GLPI["root_doc"]."/pics/picture.png";
   }


   /**
    * Get thumbnail URL from picture field.
    *
    * @since 0.85
    *
    * @param string $picture Picture field value
    *
    * @return string
    */
   static function getThumbnailURLForPicture($picture) {
      global $CFG_GLPI;

      // prevent xss
      $picture = Html::cleanInputText($picture);

      if (!empty($picture)) {
         $tmp = explode(".", $picture);
         if (count($tmp) ==2) {
            return $CFG_GLPI["root_doc"]."/front/document.send.php?file=_pictures/".$tmp[0].
                   "_min.".$tmp[1];
         }
         return $CFG_GLPI["root_doc"]."/pics/picture_min.png";
      }
      return $CFG_GLPI["root_doc"]."/pics/picture_min.png";

   }


   /**
    * Drop existing files for user picture.
    *
    * @since 0.85
    *
    * @param string $picture Picture field value
    *
    * @return void
    */
   static function dropPictureFiles($picture) {

      if (!empty($picture)) {
         // unlink main file
         if (file_exists(GLPI_PICTURE_DIR."/$picture")) {
            @unlink(GLPI_PICTURE_DIR."/$picture");
         }
         // unlink Thunmnail
         $tmp = explode(".", $picture);
         if (count($tmp) == 2) {
            if (file_exists(GLPI_PICTURE_DIR."/".$tmp[0]."_min.".$tmp[1])) {
               @unlink(GLPI_PICTURE_DIR."/".$tmp[0]."_min.".$tmp[1]);
            }
         }
      }
   }

   function getRights($interface = 'central') {

      $values = parent::getRights();
      //TRANS: short for : Add users from an external source
      $values[self::IMPORTEXTAUTHUSERS] = ['short' => __('Add external'),
                                                'long'  => __('Add users from an external source')];
       //TRANS: short for : Read method for user authentication and synchronization
      $values[self::READAUTHENT]        = ['short' => __('Read auth'),
                                                'long'  => __('Read user authentication and synchronization method')];
      //TRANS: short for : Update method for user authentication and synchronization
      $values[self::UPDATEAUTHENT]      = ['short' => __('Update auth and sync'),
                                                'long'  => __('Update method for user authentication and synchronization')];

      return $values;
   }


   /**
    * Retrieve the list of LDAP field names from a list of fields
    * allow pattern substitution, e.g. %{name}.
    *
    * @since 9.1
    *
    * @param string[] $map array of fields
    *
    * @return string[]
    */
   private static function getLdapFieldNames(array $map) {

      $ret =  [];
      foreach ($map as $v) {
         /** @var array $reg */
         if (preg_match_all('/%{(.*)}/U', $v, $reg)) {
            // e.g. "%{country} > %{city} > %{site}"
            foreach ($reg [1] as $f) {
               $ret [] = $f;
            }
         } else {
            // single field name
            $ret [] = $v;
         }
      }
      return $ret;
   }


   /**
    * Retrieve the value of a fields from a LDAP result applying needed substitution of %{value}.
    *
    * @since 9.1
    *
    * @param string $map String with field format
    * @param array  $res LDAP result
    *
    * @return string
    */
   private static function getLdapFieldValue($map, array $res) {

      $map = Toolbox::unclean_cross_side_scripting_deep($map);
      $ret = preg_replace_callback('/%{(.*)}/U',
                                    function ($matches) use ($res) {
                                       return (isset($res[0][$matches[1]][0]) ? $res[0][$matches[1]][0] : '');
                                    }, $map );

      return $ret == $map ? (isset($res[0][$map][0]) ? $res[0][$map][0] : '') : $ret;
   }

   /**
    * Get/Print the switch language form.
    *
    * @param boolean $display Whether to display or return output
    * @param array   $options Options
    *    - string   value       Selected language value
    *    - boolean  showbutton  Whether to display or not submit button
    *
    * @return void|string Nothing if displayed, string to display otherwise
    */
   function showSwitchLangForm($display = true, array $options = []) {

      $params = [
         'value'        => $_SESSION["glpilanguage"],
         'display'      => false,
         'showbutton'   => true
      ];

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

      $out = '';
      $out .= "<form method='post' name='switchlang' action='".User::getFormURL()."' autocomplete='off'>";
      $out .= "<p class='center'>";
      $out .= Dropdown::showLanguages("language", $params);
      if ($params['showbutton'] === true) {
         $out .= "&nbsp;<input type='submit' name='update' value=\""._sx('button', 'Save')."\" class='submit'>";
      }
      $out .= "</p>";
      $out .= Html::closeForm(false);

      if ($display === true) {
         echo $out;
      } else {
         return $out;
      }
   }

   /**
    * Get list of entities ids for current user.
    *
    * @return integer[]
    */
   private function getEntities() {
      //get user entities
      if ($this->entities == null) {
         $this->entities = Profile_User::getUserEntities($this->fields['id'], true);
      }
      return $this->entities;
   }


   /**
    * Give cron information.
    *
    * @param string $name Task's name
    *
    * @return array
    */
   public static function cronInfo(string $name): array {

      $info = [];
      switch ($name) {
         case 'passwordexpiration':
            $info = [
               'description' => __('Handle users passwords expiration policy'),
               'parameter'   => __('Maximum expiration notifications to send at once'),
            ];
            break;
      }
      return $info;
   }

   /**
    * Cron that notify users about when their password expire and deactivate their account
    * depending on password expiration policy.
    *
    * @param CronTask $task
    *
    * @return integer
    */
   public static function cronPasswordExpiration(CronTask $task) {
      global $CFG_GLPI, $DB;

      $expiration_delay   = (int)$CFG_GLPI['password_expiration_delay'];
      $notice_time        = (int)$CFG_GLPI['password_expiration_notice'];
      $notification_limit = (int)$task->fields['param'];
      $lock_delay         = (int)$CFG_GLPI['password_expiration_lock_delay'];

      if (-1 === $expiration_delay || (-1 === $notice_time && -1 === $lock_delay)) {
         // Nothing to do if passwords does not expire
         // or if password expires without notice and with no lock delay
         return 0;
      }

      // Notify users about expiration of their password.
      $to_notify_count = 0;
      if (-1 !== $notice_time) {
         $notification_request = [
            'FROM'      => self::getTable(),
            'LEFT JOIN' => [
               Alert::getTable() => [
                  'ON' => [
                     Alert::getTable() => 'items_id',
                     self::getTable()  => 'id',
                     [
                        'AND' => [
                           Alert::getTableField('itemtype') => self::getType(),
                        ]
                     ],
                  ]
               ]
            ],
            'WHERE'     => [
               self::getTableField('is_deleted') => 0,
               self::getTableField('is_active')  => 1,
               self::getTableField('authtype')   => Auth::DB_GLPI,
               new QueryExpression(
                  sprintf(
                     'NOW() > ADDDATE(%s, INTERVAL %s DAY)',
                     $DB->quoteName(self::getTableField('password_last_update')),
                     $expiration_delay - $notice_time
                  )
               ),
               // Get only users that has not yet been notified within last day
               'OR'                              => [
                  [Alert::getTableField('date') => null],
                  [Alert::getTableField('date') => ['<', new QueryExpression('CURRENT_TIMESTAMP() - INTERVAL 1 day')]],
               ],
            ],
         ];

         $to_notify_count_request = array_merge(
            $notification_request,
            [
               'COUNT'  => 'cpt',
            ]
         );
         $to_notify_count = $DB->request($to_notify_count_request)->next()['cpt'];

         $notification_data_request  = array_merge(
            $notification_request,
            [
               'SELECT'    => [
                  self::getTableField('id as user_id'),
                  Alert::getTableField('id as alert_id'),
               ],
               'LIMIT'     => $notification_limit,
            ]
         );
         $notification_data_iterator = $DB->request($notification_data_request);

         foreach ($notification_data_iterator as $notification_data) {
            $user_id  = $notification_data['user_id'];
            $alert_id = $notification_data['alert_id'];

            $user = new User();
            $user->getFromDB($user_id);

            $is_notification_send = NotificationEvent::raiseEvent(
               'passwordexpires',
               $user,
               ['entities_id' => 0] // Notication on root entity (glpi_users.entities_id is only a pref)
            );
            if (!$is_notification_send) {
               continue;
            }

            $task->addVolume(1);

            $alert = new Alert();

            // Delete existing alert if any
            if (null !== $alert_id) {
               $alert->delete(['id' => $alert_id]);
            }

            // Add an alert to not warn user for at least one day
            $alert->add(
               [
                  'itemtype' => 'User',
                  'items_id' => $user_id,
                  'type'     => Alert::NOTICE,
               ]
            );
         }
      }

      // Disable users if their password has expire for too long.
      if (-1 !== $lock_delay) {
         $DB->update(
            self::getTable(),
            [
               'is_active'         => 0,
               'cookie_token'      => null,
               'cookie_token_date' => null,
            ],
            [
               'is_deleted' => 0,
               'is_active'  => 1,
               'authtype'   => Auth::DB_GLPI,
               new QueryExpression(
                  sprintf(
                     'NOW() > ADDDATE(ADDDATE(%s, INTERVAL %d DAY), INTERVAL %s DAY)',
                     $DB->quoteName(self::getTableField('password_last_update')),
                     $expiration_delay,
                     $lock_delay
                  )
               ),
            ]
         );
      }

      return -1 !== $notice_time && $to_notify_count > $notification_limit
         ? -1 // -1 for partial process (remaining notifications to send)
         : 1; // 1 for fully process
   }

   /**
    * Get password expiration time.
    *
    * @return null|int Password expiration time, or null if expiration mechanism is not active.
    */
   public function getPasswordExpirationTime() {
      global $CFG_GLPI;

      if (!array_key_exists('id', $this->fields) || $this->fields['id'] < 1) {
         return null;
      }

      $expiration_delay = (int)$CFG_GLPI['password_expiration_delay'];

      if (-1 === $expiration_delay) {
         return null;
      }

      return strtotime(
         '+ ' . $expiration_delay . ' days',
         strtotime($this->fields['password_last_update'])
      );
   }

   /**
    * Check if password should be changed (if it expires soon).
    *
    * @return boolean
    */
   public function shouldChangePassword() {
      global $CFG_GLPI;

      if ($this->hasPasswordExpired()) {
         return true; // too late to change password, but returning false would not be logical here
      }

      $expiration_time = $this->getPasswordExpirationTime();
      if (null === $expiration_time) {
         return false;
      }

      $notice_delay    = (int)$CFG_GLPI['password_expiration_notice'];
      if (-1 === $notice_delay) {
         return false;
      }

      $notice_time = strtotime('- ' . $notice_delay . ' days', $expiration_time);

      return $notice_time < time();
   }

   /**
    * Check if password expired.
    *
    * @return boolean
    */
   public function hasPasswordExpired() {

      $expiration_time = $this->getPasswordExpirationTime();
      if (null === $expiration_time) {
         return false;
      }

      return $expiration_time < time();
   }

   public static function getFriendlyNameSearchCriteria(string $filter): array {
      $table     = self::getTable();
      $login     = DBmysql::quoteName("$table.name");
      $firstname = DBmysql::quoteName("$table.firstname");
      $lastname  = DBmysql::quoteName("$table.realname");

      $filter = strtolower($filter);
      $filter_no_spaces = str_replace(" ", "", $filter);

      return [
         'OR' => [
            ['RAW' => ["LOWER($login)" => ['LIKE', "%$filter%"]]],
            ['RAW' => ["LOWER(REPLACE(CONCAT($firstname, $lastname), ' ', ''))" => ['LIKE', "%$filter_no_spaces%"]]],
            ['RAW' => ["LOWER(REPLACE(CONCAT($lastname, $firstname), ' ', ''))" => ['LIKE', "%$filter_no_spaces%"]]],
         ]
      ];
   }

   public static function getFriendlyNameFields(string $alias = "name") {
      $config = Config::getConfigurationValues('core');
      if ($config['names_format'] == User::FIRSTNAME_BEFORE) {
         $first = "firstname";
         $second = "realname";
      } else {
         $first = "realname";
         $second = "firstname";
      }

      $table  = self::getTable();
      $first  = DB::quoteName("$table.$first");
      $second = DB::quoteName("$table.$second");
      $alias  = DB::quoteName($alias);
      $name   = DB::quoteName(self::getNameField());

      return new QueryExpression("IF(
            $first <> '' && $second <> '',
            CONCAT($first, ' ', $second),
            $name
         ) AS $alias"
      );
   }

   static function getIcon() {
      return "fas fa-user";
   }

   /**
    * Add groups stored in "_ldap_rules/groups_id" special input
    */
   public function applyGroupsRules() {
      if (!isset($this->input["_ldap_rules"]['groups_id'])) {
         return;
      }

      $group_ids = array_unique($this->input["_ldap_rules"]['groups_id']);
      foreach ($group_ids as $group_id) {
         $group_user = new Group_User();

         $data = [
            'groups_id' => $group_id,
            'users_id'  => $this->getId()
         ];

         if (!$group_user->getFromDBByCrit($data)) {
            $group_user->add($data);
         }

      }
   }
}
