<?php

/*
 * Copyright 2005 - 2025 Centreon (https://www.centreon.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * For more information : contact@centreon.com
 *
 */

use LDAP\Connection;

/**
 * Class
 *
 * @class CentreonLDAP
 */
class CentreonLDAP
{
    /** @var CentreonLog|null */
    public $centreonLog;

    /** @var Connection */
    private $ds;

    /** @var CentreonDB */
    private $db;

    /** @var string */
    private $linkId;

    /** @var array */
    private $ldapHosts = [];

    /** @var array|null */
    private $ldap = null;

    /** @var array */
    private $constuctCache = [];

    /** @var array|null */
    private $userSearchInfo = null;

    /** @var array|null */
    private $groupSearchInfo = null;

    /** @var bool */
    private $debugImport = false;

    /** @var string */
    private $debugPath = '';

    /**
     * CentreonLDAP constructor
     *
     * @param CentreonDB $pearDB The database connection
     * @param CentreonLog|null $centreonLog The logging object
     * @param null $arId
     *
     * @throws PDOException
     */
    public function __construct($pearDB, $centreonLog = null, $arId = null)
    {
        $this->centreonLog = $centreonLog;
        $this->db = $pearDB;

        // Check if use service form DNS
        $use_dns_srv = 0;
        $dbResult = $this->db->query(
            "SELECT `ari_value`
            FROM `auth_ressource_info`
            WHERE `ari_name` = 'ldap_srv_dns' AND ar_id = " . (int) $arId
        );
        $row = $dbResult->fetch();
        $dbResult->closeCursor();
        if (isset($row['ari_value'])) {
            $use_dns_srv = $row['ari_value'];
        }

        $dbResult = $this->db->query(
            "SELECT `key`, `value`
            FROM `options`
            WHERE `key`
            IN ('debug_ldap_import', 'debug_path')"
        );
        while ($row = $dbResult->fetch()) {
            if ($row['key'] == 'debug_ldap_import') {
                if ($row['value'] == 1) {
                    $this->debugImport = true;
                }
            } elseif ($row['key'] == 'debug_path') {
                $this->debugPath = trim($row['value']);
            }
        }
        $dbResult->closeCursor();
        if ($this->debugPath == '') {
            $this->debugImport = false;
        }

        $connectionTimeout = null;
        $dbConnectionTimeout = $this->getLdapHostParameters($arId, 'ldap_connection_timeout');
        if (! empty($dbConnectionTimeout['ari_value'])) {
            $connectionTimeout = $dbConnectionTimeout['ari_value'];
        }

        $searchTimeout = 5;
        $tempSearchTimeout = $this->getLdapHostParameters($arId, 'ldap_search_timeout');
        if (! empty($tempSearchTimeout['ari_value'])) {
            $searchTimeout = $tempSearchTimeout['ari_value'];
        }

        // Get the list of server ldap
        if ($use_dns_srv != '0') {
            $dns_query = '_ldap._tcp';
            $dbResult = $this->db->query(
                "SELECT `ari_value`
                FROM auth_ressource_info
                WHERE `ari_name` = 'ldap_dns_use_domain'
                AND ar_id = " . (int) $arId
            );
            $row = $dbResult->fetch();
            $dbResult->closeCursor();
            if ($row && trim($row['ari_value']) != '') {
                $dns_query .= '.' . $row['ari_value'];
            }
            $list = dns_get_record($dns_query, DNS_SRV);
            foreach ($list as $entry) {
                $ldap = [
                    'host' => $entry['target'],
                    'id' => $arId,
                    'connection_timeout' => $connectionTimeout,
                    'search_timeout' => $searchTimeout,
                    'info' => $this->getInfoUseDnsConnect(),
                ];
                $ldap['info']['port'] = $entry['port'];
                $ldap['info'] = array_merge($ldap['info'], $this->getBindInfo((int) $arId));
                $this->ldapHosts[] = $ldap;
            }
        } else {
            $dbResult = $this->db->query(
                'SELECT ldap_host_id, host_address
                FROM auth_ressource_host
                WHERE auth_ressource_id = ' . (int) $arId . ' ORDER BY host_order'
            );
            while ($row = $dbResult->fetch()) {
                $ldap = [
                    'host' => $row['host_address'],
                    'id' => $arId,
                    'connection_timeout' => $connectionTimeout,
                    'search_timeout' => $searchTimeout,
                    'info' => $this->getInfoUseDnsConnect(),
                ];
                $ldap['info'] = $this->getInfoConnect($row['ldap_host_id']);
                $ldap['info'] = array_merge($ldap['info'], $this->getBindInfo((int) $arId));
                $this->ldapHosts[] = $ldap;
            }
            $dbResult->closeCursor();
        }
    }

    /**
     * @param int $arId
     * @param string $filter
     *
     * @throws PDOException
     * @return array<int, array<string, string>>
     */
    public function getLdapHostParameters($arId, $filter = ''): array
    {
        // ldap_search_timeout
        $queryLdapHostParameters = 'SELECT * FROM auth_ressource_info WHERE ar_id = ' . (int) $arId;

        if (! empty($filter)) {
            $queryLdapHostParameters .= ' AND `ari_name` = :filter';
        }

        $statement = $this->db->prepare($queryLdapHostParameters);
        if (! empty($filter)) {
            $statement->bindValue(':filter', $filter, PDO::PARAM_STR);
        }
        $statement->execute();

        $finalLdapHostParameters = [];

        while ($rowLdapHostParameters = $statement->fetch()) {
            $finalLdapHostParameters = $rowLdapHostParameters;
        }

        return $finalLdapHostParameters;
    }

    /**
     * Connect to the first LDAP server
     *
     * @return bool
     */
    public function connect(): bool
    {
        foreach ($this->ldapHosts as $ldap) {
            $port = '';
            if (isset($ldap['info']['port'])) {
                $port = ':' . $ldap['info']['port'];
            }
            if (isset($ldap['info']['use_ssl']) && $ldap['info']['use_ssl'] == 1) {
                $url = 'ldaps://' . $ldap['host'] . $port . '/';
            } else {
                $url = 'ldap://' . $ldap['host'] . $port . '/';
            }
            $this->debug('LDAP Connect : trying url : ' . $url);
            $this->setErrorHandler();
            $this->ds = ldap_connect($url);
            if (! empty($ldap['connection_timeout'])) {
                ldap_set_option($this->ds, LDAP_OPT_NETWORK_TIMEOUT, $ldap['connection_timeout']);
            }
            ldap_set_option($this->ds, LDAP_OPT_REFERRALS, 0);
            $protocol_version = 3;
            if (isset($ldap['info']['protocol_version'])) {
                $protocol_version = $ldap['info']['protocol_version'];
            }
            ldap_set_option($this->ds, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
            if (isset($ldap['info']['use_tls']) && $ldap['info']['use_tls'] == 1) {
                $this->debug('LDAP Connect : use tls');
                @ldap_start_tls($this->ds);
            }
            restore_error_handler();
            $this->ldap = $ldap;
            $bindResult = $this->rebind();
            if ($bindResult) {
                return true;
            }
            $this->debug('LDAP Connect : connection error');
        }

        return false;
    }

    /**
     * Close LDAP Connexion
     *
     * @return void
     */
    public function close(): void
    {
        $this->setErrorHandler();
        ldap_close($this->ds);
        restore_error_handler();
    }

    /**
     * Rebind with the default bind_dn
     *
     * @throws PDOException
     * @return bool If the connection is good
     */
    public function rebind(): bool
    {
        $this->setErrorHandler();
        if (
            isset($this->ldap['info']['bind_dn'])
            && $this->ldap['info']['bind_dn'] != ''
            && isset($this->ldap['info']['bind_pass'])
            && $this->ldap['info']['bind_pass'] != ''
        ) {
            $this->debug('LDAP Connect : Credentials : ' . $this->ldap['info']['bind_dn']);
            $bindResult = @ldap_bind($this->ds, $this->ldap['info']['bind_dn'], $this->ldap['info']['bind_pass']);
        } else {
            $this->debug('LDAP Connect : Credentials : anonymous');
            $bindResult = @ldap_bind($this->ds);
        }
        if ($bindResult) {
            $this->linkId = $this->ldap['id'];
            $this->loadSearchInfo($this->ldap['id']);
            restore_error_handler();

            return true;
        }
        $this->debug('LDAP Connect : Bind : ' . ldap_error($this->ds));
        restore_error_handler();

        return false;
    }

    /**
     * Send back the ldap resource
     *
     * @return Connection
     */
    public function getDs()
    {
        return $this->ds;
    }

    /**
     * Transform user, group name for filter
     *
     * @param string $name the atrribute
     * @return string The string changed
     */
    public function replaceFilter($name): string
    {
        $name = str_replace('(', '\\(', $name);

        return str_replace(')', '\\)', $name);
    }

    /**
     * Escape special characters in LDAP filter value for username and group
     * @see https://datatracker.ietf.org/doc/html/rfc4515#section-3
     *
     * @param string $name The attribute to escape
     * @return string The escaped string
     */
    public function escapeLdapFilterSpecialChars($name): string
    {
        $specialChars = [
            '\\' => '\\5C',
            '*' => '\\2A',
            '(' => '\\28',
            ')' => '\\29',
            '\0' => '\\00',
        ];

        foreach ($specialChars as $char => $replace) {
            $name = str_replace($char, $replace, $name);
        }

        return $name;
    }

    /**
     * Get the dn for a user
     *
     * @param string $username The username
     * @return string|bool The dn string or false if not found
     */
    public function findUserDn($username): string|false
    {
        if (trim($this->userSearchInfo['filter']) == '') {
            return false;
        }
        $this->setErrorHandler();
        $filter = preg_replace('/%s/', $this->escapeLdapFilterSpecialChars($username), $this->userSearchInfo['filter']);
        $result = ldap_search($this->ds, $this->userSearchInfo['base_search'], $filter);
        // no results were returned using this base_search
        if ($result === false) {
            return false;
        }
        $entries = ldap_get_entries($this->ds, $result);
        restore_error_handler();
        if ($entries['count'] === 0) {
            return false;
        }

        return $entries[0]['dn'];
    }

    /**
     * Get the dn for a group
     *
     * @param string $group The group
     * @return string|bool The dn string or false if not found
     */
    public function findGroupDn($group): string|false
    {
        if (trim($this->groupSearchInfo['filter']) == '') {
            return false;
        }
        $this->setErrorHandler();
        $filter = preg_replace('/%s/', $this->escapeLdapFilterSpecialChars($group), $this->groupSearchInfo['filter']);
        $result = ldap_search($this->ds, $this->groupSearchInfo['base_search'], $filter);
        $entries = ldap_get_entries($this->ds, $result);
        restore_error_handler();
        if ($entries['count'] === 0) {
            return false;
        }

        return $entries[0]['dn'];
    }

    /**
     * Return the list of groups
     *
     * @param string $pattern The pattern for search
     * @return array<array<string,string>> The list of groups
     */
    public function listOfGroups($pattern = '*'): array
    {
        if (! isset($this->groupSearchInfo['filter']) || trim($this->groupSearchInfo['filter']) === '') {
            return [];
        }
        $this->setErrorHandler();
        $filter = preg_replace('/%s/', $pattern, $this->groupSearchInfo['filter']);
        $result = @ldap_search($this->ds, $this->groupSearchInfo['base_search'], $filter);
        if ($result === false) {
            restore_error_handler();

            return [];
        }

        $entries = ldap_get_entries($this->ds, $result);

        $groups = [];
        for ($i = 0; $i < $entries['count']; $i++) {
            $groups[] = [
                'name' => $entries[$i][$this->groupSearchInfo['group_name']][0],
                'dn' => $entries[$i]['dn'],
            ];
        }

        restore_error_handler();

        return $groups;
    }

    /**
     * Return the list of users
     *
     * @param string $pattern The pattern for search
     * @return array The list of users
     */
    public function listOfUsers($pattern = '*'): array
    {
        if (trim($this->userSearchInfo['filter']) == '') {
            return [];
        }
        $this->setErrorHandler();
        $filter = preg_replace('/%s/', $pattern, $this->userSearchInfo['filter']);
        $result = ldap_search($this->ds, $this->userSearchInfo['base_search'], $filter);
        $entries = ldap_get_entries($this->ds, $result);
        $nbEntries = $entries['count'];
        $list = [];
        for ($i = 0; $i < $nbEntries; $i++) {
            $list[] = $entries[$i][$this->userSearchInfo['alias']][0];
        }
        restore_error_handler();

        return $list;
    }

    /**
     * Get a LDAP entry
     *
     * @param string $dn The DN
     * @param array $attr The list of attribute
     * @return array|bool The list of information, or false in error
     */
    public function getEntry($dn, $attr = []): array|false
    {
        $this->setErrorHandler();
        if (! is_array($attr)) {
            $attr = [$attr];
        }
        $result = ldap_read($this->ds, $dn, '(objectClass=*)', $attr);
        if ($result === false) {
            restore_error_handler();

            return false;
        }
        $entry = ldap_get_entries($this->ds, $result);
        if ($entry['count'] === 0) {
            restore_error_handler();

            return false;
        }
        $infos = [];
        foreach ($entry[0] as $info => $value) {
            if (isset($value['count'])) {
                if ($value['count'] === 1) {
                    $infos[$info] = $value[0];
                } elseif ($value['count'] > 1) {
                    $infos[$info] = [];
                    for ($i = 0; $i < $value['count']; $i++) {
                        $infos[$info][$i] = $value[$i];
                    }
                }
            }
        }
        restore_error_handler();

        return $infos;
    }

    /**
     * Get the list of groups for a user
     *
     * @param string $userdn The user dn
     * @return array
     */
    public function listGroupsForUser($userdn): array
    {
        $this->setErrorHandler();
        if (trim($this->groupSearchInfo['filter']) === '') {
            restore_error_handler();

            return [];
        }
        $userdn = str_replace('\\', '\\\\', $userdn);
        $filter = '(&' . preg_replace('/%s/', '*', $this->groupSearchInfo['filter'])
            . '(' . $this->groupSearchInfo['member'] . '=' . $this->replaceFilter($userdn) . '))';
        $result = @ldap_search($this->ds, $this->groupSearchInfo['base_search'], $filter);
        if ($result === false) {
            restore_error_handler();

            return [];
        }
        $entries = ldap_get_entries($this->ds, $result);
        $nbEntries = $entries['count'];
        $list = [];
        for ($i = 0; $i < $nbEntries; $i++) {
            $list[] = $entries[$i][$this->groupSearchInfo['group_name']][0];
        }
        restore_error_handler();

        return $list;
    }

    /**
     * Return the list of member of a group
     *
     * @param string $groupdn The group dn
     * @return array The list of member
     */
    public function listUserForGroup($groupdn): array
    {
        $this->setErrorHandler();
        if (trim($this->userSearchInfo['filter']) == '') {
            restore_error_handler();

            return [];
        }
        $groupdn = str_replace('\\', '\\\\', $groupdn);
        $list = [];
        if (! empty($this->userSearchInfo['group'])) {
            /**
             * we have specific parameter for user to denote groups he belongs to
             */
            $filter = '(&' . preg_replace('/%s/', '*', $this->userSearchInfo['filter'])
                . '(' . $this->userSearchInfo['group'] . '=' . $this->replaceFilter($groupdn) . '))';
            $result = @ldap_search($this->ds, $this->userSearchInfo['base_search'], $filter);

            if ($result === false) {
                restore_error_handler();

                return [];
            }
            $entries = ldap_get_entries($this->ds, $result);
            $nbEntries = $entries['count'];
            for ($i = 0; $i < $nbEntries; $i++) {
                $list[] = $entries[$i]['dn'];
            }
            restore_error_handler();
        } else {
            /**
             * we get list of members by group
             */
            $filter = preg_replace('/%s/', $this->getCnFromDn($groupdn), $this->groupSearchInfo['filter']);
            $result = @ldap_search($this->ds, $this->groupSearchInfo['base_search'], $filter);

            if ($result === false) {
                restore_error_handler();

                return [];
            }
            $entries = ldap_get_entries($this->ds, $result);
            $memberAttribute = $this->groupSearchInfo['member'];
            $nbEntries = ! empty($entries[0][$memberAttribute]['count']) ? $entries[0][$memberAttribute]['count'] : 0;
            for ($i = 0; $i < $nbEntries; $i++) {
                $list[] = $entries[0][$memberAttribute][$i];
            }
            restore_error_handler();
        }

        return $list;
    }

    /**
     * Return the attribute name for ldap
     *
     * @param string $type user or group
     * @param string $info The information to get the attribute name
     * @return string|null The attribute name or null if not found
     */
    public function getAttrName($type, $info): ?string
    {
        switch ($type) {
            case 'user':
                if (isset($this->userSearchInfo[$info])) {
                    return $this->userSearchInfo[$info];
                }
                break;
            case 'group':
                if (isset($this->groupSearchInfo[$info])) {
                    return $this->groupSearchInfo[$info];
                }
                break;
            default:
                return null;
        }

        return null;
    }

    /**
     * Search function
     *
     * @param string|null $filter The filter string, null for use default
     * @param string $basedn The basedn, null for use default
     * @param int $searchLimit The search limit, null for all
     * @param int $searchTimeout The search timeout, null for default
     * @return array<int, array<string, mixed>> The search result
     */
    public function search($filter, $basedn, $searchLimit, $searchTimeout): array
    {
        $this->setErrorHandler();
        $attr = [$this->userSearchInfo['alias'], $this->userSearchInfo['name'], $this->userSearchInfo['email'], $this->userSearchInfo['pager'], $this->userSearchInfo['firstname'], $this->userSearchInfo['lastname']];
        // Set default
        if (is_null($filter)) {
            $filter = $this->userSearchInfo['filter'];
        }
        if (is_null($basedn)) {
            $filter = $this->userSearchInfo['base_search'];
        }
        if (is_null($searchLimit)) {
            $searchLimit = 0;
        }
        if (is_null($searchTimeout)) {
            $searchTimeout = 0;
        }
        // Display debug
        $this->debug('LDAP Search : Base DN : ' . $basedn);
        $this->debug('LDAP Search : Filter : ' . $filter);
        $this->debug('LDAP Search : Size Limit : ' . $searchLimit);
        $this->debug('LDAP Search : Timeout : ' . $searchTimeout);
        // Search
        $filter = preg_replace('/%s/', '*', $filter);
        $sr = ldap_search($this->ds, $basedn, $filter, $attr, 0, $searchLimit, $searchTimeout);

        // Sort
        if ($sr !== false) {
            $numberReturned = ldap_count_entries($this->ds, $sr);
            $this->debug('LDAP Search : ' . ($numberReturned ?? '0') . ' entries found');
        } else {
            $this->debug('LDAP Search : cannot retrieve entries');

            return [];
        }

        $info = ldap_get_entries($this->ds, $sr);
        $this->debug('LDAP Search : ' . $info['count']);
        ldap_free_result($sr);

        // Format the result
        $results = [];
        for ($i = 0; $i < $info['count']; $i++) {
            $result = [];
            $result['dn'] = $info[$i]['dn'] ?? '';
            $result['alias'] = $info[$i][$this->userSearchInfo['alias']][0] ?? '';
            $result['name'] = $info[$i][$this->userSearchInfo['name']][0] ?? '';
            $result['email'] = $info[$i][$this->userSearchInfo['email']][0] ?? '';
            $result['pager'] = $info[$i][$this->userSearchInfo['pager']][0] ?? '';
            $result['firstname'] = $info[$i][$this->userSearchInfo['firstname']][0] ?? '';
            $result['lastname'] = $info[$i][$this->userSearchInfo['lastname']][0] ?? '';
            $results[] = $result;
        }
        restore_error_handler();

        return $results;
    }

    /**
     * Validate the filter string
     *
     * @param string $filter The filter string to validate
     * @return bool
     */
    public static function validateFilterPattern($filter): bool
    {
        return ! (! str_contains($filter, '%s'));
    }

    /**
     * Set a relation between the LDAP's default contactgroup and the user
     *
     * @internal Method needed for the user's manual and auto import from the LDAP
     * @since 18.10.4
     *
     * @param int|null $arId The Id of the chosen LDAP, from which we'll find the default contactgroup
     * @param int|null $contactId The Id of the contact to be added
     *
     * @throws exception
     * @return bool Return true to the parent if everything goes well. Needed for the method calling it
     */
    public function addUserToLdapDefaultCg(?int $arId = null, ?int $contactId = null): bool
    {
        $ldapCg = null;
        try {
            // Searching the default contactgroup chosen in the ldap configuration
            $resLdap = $this->db->prepare(
                'SELECT ari_value FROM auth_ressource_info '
                . "WHERE ari_name LIKE 'ldap_default_cg' AND ar_id = :arId"
            );
            $resLdap->bindValue(':arId', $arId, PDO::PARAM_INT);
            $resLdap->execute();
            while ($result = $resLdap->fetch()) {
                $ldapCg = $result['ari_value'];
            }
            unset($resLdap);
            if (! $ldapCg) {
                // No default contactgroup was set in the LDAP parameters
                return true;
            }

            // Checking if the user isn't already linked to this contactgroup
            $resCgExist = $this->db->prepare(
                'SELECT COUNT(*) AS `exist` FROM contactgroup_contact_relation '
                . 'WHERE contact_contact_id = :contactId '
                . 'AND contactgroup_cg_id = :ldapCg'
            );
            $resCgExist->bindValue(':contactId', $contactId, PDO::PARAM_INT);
            $resCgExist->bindValue(':ldapCg', $ldapCg, PDO::PARAM_INT);
            $resCgExist->execute();
            $row = $resCgExist->fetch();
            unset($resCgExist);
            if ($row['exist'] != 0) {
                // User is already linked to this contactgroup
                return true;
            }

            // Inserting the user to the chosen default contactgroup
            $resCg = $this->db->prepare(
                'INSERT INTO contactgroup_contact_relation '
                . '(contactgroup_cg_id, contact_contact_id) '
                . 'VALUES (:ldapDefaultCg, :contactId)'
            );
            $resCg->bindValue(':ldapDefaultCg', $ldapCg, PDO::PARAM_INT);
            $resCg->bindValue(':contactId', $contactId, PDO::PARAM_INT);
            $resCg->execute();
            unset($resCg);
        } catch (PDOException $e) {
            return false;
        }

        return true;
    }

    /**
     * Update user's LDAP last sync in the contact table
     *
     * @param array<string, mixed> $currentUser User's alias and Id are needed
     * @throws Exception
     * @return void
     */
    public function setUserCurrentSyncTime(array $currentUser): void
    {
        $stmt = $this->db->prepare(
            "UPDATE contact SET
               `contact_ldap_last_sync` = :currentTime,
               `contact_ldap_required_sync` = '0'
            WHERE contact_id = :contactId"
        );
        try {
            $stmt->bindValue(':currentTime', time(), PDO::PARAM_INT);
            $stmt->bindValue(':contactId', $currentUser['contact_id'], PDO::PARAM_INT);
            $stmt->execute();
        } catch (PDOException $e) {
            $this->centreonLog->insertLog(
                3,
                "LDAP MANUAL SYNC : Failed to update ldap_last_sync's values for " . $currentUser['contact_alias']
            );
        }
    }

    /**
     * If the option is disabled in the LDAP parameter's form, we don't sync the LDAP user's modifications on login
     * unless it's required
     * If it's enabled, we need to wait until the next synchronization
     *
     * @param int $arId Id of the current LDAP
     * @param int $contactId Id the contact
     * @return bool
     * @internal Needed on user's login and when manually requesting an update of user's LDAP data
     */
    public function isSyncNeededAtLogin(int $arId, int $contactId): bool
    {
        try {
            // checking if an override was manually set on this contact
            $stmtManualRequest = $this->db->prepare(
                'SELECT `contact_name`, `contact_ldap_required_sync`, `contact_ldap_last_sync`
                FROM contact WHERE contact_id = :contactId'
            );
            $stmtManualRequest->bindValue(':contactId', $contactId, PDO::PARAM_INT);
            $stmtManualRequest->execute();
            $contactData = $stmtManualRequest->fetch();
            // check if a manual override was set for this user
            if ($contactData !== false && $contactData['contact_ldap_required_sync'] === '1') {
                $this->centreonLog->insertLog(
                    3,
                    'LDAP AUTH : LDAP synchronization was requested manually for ' . $contactData['contact_name']
                );

                return true;
            }

            // getting the synchronization options
            $stmtSyncState = $this->db->prepare(
                "SELECT ari_name, ari_value FROM auth_ressource_info
                WHERE ari_name IN ('ldap_auto_sync', 'ldap_sync_interval')
                AND ar_id = :arId"
            );
            $stmtSyncState->bindValue(':arId', $arId, PDO::PARAM_INT);
            $stmtSyncState->execute();
            $syncState = [];
            while ($row = $stmtSyncState->fetch()) {
                $syncState[$row['ari_name']] = $row['ari_value'];
            }

            if ($syncState['ldap_auto_sync'] || $contactData['contact_ldap_last_sync'] === 0) {
                // getting the base date reference set in the LDAP parameters
                $stmtLdapBaseSync = $this->db->prepare(
                    'SELECT ar_sync_base_date AS `referenceDate` FROM auth_ressource
                    WHERE ar_id = :arId'
                );
                $stmtLdapBaseSync->bindValue(':arId', $arId, PDO::PARAM_INT);
                $stmtLdapBaseSync->execute();
                $ldapBaseSync = $stmtLdapBaseSync->fetch();

                // checking if the interval between two synchronizations is reached
                $currentTime = time();

                if (
                    ($syncState['ldap_sync_interval'] * 3600 + $contactData['contact_ldap_last_sync']) <= $currentTime
                    && $contactData['contact_ldap_last_sync'] < $ldapBaseSync['referenceDate']
                ) {
                    // synchronization is expected
                    $this->centreonLog->insertLog(
                        3,
                        'LDAP AUTH : Updating user DN of ' . $contactData['contact_name']
                    );

                    return true;
                }
            }
        } catch (PDOException $e) {
            $this->centreonLog->insertLog(
                3,
                'Error while getting automatic synchronization value for LDAP Id : ' . $arId
            );

            // assuming it needs to be synchronized
            return true;
        }
        $this->centreonLog->insertLog(
            3,
            'LDAP AUTH : Synchronization was skipped. For more details, check your LDAP parameters in Administration'
        );

        return false;
    }

    /**
     * Override the custom errorHandler to avoid false errors in the log,
     *
     * @param int $errno The error num
     * @param string $errstr The error message
     * @param string $errfile The error file
     * @param int $errline The error line
     * @return bool
     */
    public function errorLdapHandler($errno, $errstr, $errfile, $errline): bool
    {
        if ($errno === 2 && ldap_errno($this->ds) === 4) {
            /*
            Silencing : 'size limit exceeded' warnings in the logs
            As the $searchLimit value needs to be consistent with the ldap server's configuration and
            as the size limit error thrown is not related with the results.
                ldap_errno : 4 = LDAP_SIZELIMIT_EXCEEDED
                $errno     : 2 = PHP_WARNING
            */
            $this->debug('LDAP Error : Size limit exceeded error. This error was not added to php log. '
                . "Kindly, check your LDAP server's configuration and your Centreon's LDAP parameters.");

            return true;
        }

        // throwing all errors
        $this->debug('LDAP Error : ' . ldap_error($this->ds));

        return false;
    }

    /**
     * Load the search information
     *
     * @param null $ldapHostId
     *
     * @throws PDOException
     * @return void
     */
    private function loadSearchInfo($ldapHostId = null): void
    {
        if (is_null($ldapHostId)) {
            $ldapHostId = $this->linkId;
        }
        $dbResult = $this->db->query(
            "SELECT ari_name, ari_value
            FROM auth_ressource_info ari
            WHERE ari_name IN
                ('user_filter', 'user_base_search', 'alias', 'user_group', 'user_name', 'user_email', 'user_pager',
                'user_firstname', 'user_lastname', 'group_filter', 'group_base_search', 'group_name', 'group_member')
            AND ari.ar_id = " . (int) $ldapHostId
        );
        $user = [];
        $group = [];
        while ($row = $dbResult->fetch()) {
            switch ($row['ari_name']) {
                case 'user_filter':
                    $user['filter'] = $row['ari_value'];
                    break;
                case 'user_base_search':
                    $user['base_search'] = $row['ari_value'];
                    // Fix for domino
                    if (trim($user['base_search']) == '') {
                        $user['base_search'] = '';
                    }
                    break;
                case 'alias':
                    $user['alias'] = $row['ari_value'];
                    break;
                case 'user_group':
                    $user['group'] = $row['ari_value'];
                    break;
                case 'user_name':
                    $user['name'] = $row['ari_value'];
                    break;
                case 'user_email':
                    $user['email'] = $row['ari_value'];
                    break;
                case 'user_pager':
                    $user['pager'] = $row['ari_value'];
                    break;
                case 'user_firstname':
                    $user['firstname'] = $row['ari_value'];
                    break;
                case 'user_lastname':
                    $user['lastname'] = $row['ari_value'];
                    break;
                case 'group_filter':
                    $group['filter'] = $row['ari_value'];
                    break;
                case 'group_base_search':
                    $group['base_search'] = $row['ari_value'];
                    // Fix for domino
                    if (trim($group['base_search']) == '') {
                        $group['base_search'] = ' ';
                    }
                    break;
                case 'group_name':
                    $group['group_name'] = $row['ari_value'];
                    break;
                case 'group_member':
                    $group['member'] = $row['ari_value'];
                    break;
            }
        }
        if (isset($user['filter'])) {
            $this->userSearchInfo = $user;
        }
        if (isset($group['filter'])) {
            $this->groupSearchInfo = $group;
        }
    }

    /**
     * Get the information from the database for a ldap connection
     *
     * @param int $id | id of ldap host
     *
     * @throws PDOException
     * @return array<string, mixed>
     */
    private function getInfoConnect($id): array
    {
        $dbResult = $this->db->query(
            'SELECT use_ssl, use_tls, host_port as port
            FROM auth_ressource_host
            WHERE ldap_host_id = ' . (int) $id
        );

        return $dbResult->fetch();
    }

    /**
     * Get the information from the database for a ldap connection
     *
     * @throws PDOException
     * @return array<string, string>
     */
    private function getInfoUseDnsConnect(): array
    {
        $query = "SELECT `key`, `value` FROM `options` WHERE `key` IN ('ldap_dns_use_ssl', 'ldap_dns_use_tls')";
        $dbResult = $this->db->query($query);
        $infos = [];
        while ($row = $dbResult->fetch()) {
            if ($row['key'] == 'ldap_dns_use_ssl') {
                $infos['use_ssl'] = $row['value'];
            } elseif ($row['key'] == 'ldap_dns_use_tls') {
                $infos['use_tls'] = $row['value'];
            }
        }
        $dbResult->closeCursor();

        return $infos;
    }

    /**
     * Get bind information for connection
     *
     * @param int $id The auth resource id
     *
     * @throws PDOException
     * @return array<string, string>
     */
    private function getBindInfo($id): array
    {
        if (isset($this->constuctCache[$id])) {
            return $this->constuctCache[$id];
        }
        $query = "SELECT ari_name, ari_value 
                 FROM auth_ressource_info 
                 WHERE ari_name IN ('bind_dn', 'bind_pass', 'protocol_version') 
                 AND ar_id = " . (int) $id;
        $dbResult = $this->db->query($query);
        $infos = [];
        while ($row = $dbResult->fetch()) {
            $infos[$row['ari_name']] = $row['ari_value'];
        }
        $dbResult->closeCursor();
        $this->constuctCache[$id] = $infos;

        return $infos;
    }

    /**
     * Debug for ldap
     *
     * @param string $msg
     */
    private function debug($msg): void
    {
        if ($this->debugImport) {
            error_log('[' . date('d/m/Y H:i') . '] ' . $msg . "\n", 3, $this->debugPath . 'ldapsearch.log');
        }
    }

    /**
     * Set the error handler for LDAP
     * @see errorLdapHandler
     */
    private function setErrorHandler(): void
    {
        set_error_handler([$this, 'errorLdapHandler']);
    }

    /**
     * get cn from dn
     *
     * @param string $dn
     * @return string|bool
     */
    private function getCnFromDn($dn): string|false
    {
        if (preg_match('/(?i:(?<=cn=)).*?(?=,[A-Za-z]{0,2}=|$)/', $dn, $dnArray)) {
            return $dnArray !== [] ? $dnArray[0] : false;
        }

        return false;
    }
}
