<?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
 *
 */

require_once realpath(__DIR__ . '/centreonLDAP.class.php');
require_once realpath(__DIR__ . '/centreonACL.class.php');

/**
 * Class
 *
 * @class CentreonContactgroup
 */
class CentreonContactgroup
{
    /** @var CentreonDB */
    private $db;

    /**
     * CentreonContactgroup constructor
     *
     * @param CentreonDB $pearDB
     */
    public function __construct($pearDB)
    {
        $this->db = $pearDB;
    }

    /**
     * Get the list of contactgroups with its id, or its name for a ldap groups if isn't sync in database
     *
     * @param bool $withLdap if include LDAP group
     * @param bool $dbOnly | will not return ldap groups that are not stored in db
     *
     * @throws PDOException
     * @return array
     */
    public function getListContactgroup($withLdap = false, $dbOnly = false)
    {
        // Contactgroup from database
        $contactgroups = [];

        $query = 'SELECT a.cg_id, a.cg_name, a.cg_ldap_dn, b.ar_name FROM contactgroup a ';
        $query .= ' LEFT JOIN auth_ressource b ON a.ar_id = b.ar_id';
        if ($withLdap === false) {
            $query .= " WHERE a.cg_type != 'ldap'";
        }
        $query .= ' ORDER BY a.cg_name';

        $res = $this->db->query($query);
        while ($contactgroup = $res->fetch()) {
            $contactgroups[$contactgroup['cg_id']] = $contactgroup['cg_name'];
            if ($withLdap && isset($contactgroup['cg_ldap_dn']) && $contactgroup['cg_ldap_dn'] != '') {
                $contactgroups[$contactgroup['cg_id']] = $this->formatLdapContactgroupName(
                    $contactgroup['cg_name'],
                    $contactgroup['ar_name']
                );
            }
        }
        $res->closeCursor();

        // Get ldap contactgroups
        if ($withLdap && $dbOnly === false) {
            $ldapContactgroups = $this->getLdapContactgroups();
            $contactgroupNames = array_values($contactgroups);
            foreach ($ldapContactgroups as $id => $name) {
                if (! in_array($name, $contactgroupNames)) {
                    $contactgroups[$id] = $name;
                }
            }
        }

        return $contactgroups;
    }

    /**
     * Get the list of ldap contactgroups
     *
     * @param string $filter
     *
     * @throws PDOException
     * @return array
     */
    public function getLdapContactgroups($filter = '')
    {
        $cgs = [];

        $query = "SELECT `value` FROM `options` WHERE `key` = 'ldap_auth_enable'";
        $res = $this->db->query($query);
        $row = $res->fetch();
        if ($row['value'] == 1) {
            $query = "SELECT ar_id, ar_name FROM auth_ressource WHERE ar_enable = '1'";
            $ldapRes = $this->db->query($query);
            while ($ldapRow = $ldapRes->fetch()) {
                $ldap = new CentreonLDAP($this->db, null, $ldapRow['ar_id']);
                $ldap->connect();
                $ldapGroups = $ldap->listOfGroups();

                foreach ($ldapGroups as $ldapGroup) {
                    $ldapGroupName = $ldapGroup['name'];
                    if (
                        array_search($ldapGroupName . ' (LDAP : ' . $ldapRow['ar_name'] . ')', $cgs) === false
                        && preg_match('/' . $filter . '/i', $ldapGroupName)
                    ) {
                        $cgs['[' . $ldapRow['ar_id'] . ']' . $ldapGroupName] = $this->formatLdapContactgroupName(
                            $ldapGroupName,
                            $ldapRow['ar_name']
                        );
                    }
                }
            }
        }

        return $cgs;
    }

    /**
     * format ldap contactgroup name
     *
     * @param string $cg_name : name of the ldap group name
     * @param string $ldap_name : name of the ldap
     *
     * @return string
     */
    public function formatLdapContactgroupName($cg_name, $ldap_name)
    {
        return $cg_name . ' (LDAP : ' . $ldap_name . ')';
    }

    /**
     * Insert ldap group if does not exist
     *
     * @param int $ldapId
     * @param string $name
     * @param string $dn
     *
     * @throws PDOException
     * @return int|null
     */
    public function insertLdapGroupByNameAndDn(int $ldapId, string $name, string $dn): ?int
    {
        // Check if contactgroup is not in the database
        $ldapGroupId = $this->findLdapGroupIdByName($ldapId, $name);
        if ($ldapGroupId !== null) {
            return $ldapGroupId;
        }

        $query = 'INSERT INTO contactgroup (cg_name, cg_alias, cg_activate, cg_type, cg_ldap_dn, ar_id) '
            . "VALUES (:name, :name, '1', 'ldap', :dn, :ldapId)";
        $statement = $this->db->prepare($query);
        $statement->bindValue(':name', $name, PDO::PARAM_STR);
        $statement->bindValue(':dn', $dn, PDO::PARAM_STR);
        $statement->bindValue(':ldapId', $ldapId, PDO::PARAM_INT);
        $statement->execute();

        return $this->findLdapGroupIdByName($ldapId, $name);
    }

    /**
     * Insert the ldap groups in table contactgroups
     *
     * @param string $cgName The ldap group name
     *
     * @throws PDOException
     * @return int|null The contactgroup id or null if not found
     */
    public function insertLdapGroup(string $cgName): ?int
    {
        // Parse contactgroup name
        if (preg_match('/\[(\d+)\](.*)/', $cgName, $matches) === false) {
            return 0;
        }
        $arId = (int) $matches[1];
        $cgName = $matches[2];

        // Check if contactgroup is not in the database
        $ldapGroupId = $this->findLdapGroupIdByName($arId, $cgName);
        if ($ldapGroupId !== null) {
            return $ldapGroupId;
        }

        $ldap = new CentreonLDAP($this->db, null, $arId);
        $ldap->connect();
        $ldapDn = $ldap->findGroupDn($cgName);

        // Reset ldap build cache time
        $this->db->query('UPDATE options SET `value` = 0 WHERE `key` = "ldap_last_acl_update"');

        if ($ldapDn !== false) {
            $this->insertLdapGroupByNameAndDn($arId, $cgName, $ldapDn);

            return $this->findLdapGroupIdByDn($arId, $ldapDn);
        }

        return null;
    }

    /**
     * Optimized method to get better performance at config generation when LDAP have groups
     * Useful to avoid calculating and refreshing configuration from LDAP when nothing has changed
     *
     * @throws PDOException
     * @return array $msg array of error messages
     */
    public function syncWithLdapConfigGen()
    {
        $msg = [];
        $ldapServerConnError = [];

        $cgRes = $this->db->query(
            "SELECT cg.cg_id, cg.cg_name, cg.cg_ldap_dn, cg.ar_id, ar.ar_name
            FROM contactgroup as cg, auth_ressource as ar
            WHERE cg.cg_type = 'ldap'
            AND cg.ar_id = ar.ar_id
            AND ar.ar_enable = '1'
            AND (
                EXISTS (
                    SELECT 1 FROM contactgroup_host_relation chr WHERE chr.contactgroup_cg_id = cg.cg_id LIMIT 1
                ) OR EXISTS (
                    SELECT 1 FROM contactgroup_service_relation csr WHERE csr.contactgroup_cg_id = cg.cg_id LIMIT 1
                ) OR EXISTS (
                    SELECT 1 FROM contactgroup_hostgroup_relation chr WHERE chr.contactgroup_cg_id = cg.cg_id LIMIT 1
                ) OR EXISTS (
                    SELECT 1 FROM contactgroup_servicegroup_relation csr WHERE csr.contactgroup_cg_id = cg.cg_id LIMIT 1
                ) OR EXISTS (
                    SELECT 1 FROM escalation_contactgroup_relation ecr WHERE ecr.contactgroup_cg_id = cg.cg_id LIMIT 1
                )
            )
            ORDER BY cg.ar_id"
        );

        $currentLdapId = 0; // the chosen LDAP configuration which should never stay to 0 if the LDAP is found
        $ldapConn = null;
        while ($cgRow = $cgRes->fetch()) {
            if (isset($ldapServerConnError[$cgRow['ar_id']])) {
                continue;
            }
            // if $currentLdapId == cgRow['ar_id'], then nothing has changed and we can skip the next operations
            if ($currentLdapId != $cgRow['ar_id']) {
                $currentLdapId = $cgRow['ar_id'];
                if (! is_null($ldapConn)) {
                    $ldapConn->close();
                }
                $ldapConn = new CentreonLDAP($this->db, null, (int) $cgRow['ar_id']);
                $connectionResult = $ldapConn->connect();
                if ($connectionResult == false) {
                    $ldapServerConnError[$cgRow['ar_id']] = 1;
                    $msg[] = 'Unable to connect to LDAP server : ' . $cgRow['ar_name'] . '.';
                    continue;
                }
            }

            // Refresh Users Groups by deleting old relations and inserting new ones if needed.
            $this->db->query('DELETE FROM contactgroup_contact_relation '
                . 'WHERE contactgroup_cg_id = ' . (int) $cgRow['cg_id']);

            $members = $ldapConn->listUserForGroup($cgRow['cg_ldap_dn']);
            $contact = '';
            foreach ($members as $member) {
                $contact .= $this->db->quote($member) . ',';
            }
            $contact = rtrim($contact, ',');

            if (! $contact) {
                // no need to continue. If there's no contact, there's no relation to insert.
                $msg[] = "Error : there's no contact to update for LDAP : " . $cgRow['ar_name'] . '.';

                return $msg;
            }
            try {
                $resContact = $this->db->query('SELECT contact_id FROM contact '
                    . 'WHERE contact_ldap_dn IN (' . $contact . ')');

                while ($rowContact = $resContact->fetch()) {
                    try {
                        // inserting the LDAP contactgroups relation between the cg and the user
                        $this->db->query('INSERT INTO contactgroup_contact_relation '
                            . '(contactgroup_cg_id, contact_contact_id) '
                            . 'VALUES (' . (int) $cgRow['cg_id'] . ', ' . (int) $rowContact['contact_id'] . ')');
                    } catch (PDOException $e) {
                        $stmt = $this->db->query('SELECT c.contact_name, cg_name FROM contact c '
                            . 'INNER JOIN contactgroup_contact_relation cgr ON cgr.contact_contact_id = c.contact_id '
                            . 'INNER JOIN contactgroup cg ON cg.cg_id = cgr.contactgroup_cg_id');
                        $res = $stmt->fetch();
                        $msg[] = 'Error inserting relation between contactgroup : ' . $res['cg_name']
                            . ' and contact : ' . $res['contact_name'] . '.';
                    }
                }
            } catch (PDOException $e) {
                $msg[] = "Error in getting contact ID's list : " . $contact . ' from members.';
                continue;
            }
        }

        return $msg;
    }

    /**
     * Synchronize with LDAP groups
     *
     * @throws PDOException
     * @return array of error messages
     */
    public function syncWithLdap()
    {
        $msg = [];
        $ldapRes = $this->db->query(
            "SELECT ar_id FROM auth_ressource WHERE ar_enable = '1'"
        );

        // Connect to LDAP Server
        while ($ldapRow = $ldapRes->fetch()) {
            $ldapConn = new CentreonLDAP($this->db, null, $ldapRow['ar_id']);
            $connectionResult = $ldapConn->connect();
            if ($connectionResult != false) {
                $res = $this->db->prepare(
                    'SELECT cg_id, cg_name, cg_ldap_dn FROM contactgroup '
                    . "WHERE cg_type = 'ldap' AND ar_id = :arId"
                );
                $res->bindValue(':arId', $ldapRow['ar_id'], PDO::PARAM_INT);
                $res->execute();

                // insert groups from ldap into centreon
                $registeredGroupsFromDB = $res->fetchAll();
                $registeredGroups = [];
                foreach ($registeredGroupsFromDB as $registeredGroupFromDB) {
                    $registeredGroups[] = $registeredGroupFromDB['cg_name'];
                }

                $ldapGroups = $ldapConn->listOfGroups();

                foreach ($ldapGroups as $ldapGroup) {
                    if (! in_array($ldapGroup['name'], $registeredGroups)) {
                        $this->insertLdapGroupByNameAndDn(
                            (int) $ldapRow['ar_id'],
                            $ldapGroup['name'],
                            $ldapGroup['dn']
                        );
                    }
                }

                $res = $this->db->prepare(
                    'SELECT cg_id, cg_name, cg_ldap_dn FROM contactgroup '
                    . "WHERE cg_type = 'ldap' AND ar_id = :arId"
                );
                $res->bindValue(':arId', $ldapRow['ar_id'], PDO::PARAM_INT);
                $res->execute();

                $this->db->beginTransaction();
                try {
                    while ($row = $res->fetch()) {
                        // Test is the group has not been moved or deleted in ldap
                        if ((empty($row['cg_ldap_dn']) || $ldapConn->getEntry($row['cg_ldap_dn']) === false)
                            && ldap_errno($ldapConn->getDs()) != 3
                        ) {
                            $dn = $ldapConn->findGroupDn($row['cg_name']);
                            if ($dn === false && ldap_errno($ldapConn->getDs()) != 3) {
                                // Delete the ldap group in contactgroup
                                try {
                                    $stmt = $this->db->prepare(
                                        'DELETE FROM contactgroup WHERE cg_id = :cgId'
                                    );
                                    $stmt->bindValue('cgId', $row['cg_id'], PDO::PARAM_INT);
                                    $stmt->execute();
                                } catch (PDOException $e) {
                                    $msg[] = 'Error processing delete contactgroup request of ldap group : '
                                        . $row['cg_name'];

                                    throw $e;
                                }
                                continue;
                            }   // Update the ldap group dn in contactgroup
                            try {
                                $updateDnStatement = $this->db->prepare(
                                    'UPDATE contactgroup SET cg_ldap_dn = :cg_dn WHERE cg_id = :cg_id'
                                );
                                $updateDnStatement->bindValue(':cg_dn', $dn, PDO::PARAM_STR);
                                $updateDnStatement->bindValue(':cg_id', $row['cg_id'], PDO::PARAM_INT);
                                $updateDnStatement->execute();
                                $row['cg_ldap_dn'] = $dn;
                            } catch (PDOException $e) {
                                $msg[] = 'Error processing update contactgroup request of ldap group : '
                                    . $row['cg_name'];

                                throw $e;
                            }

                        }
                        $members = $ldapConn->listUserForGroup($row['cg_ldap_dn']);

                        // Refresh Users Groups.
                        $deleteStmt = $this->db->prepare(
                            'DELETE FROM contactgroup_contact_relation
                            WHERE contactgroup_cg_id = :cgId'
                        );
                        $deleteStmt->bindValue(':cgId', $row['cg_id'], PDO::PARAM_INT);
                        $deleteStmt->execute();
                        $contactDns = '';
                        foreach ($members as $member) {
                            $contactDns .= $this->db->quote($member) . ',';
                        }
                        $contactDns = rtrim($contactDns, ',');

                        if ($contactDns !== '') {
                            try {
                                $resContact = $this->db->query(
                                    'SELECT contact_id FROM contact WHERE contact_ldap_dn IN (' . $contactDns . ')'
                                );
                            } catch (PDOException $e) {
                                $msg[] = 'Error in getting contact id from members.';

                                throw $e;
                            }
                            while ($rowContact = $resContact->fetch()) {
                                try {
                                    $insertStmt = $this->db->prepare(
                                        'INSERT INTO contactgroup_contact_relation
                                        (contactgroup_cg_id, contact_contact_id)
                                        VALUES (:cgId, :contactId)'
                                    );
                                    $insertStmt->bindValue(':cgId', $row['cg_id'], PDO::PARAM_INT);
                                    $insertStmt->bindValue(':contactId', $rowContact['contact_id'], PDO::PARAM_INT);
                                    $insertStmt->execute();
                                } catch (PDOException $e) {
                                    $msg[] = 'Error insert relation between contactgroup ' . $row['cg_id']
                                        . ' and contact ' . $rowContact['contact_id'];

                                    throw $e;
                                }
                            }
                        }
                    }
                    $updateTime = $this->db->prepare(
                        "UPDATE `options` SET `value` = :currentTime
                        WHERE `key` = 'ldap_last_acl_update'"
                    );
                    $updateTime->bindValue(':currentTime', time(), PDO::PARAM_INT);
                    $updateTime->execute();
                    $this->db->commit();
                } catch (PDOException $e) {
                    $this->db->rollBack();
                }
            } else {
                $msg[] = 'Unable to connect to LDAP server.';
            }
        }

        return $msg;
    }

    /**
     * Get contact group name from contact group id
     *
     * @param int $cgId : The id of the contactgroup
     * @throws Exception
     * @return string
     */
    public function getNameFromCgId($cgId)
    {
        $query = 'SELECT cg_name FROM contactgroup WHERE cg_id = ' . CentreonDB::escape($cgId) . ' LIMIT 1';
        $res = $this->db->query($query);
        if ($res->rowCount()) {
            $row = $res->fetch();

            return $row['cg_name'];
        }

        throw new Exception('No contact group name found');
    }

    /**
     * Verify if ldap contactgroup as not the same name of a Centreon contactgroup
     *
     * @param array $listCgs The list of contactgroups to validate
     * @return bool
     */
    public static function verifiedExists($listCgs)
    {
        global $pearDB;
        foreach ($listCgs as $cg) {
            if (is_numeric($cg) === false) {
                // Parse the name
                if (preg_match('/\[(\d+)\](.*)/', $cg, $matches) === false) {
                    return false;
                }
                $cg_name = $matches[2];

                // Query test if exists
                $query = 'SELECT COUNT(*) as nb FROM contactgroup '
                    . "WHERE cg_name = '" . $pearDB->escape($cg_name) . "' AND cg_type != 'ldap' ";
                try {
                    $res = $pearDB->query($query);
                } catch (PDOException $e) {
                    return false;
                }
                $row = $res->fetch();
                if ($row['nb'] != 0) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * @param int $field
     * @return array
     */
    public static function getDefaultValuesParameters($field)
    {
        $parameters = [];
        $parameters['currentObject']['table'] = 'contactgroup';
        $parameters['currentObject']['id'] = 'cg_id';
        $parameters['currentObject']['name'] = 'cg_name';
        $parameters['currentObject']['comparator'] = 'cg_id';

        switch ($field) {
            case 'cg_contacts':
                $parameters['type'] = 'relation';
                $parameters['externalObject']['table'] = 'contact';
                $parameters['externalObject']['id'] = 'contact_id';
                $parameters['externalObject']['name'] = 'contact_name';
                $parameters['externalObject']['comparator'] = 'contact_id';
                $parameters['relationObject']['table'] = 'contactgroup_contact_relation';
                $parameters['relationObject']['field'] = 'contact_contact_id';
                $parameters['relationObject']['comparator'] = 'contactgroup_cg_id';
                break;
            case 'cg_acl_groups':
                $parameters['type'] = 'relation';
                $parameters['externalObject']['table'] = 'acl_groups';
                $parameters['externalObject']['id'] = 'acl_group_id';
                $parameters['externalObject']['name'] = 'acl_group_name';
                $parameters['externalObject']['comparator'] = 'acl_group_id';
                $parameters['relationObject']['table'] = 'acl_group_contactgroups_relations';
                $parameters['relationObject']['field'] = 'acl_group_id';
                $parameters['relationObject']['comparator'] = 'cg_cg_id';
                break;
        }

        return $parameters;
    }

    /**
     * @param array $values
     * @param array $options
     *
     * @throws PDOException
     * @return array
     */
    public function getObjectForSelect2($values = [], $options = [])
    {
        global $centreon;
        $items = [];

        // get list of authorized contactgroups
        if (! $centreon->user->access->admin) {
            $cgAcl = $centreon->user->access->getContactGroupAclConf(
                ['fields' => ['cg_id'], 'get_row' => 'cg_id', 'keys' => ['cg_id'], 'conditions' => ['cg_id' => ['IN', $values]]],
                false
            );
        }

        $aElement = [];
        if (is_array($values)) {
            foreach ($values as $value) {
                if (preg_match_all('/\[(\w+)\]/', $value, $matches, PREG_SET_ORDER)) {
                    foreach ($matches as $match) {
                        if (! in_array($match[1], $aElement)) {
                            $aElement[] = $match[1];
                        }
                    }
                } elseif (! in_array($value, $aElement)) {
                    $aElement[] = $value;
                }
            }
        }

        $listValues = '';
        $queryValues = [];
        if ($aElement !== []) {
            foreach ($aElement as $k => $v) {
                $listValues .= ':cg' . $v . ',';
                $queryValues['cg' . $v] = (int) $v;
            }
            $listValues = rtrim($listValues, ',');
        } else {
            $listValues .= '""';
        }

        // get list of selected contactgroups
        $query = 'SELECT cg.cg_id, cg.cg_name, cg.cg_ldap_dn, ar.ar_id, ar.ar_name FROM contactgroup cg '
            . 'LEFT JOIN auth_ressource ar ON cg.ar_id = ar.ar_id '
            . 'WHERE cg.cg_id IN (' . $listValues . ') ORDER BY cg.cg_name ';

        $stmt = $this->db->prepare($query);
        foreach ($queryValues as $key => $id) {
            $stmt->bindValue(':' . $key, $id, PDO::PARAM_INT);
        }
        $stmt->execute();

        while ($row = $stmt->fetch()) {
            if (isset($row['cg_ldap_dn']) && $row['cg_ldap_dn'] != '') {
                $cgName = $this->formatLdapContactgroupName($row['cg_name'], $row['ar_name']);
            } else {
                $cgName = $row['cg_name'];
            }
            $cgId = $row['cg_id'];

            // hide unauthorized contactgroups
            $hide = false;
            if (! $centreon->user->access->admin && ! in_array($cgId, $cgAcl)) {
                $hide = true;
            }

            $items[] = ['id' => $cgId, 'text' => $cgName, 'hide' => $hide];
        }

        return $items;
    }

    /**
     * find LDAP group id by name
     *
     * @param int $ldapId
     * @param string $name
     *
     * @throws PDOException
     * @return int|null
     */
    private function findLdapGroupIdByName(int $ldapId, string $name): ?int
    {
        $ldapGroupId = null;

        $query = 'SELECT cg_id '
            . 'FROM contactgroup '
            . 'WHERE cg_name = :name '
            . 'AND ar_id = :ldapId';
        $statement = $this->db->prepare($query);
        $statement->bindValue(':name', $name, PDO::PARAM_STR);
        $statement->bindValue(':ldapId', $ldapId, PDO::PARAM_INT);
        $statement->execute();

        if ($row = $statement->fetch()) {
            $ldapGroupId = (int) $row['cg_id'];
        }

        return $ldapGroupId;
    }

    /**
     * find LDAP group id by dn
     *
     * @param int $ldapId
     * @param string $dn
     *
     * @throws PDOException
     * @return int|null
     */
    private function findLdapGroupIdByDn(int $ldapId, string $dn): ?int
    {
        $ldapGroupId = null;

        $query = 'SELECT cg_id '
            . 'FROM contactgroup '
            . 'WHERE cg_ldap_dn = :dn '
            . 'AND ar_id = :ldapId';
        $statement = $this->db->prepare($query);
        $statement->bindValue(':dn', $dn, PDO::PARAM_STR);
        $statement->bindValue(':ldapId', $ldapId, PDO::PARAM_INT);
        $statement->execute();

        if ($row = $statement->fetch()) {
            $ldapGroupId = (int) $row['cg_id'];
        }

        return $ldapGroupId;
    }
}
