#!@PHP_BIN@
<?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__ . '/../config/centreon.config.php');
include_once _CENTREON_PATH_ . '/cron/centAcl-Func.php';
include_once _CENTREON_PATH_ . '/www/class/centreonDB.class.php';
include_once _CENTREON_PATH_ . '/www/class/centreonLDAP.class.php';
include_once _CENTREON_PATH_ . '/www/class/centreonMeta.class.php';
include_once _CENTREON_PATH_ . '/www/class/centreonContactgroup.class.php';
include_once _CENTREON_PATH_ . '/www/class/centreonLog.class.php';

$centreonDbName = $conf_centreon['db'];
$centreonLog = new CentreonLog();

// Define the period between two update in second for LDAP user/contactgroup
define('LDAP_UPDATE_PERIOD', 3600);

/**
 * CentAcl script
 */
$nbProc = exec('ps -o args -p $(pidof -o $$ -o $PPID -o %PPID -x php || echo 1000000) | grep -c ' . __FILE__);
if ((int) $nbProc > 0) {
    programExit('More than one centAcl.php process is currently running. Going to exit...');
}

ini_set('max_execution_time', 0);

try {
    // Init values
    $debug = 0;

    // Init DB connections
    $pearDB = new CentreonDB();
    $pearDBO = new CentreonDB('centstorage');

    $metaObj = new CentreonMeta($pearDB);
    $cgObj = new CentreonContactgroup($pearDB);

    // checking the state of the Cron
    $data = getCentAclRunningState();
    $beginTime = time();

    if (empty($data)) {
        try {
            // at first run (eg: after the install), data may be missing.
            $pearDB->query(
                "INSERT INTO `cron_operation` (`name`, `system`, `activate`) VALUES ('centAcl.php', '1', '1')"
            );
        } catch (PDOException $e) {
            programExit("Error can't insert centAcl values in the `cron_operation` table.");
        }
        $data = getCentAclRunningState();
        $appId = (int) $data['id'] ?? 0;
        $is_running = 0;
    } else {
        $is_running = $data['running'];
        $appId = (int) $data['id'];
    }

    // Lock in MySQL (ie: by setting the `running` value to 1)
    if ($is_running == 0) {
        putALock($appId);
    } else {
        if ($nbProc <= 1) {
            $errorMessage = 'According to DB another instance of centAcl.php is already running and I found '
                . $nbProc . " process...\n";
            $errorMessage .= 'Correcting the state in the DB, by setting the `running` value to 0 for id =  ' . $appId;
            removeLock($appId);
        } else {
            $errorMessage = 'centAcl marked as running. Exiting...';
        }
        programExit($errorMessage);
    }

    /**
     * Sync ACL with LDAP's contactgroup
     * If the LDAP is enabled and the last check is greater than the update period
     *
     * @TODO : Synchronize LDAP with contacts data in background to avoid it at login
     */
    $ldapEnable = '0';
    $ldapLastUpdate = 0;
    $queryOptions = "SELECT `key`, `value` FROM `options` WHERE `key` IN ('ldap_auth_enable', 'ldap_last_acl_update')";
    $res = $pearDB->query($queryOptions);
    while ($row = $res->fetch()) {
        switch ($row['key']) {
            case 'ldap_auth_enable':
                $ldapEnable = $row['value'];
                break;
            case 'ldap_last_acl_update':
                $ldapLastUpdate = $row['value'];
                break;
        }
    }
    if ($ldapEnable === '1' && $ldapLastUpdate < (time() - LDAP_UPDATE_PERIOD)) {
        $cgObj->syncWithLdap();
    }

    /**
     * Check expected contact data sync on login with the LDAP, depending on last sync time and own sync interval
     */
    $pearDB->beginTransaction();
    try {
        $ldapConf = $pearDB->query(
            "SELECT auth.ar_id, auth.ar_sync_base_date, info.ari_value AS `interval`
            FROM auth_ressource auth
            INNER JOIN auth_ressource_info info ON auth.ar_id = info.ar_id
            WHERE auth.ar_enable = '1' AND info.ari_name = 'ldap_sync_interval'"
        );

        $updateSyncTime = $pearDB->prepare(
            'UPDATE auth_ressource SET ar_sync_base_date = :currentTime
            WHERE ar_id = :arId'
        );

        $currentTime = time();
        while ($ldapRow = $ldapConf->fetch()) {
            if ($currentTime > ($ldapRow['ar_sync_base_date'] + 3600 * $ldapRow['interval'])) {
                $updateSyncTime->bindValue(':currentTime', $currentTime, PDO::PARAM_INT);
                $updateSyncTime->bindValue(':arId', (int) $ldapRow['ar_id'], PDO::PARAM_INT);
                $updateSyncTime->execute();
            }
        }
        $pearDB->commit();
    } catch (PDOException $e) {
        $pearDB->rollBack();
        programExit("Error when updating LDAP's reference date for next synchronization");
    }

    /**
     * Remove data from old groups (deleted groups)
     */
    $aclGroupToDelete = 'SELECT DISTINCT acl_group_id
        FROM `' . $centreonDbName . "`.acl_groups WHERE acl_group_activate = '1'";
    $aclGroupToDelete2 = 'SELECT DISTINCT acl_group_id FROM `' . $centreonDbName . '`.acl_res_group_relations';
    $pearDBO->beginTransaction();
    try {
        $pearDBO->query('DELETE FROM centreon_acl WHERE group_id NOT IN (' . $aclGroupToDelete . ')');
        $pearDBO->query('DELETE FROM centreon_acl WHERE group_id NOT IN (' . $aclGroupToDelete2 . ')');
        $pearDBO->commit();
    } catch (PDOException $e) {
        $pearDBO->rollBack();
        $centreonLog->insertLog(
            2,
            'CentACL CRON: failed to delete old groups relations'
        );
    }

    /**
     * Check if some ACL groups have global options selected for
     * contacts or contact groups
     */
    $request = <<<'SQL'
            SELECT
                acl_group_id,
                all_contacts,
                all_contact_groups
            FROM acl_groups
            WHERE acl_group_activate = '1'
                AND (all_contacts != 0 OR all_contact_groups != 0)
        SQL;

    $statement = $pearDB->query($request);

    while ($record = $statement->fetch(PDO::FETCH_ASSOC)) {
        $accessGroupId = $record['acl_group_id'];
        if ($record['all_contacts']) {
            // Find all contacts that are not already linked to the access group
            $contactIds = getContactsNotLinkedToAclGroup($accessGroupId);

            $pearDB->beginTransaction();
            try {
                linkContactsToAccessGroup(
                    accessGroupId: $accessGroupId,
                    contactIds: $contactIds
                );
                $pearDB->commit();
            } catch (Throwable $exception) {
                $pearDB->rollBack();
                $centreonLog->insertLog(
                    2,
                    'CentACL CRON: failed to add new contacts to access group ' . $accessGroupId
                );
            }
        }

        if ($record['all_contact_groups']) {
            // Find all contact groups that are not already linked to the access group
            $contactGroupIds = getContactGroupsNotLinkedToAclGroup($accessGroupId);

            $pearDB->beginTransaction();
            try {
                linkContactGroupsToAccessGroup(
                    accessGroupId: $accessGroupId,
                    contactGroupIds: $contactGroupIds
                );
                $pearDB->commit();
            } catch (Throwable $exception) {
                $pearDB->rollBack();
                $centreonLog->insertLog(
                    2,
                    'CentACL CRON: failed to add new contact groups to access group ' . $accessGroupId
                );
            }

        }
    }

    /**
     * Check if some ACL have global options selected for
     * all the resources
     */
    $res = $pearDB->query(
        "SELECT acl_res_id, all_hosts, all_hostgroups, all_servicegroups
        FROM acl_resources WHERE acl_res_activate = '1'
        AND (all_hosts IS NOT NULL OR all_hostgroups IS NOT NULL OR all_servicegroups IS NOT NULL)"
    );
    while ($row = $res->fetch()) {
        // manage acl_resources.changed flag
        $aclResourcesUpdated = false;

        /**
         * Add Hosts
         */
        if ($row['all_hosts']) {
            $pearDB->beginTransaction();
            try {
                $res1 = $pearDB->prepare(
                    "SELECT host_id FROM host WHERE host_id NOT IN (SELECT DISTINCT host_host_id
                FROM acl_resources_host_relations WHERE acl_res_id = :aclResId)
                AND host_register = '1'"
                );
                $res1->bindValue(':aclResId', $row['acl_res_id'], PDO::PARAM_INT);
                $res1->execute();

                if ($res1->rowCount()) {
                    // set acl_resources.changed flag to 1
                    $aclResourcesUpdated = true;
                }

                while ($rowData = $res1->fetch()) {
                    $stmt = $pearDB->prepare(
                        'INSERT INTO acl_resources_host_relations (host_host_id, acl_res_id)
                    VALUES (:hostId, :aclResId)'
                    );
                    $stmt->bindValue(':hostId', $rowData['host_id'], PDO::PARAM_INT);
                    $stmt->bindValue(':aclResId', $row['acl_res_id'], PDO::PARAM_INT);
                    $stmt->execute();
                }
                $pearDB->commit();
                $res1->closeCursor();
            } catch (PDOException $e) {
                $pearDB->rollBack();
                $centreonLog->insertLog(
                    2,
                    'CentACL CRON: failed to add new host'
                );
            }
        }

        /**
         * Add Hostgroups
         */
        if ($row['all_hostgroups']) {
            $pearDB->beginTransaction();
            try {
                $res1 = $pearDB->prepare(
                    'SELECT hg_id FROM hostgroup
                    WHERE hg_id NOT IN (
                        SELECT DISTINCT hg_hg_id FROM acl_resources_hg_relations
                        WHERE acl_res_id = :aclResId)'
                );
                $res1->bindValue(':aclResId', $row['acl_res_id'], PDO::PARAM_INT);
                $res1->execute();

                if ($res1->rowCount()) {
                    // set acl_resources.changed flag to 1
                    $aclResourcesUpdated = true;
                }

                while ($rowData = $res1->fetch()) {
                    $stmt = $pearDB->prepare(
                        'INSERT INTO acl_resources_hg_relations (hg_hg_id, acl_res_id)
                        VALUES (:hgId, :aclResId)'
                    );
                    $stmt->bindValue(':hgId', $rowData['hg_id'], PDO::PARAM_INT);
                    $stmt->bindValue(':aclResId', $row['acl_res_id'], PDO::PARAM_INT);
                    $stmt->execute();
                }
                $pearDB->commit();
                $res1->closeCursor();
            } catch (PDOException $e) {
                $pearDB->rollBack();
                $centreonLog->insertLog(
                    2,
                    'CentACL CRON: failed to add new hostgroups'
                );
            }
        }

        /**
         * Add Servicesgroups
         */
        $pearDB->beginTransaction();
        try {
            if ($row['all_servicegroups']) {
                $res1 = $pearDB->prepare(
                    'SELECT sg_id FROM servicegroup
                    WHERE sg_id NOT IN (
                        SELECT DISTINCT sg_id FROM acl_resources_sg_relations
                        WHERE acl_res_id = :aclResId)'
                );
                $res1->bindValue(':aclResId', $row['acl_res_id'], PDO::PARAM_INT);
                $res1->execute();

                if ($res1->rowCount()) {
                    // set acl_resources.changed flag to 1
                    $aclResourcesUpdated = true;
                }

                while ($rowData = $res1->fetch()) {
                    $stmt = $pearDB->prepare(
                        'INSERT INTO acl_resources_sg_relations (sg_id, acl_res_id)
                        VALUES (:sgId, :aclResId)'
                    );
                    $stmt->bindValue(':sgId', $rowData['sg_id'], PDO::PARAM_INT);
                    $stmt->bindValue(':aclResId', $row['acl_res_id'], PDO::PARAM_INT);
                    $stmt->execute();
                }
                $res1->closeCursor();
            }

            // as resources has changed we need to save it in the DB
            if ($aclResourcesUpdated) {
                $stmt = $pearDB->prepare(
                    "UPDATE acl_resources SET changed = '1' WHERE acl_res_id = :aclResId"
                );
                $stmt->bindValue(':aclResId', $row['acl_res_id'], PDO::PARAM_INT);
                $stmt->execute();
            }
            $pearDB->commit();
        } catch (PDOException $e) {
            $pearDB->rollBack();
            $centreonLog->insertLog(
                2,
                'CentACL CRON: failed to add new servicegroup'
            );
        }
    }

    /**
     * Check that the ACL resources have changed
     *  if no : go away.
     *  if yes : let's go to build cache and update database
     */
    $tabGroups = [];
    $dbResult1 = $pearDB->query(
        "SELECT DISTINCT acl_groups.acl_group_id
        FROM acl_res_group_relations, `acl_groups`, `acl_resources`
        WHERE acl_groups.acl_group_id = acl_res_group_relations.acl_group_id
            AND acl_res_group_relations.acl_res_id = acl_resources.acl_res_id
            AND acl_groups.acl_group_activate = '1'
            AND (
                acl_groups.acl_group_changed = '1' OR 
                (acl_resources.changed = '1' AND acl_resources.acl_res_activate IS NOT NULL)
            )"
    );
    while ($result = $dbResult1->fetch()) {
        $tabGroups[] = $result['acl_group_id'];
    }
    unset($result);

    if ($tabGroups !== []) {

        /**
         *  Cache for hosts and host Templates
         */
        $hostTemplateCache = [];
        $res = $pearDB->query(
            'SELECT host_host_id, host_tpl_id FROM host_template_relation'
        );
        while ($row = $res->fetch()) {
            if (! isset($hostTemplateCache[$row['host_tpl_id']])) {
                $hostTemplateCache[$row['host_tpl_id']] = [];
            }
            $hostTemplateCache[$row['host_tpl_id']][$row['host_host_id']] = $row['host_host_id'];
        }

        $hostCache = [];
        $dbResult = $pearDB->query(
            "SELECT host_id, host_name FROM host WHERE host_register IN ('1', '2')"
        );
        while ($h = $dbResult->fetch()) {
            $hostCache[$h['host_id']] = $h['host_name'];
        }
        unset($h);

        /**
         * Cache for host poller relation
         */
        $hostPollerCache = [];
        $res = $pearDB->query(
            'SELECT nagios_server_id, host_host_id FROM ns_host_relation'
        );
        while ($row = $res->fetch()) {
            if (! isset($hostPollerCache[$row['nagios_server_id']])) {
                $hostPollerCache[$row['nagios_server_id']] = [];
            }
            $hostPollerCache[$row['nagios_server_id']][$row['host_host_id']] = $row['host_host_id'];
        }

        /**
         * Get all included Hosts
         */
        $hostIncCache = [];
        $dbResult = $pearDB->query(
            'SELECT host_host_id, acl_res_id
            FROM acl_resources_host_relations'
        );
        while ($h = $dbResult->fetch()) {
            if (! isset($hostIncCache[$h['acl_res_id']])) {
                $hostIncCache[$h['acl_res_id']] = [];
            }
            $hostIncCache[$h['acl_res_id']][$h['host_host_id']] = 1;
        }

        /**
         * Get all excluded Hosts
         */
        $hostExclCache = [];
        $dbResult = $pearDB->query(
            'SELECT host_host_id, acl_res_id
            FROM acl_resources_hostex_relations'
        );
        while ($h = $dbResult->fetch()) {
            if (! isset($hostExclCache[$h['acl_res_id']])) {
                $hostExclCache[$h['acl_res_id']] = [];
            }
            $hostExclCache[$h['acl_res_id']][$h['host_host_id']] = 1;
        }

        /**
         * Service Cache
         */
        $svcCache = [];
        $dbResult = $pearDB->query(
            "SELECT service_id FROM `service`
            WHERE service_register = '1'"
        );
        while ($s = $dbResult->fetch()) {
            $svcCache[$s['service_id']] = 1;
        }

        /**
         * Host Host relation
         */
        $hostHGRelation = [];
        $dbResult = $pearDB->query('SELECT * FROM hostgroup_relation');
        while ($hg = $dbResult->fetch()) {
            if (! isset($hostHGRelation[$hg['hostgroup_hg_id']])) {
                $hostHGRelation[$hg['hostgroup_hg_id']] = [];
            }
            $hostHGRelation[$hg['hostgroup_hg_id']][$hg['host_host_id']] = $hg['host_host_id'];
        }
        unset($hg);

        /**
         * Host Service relation
         */
        $hsRelation = [];
        $dbResult = $pearDB->query(
            'SELECT hostgroup_hg_id, host_host_id, service_service_id
            FROM host_service_relation'
        );
        while ($sr = $dbResult->fetch()) {
            if (isset($sr['host_host_id']) && $sr['host_host_id']) {
                if (! isset($hsRelation[$sr['host_host_id']])) {
                    $hsRelation[$sr['host_host_id']] = [];
                }
                $hsRelation[$sr['host_host_id']][$sr['service_service_id']] = 1;
            } elseif (isset($hostHGRelation[$sr['hostgroup_hg_id']])) {
                foreach ($hostHGRelation[$sr['hostgroup_hg_id']] as $hostId) {
                    if (! isset($hsRelation[$hostId])) {
                        $hsRelation[$hostId] = [];
                    }
                    $hsRelation[$hostId][$sr['service_service_id']] = 1;
                }
            }
        }
        $dbResult->closeCursor();

        /**
         * Create Service template model Cache
         */
        $svcTplCache = [];
        $dbResult = $pearDB->query('SELECT service_template_model_stm_id, service_id FROM service');
        while ($tpl = $dbResult->fetch()) {
            $svcTplCache[$tpl['service_id']] = $tpl['service_template_model_stm_id'];
        }
        $dbResult->closeCursor();
        unset($tpl);

        $svcCatCache = [];
        $dbResult = $pearDB->query('SELECT sc_id, service_service_id FROM `service_categories_relation`');
        while ($res = $dbResult->fetch()) {
            if (! isset($svcCatCache[$res['service_service_id']])) {
                $svcCatCache[$res['service_service_id']] = [];
            }
            $svcCatCache[$res['service_service_id']][$res['sc_id']] = 1;
        }
        $dbResult->closeCursor();
        unset($res);

        $sgCache = [];
        $res = $pearDB->query(
            "SELECT argr.`acl_res_id`, acl_group_id
            FROM `acl_res_group_relations` argr, `acl_resources` ar
            WHERE argr.acl_res_id = ar.acl_res_id
            AND ar.acl_res_activate = '1'"
        );
        while ($row = $res->fetch()) {
            $sgCache[$row['acl_res_id']] = [];
        }
        unset($row);

        $res = $pearDB->query(
            'SELECT service_service_id, sgr.host_host_id, acl_res_id
            FROM servicegroup sg, acl_resources_sg_relations acl, servicegroup_relation sgr
            WHERE acl.sg_id = sg.sg_id
            AND sgr.servicegroup_sg_id = sg.sg_id '
        );
        while ($row = $res->fetch()) {
            foreach (array_keys($sgCache) as $rId) {
                if ($rId == $row['acl_res_id']) {
                    if (! isset($sgCache[$rId][$row['host_host_id']])) {
                        $sgCache[$rId][$row['host_host_id']] = [];
                    }
                    $sgCache[$rId][$row['host_host_id']][$row['service_service_id']] = 1;
                }
            }
        }
        unset($row);

        $res = $pearDB->query(
            'SELECT acl_res_id, hg_id
            FROM hostgroup, acl_resources_hg_relations
            WHERE acl_resources_hg_relations.hg_hg_id = hostgroup.hg_id'
        );
        $hgResCache = [];
        while ($row = $res->fetch()) {
            if (! isset($hgResCache[$row['acl_res_id']])) {
                $hgResCache[$row['acl_res_id']] = [];
            }
            $hgResCache[$row['acl_res_id']][] = $row['hg_id'];
        }
        unset($row);

        // Prepare statement
        $deleteHandler = $pearDBO->prepare('DELETE FROM centreon_acl WHERE group_id = ?');

        /**
         * Begin to build ACL
         */
        $cpt = 0;
        $resourceCache = [];
        foreach ($tabGroups as $aclGroupId) {
            // Delete old data for this group
            $deleteHandler->execute([$aclGroupId]);

            /**
             * Select
             */
            $dbResult2 = $pearDB->prepare(
                "SELECT DISTINCT(`acl_resources`.`acl_res_id`)
                FROM `acl_res_group_relations`, `acl_resources`
                WHERE `acl_res_group_relations`.`acl_group_id` = :aclGroupId
                AND `acl_res_group_relations`.acl_res_id = `acl_resources`.acl_res_id
                AND `acl_resources`.acl_res_activate = '1'"
            );
            $dbResult2->bindValue(':aclGroupId', $aclGroupId, PDO::PARAM_INT);
            $dbResult2->execute();
            if ($debug) {
                $time_start = microtime_float2();
            }

            while ($res2 = $dbResult2->fetch()) {
                if (! isset($resourceCache[$res2['acl_res_id']])) {
                    $resourceCache[$res2['acl_res_id']] = [];

                    $host = [];
                    // Get all Hosts
                    if (isset($hostIncCache[$res2['acl_res_id']])) {
                        foreach (array_keys($hostIncCache[$res2['acl_res_id']]) as $hostId) {
                            $host[$hostId] = 1;
                        }
                    }

                    if (isset($hgResCache[$res2['acl_res_id']])) {
                        foreach ($hgResCache[$res2['acl_res_id']] as $hgId) {
                            if (isset($hostHGRelation[$hgId])) {
                                foreach ($hostHGRelation[$hgId] as $hostId) {
                                    if ($hostCache[$hostId]) {
                                        $host[$hostId] = 1;
                                    } else {
                                        echo "Host {$hostId} unknown !\n";
                                    }
                                }
                            }
                        }
                    }

                    if (isset($hostExclCache[$res2['acl_res_id']])) {
                        foreach (array_keys($hostExclCache[$res2['acl_res_id']]) as $hostId) {
                            unset($host[$hostId]);
                        }
                    }

                    // Give Authorized Categories
                    $authorizedCategories = getAuthorizedCategories($res2['acl_res_id']);

                    // get all Service groups
                    $dbResult3 = $pearDB->prepare(
                        'SELECT servicegroup_relation.host_host_id, servicegroup_relation.service_service_id
                        FROM `acl_resources_sg_relations`, `servicegroup_relation`
                        WHERE acl_res_id = :aclResId
                        AND servicegroup_relation.servicegroup_sg_id = acl_resources_sg_relations.sg_id
                        UNION
                        SELECT servicegroup_relation.host_host_id, servicegroup_relation.service_service_id
                        FROM `acl_resources_sg_relations`, `servicegroup_relation`, `hostgroup`, `hostgroup_relation`
                        WHERE acl_res_id = :aclResId
                        AND hostgroup.hg_id = servicegroup_relation.hostgroup_hg_id
                        AND servicegroup_relation.hostgroup_hg_id = hostgroup_relation.hostgroup_hg_id
                        AND servicegroup_relation.servicegroup_sg_id = acl_resources_sg_relations.sg_id'
                    );
                    $dbResult3->bindValue(':aclResId', $res2['acl_res_id'], PDO::PARAM_INT);
                    $dbResult3->execute();

                    $sgElem = [];
                    $tmpH = [];
                    while ($h = $dbResult3->fetch()) {
                        if (! isset($sgElem[$h['host_host_id']])) {
                            $sgElem[$h['host_host_id']] = [];
                            $tmpH[$h['host_host_id']] = 1;
                        }
                        $sgElem[$h['host_host_id']][$h['service_service_id']] = 1;
                    }

                    $tmpH = getFilteredHostCategories($tmpH, $res2['acl_res_id']);
                    $tmpH = getFilteredPollers($tmpH, $res2['acl_res_id']);

                    foreach ($sgElem as $hostId => $value) {
                        if (isset($tmpH[$hostId])) {
                            if (count($authorizedCategories) == 0) { // no category filter
                                $resourceCache[$res2['acl_res_id']][$hostId] = $value;
                            } else {
                                foreach (array_keys($value) as $serviceId) {
                                    $linkedServiceCategories = getServiceTemplateCategoryList($serviceId);
                                    foreach ($linkedServiceCategories as $linkedServiceCategory) {
                                        // Check if category linked to service is allowed
                                        if (in_array($linkedServiceCategory, $authorizedCategories)) {
                                            $resourceCache[$res2['acl_res_id']][$hostId][$serviceId] = 1;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    unset($tmpH, $sgElem);

                    // Filter
                    $host = getFilteredHostCategories($host, $res2['acl_res_id']);
                    $host = getFilteredPollers($host, $res2['acl_res_id']);

                    // Initialize and first filter
                    foreach (array_keys($host) as $hostId) {
                        $tab = getAuthorizedServicesHost($hostId, $res2['acl_res_id'], $authorizedCategories);
                        if (! isset($resourceCache[$res2['acl_res_id']][$hostId])) {
                            $resourceCache[$res2['acl_res_id']][$hostId] = [];
                        }
                        foreach (array_keys($tab) as $serviceId) {
                            $resourceCache[$res2['acl_res_id']][$hostId][$serviceId] = 1;
                        }
                        unset($tab);
                    }
                    unset($host);

                    // Set meta services
                    $metaServices = getMetaServices($res2['acl_res_id'], $pearDB, $metaObj);
                    if (count($metaServices)) {
                        $resourceCache[$res2['acl_res_id']] += $metaServices;
                    }
                }

                $strBegin = 'INSERT INTO centreon_acl (host_id, service_id, group_id) VALUES ';
                $strEnd = ' ON DUPLICATE KEY UPDATE `group_id` = ? ';

                $str = '';
                $params = [];
                $i = 0;
                foreach ($resourceCache[$res2['acl_res_id']] as $hostId => $svcList) {
                    if (isset($hostCache[$hostId])) {
                        if ($str != '') {
                            $str .= ', ';
                        }
                        $str .= ' (?, NULL, ?) ';
                        $params[] = $hostId;
                        $params[] = $aclGroupId;

                        foreach (array_keys($svcList) as $serviceId) {
                            if ($str != '') {
                                $str .= ', ';
                            }

                            $i++;
                            $str .= ' (?, ?, ?) ';
                            $params[] = $hostId;
                            $params[] = $serviceId;
                            $params[] = $aclGroupId;
                            if ($i >= 5000) {
                                $params[] = $aclGroupId; // argument for $strEnd
                                $stmt = $pearDBO->prepare($strBegin . $str . $strEnd);
                                $stmt->execute($params); // inject acl by bulk (1000 relations)
                                $str = '';
                                $params = [];
                                $i = 0;
                            }
                        }
                    }
                }

                // inject remaining acl (bulk of less than 1000 relations)
                if ($str != '') {
                    $params[] = $aclGroupId; // argument for $strEnd
                    $stmt = $pearDBO->prepare($strBegin . $str . $strEnd);
                    $stmt->execute($params);
                    $str = '';
                }

                // reset flags of acl_resources
                $stmt = $pearDB->prepare("UPDATE `acl_resources` SET `changed` = '0' WHERE acl_res_id = :aclResId");
                $stmt->bindValue(':aclResId', $res2['acl_res_id'], PDO::PARAM_INT);
                $stmt->execute();
            }

            if ($debug) {
                $time_end = microtime_float2();
                $now = $time_end - $time_start;
                echo round($now, 3) . ' ' . _('seconds') . "\n";
            }

            $cpt++;

            // reset flags of acl_groups
            $stmt = $pearDB->prepare("UPDATE acl_groups SET acl_group_changed = '0' WHERE acl_group_id = :aclGroupId");
            $stmt->bindValue(':aclGroupId', $aclGroupId, PDO::PARAM_INT);
            $stmt->execute();
        }

        /**
         * Include module specific ACL evaluation
         */
        $extensionsPaths = getModulesExtensionsPaths($pearDB);
        foreach ($extensionsPaths as $extensionPath) {
            require_once $extensionPath . 'centAcl.php';
        }
    }

    /**
     * Remove lock
     */
    $dbResult = $pearDB->prepare(
        "UPDATE cron_operation
        SET running = '0', last_execution_time = :time
        WHERE id = :appId"
    );
    $dbResult->bindValue(':time', (time() - $beginTime), PDO::PARAM_INT);
    $dbResult->bindValue(':appId', $appId, PDO::PARAM_INT);
    $dbResult->execute();

    // Close connection to databases
    $pearDB = null;
    $pearDBO = null;
} catch (Exception $e) {
    programExit($e->getMessage());
}
