<?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 __DIR__ . '/centreonDuration.class.php';
require_once __DIR__ . '/centreonGMT.class.php';
require_once __DIR__ . '/centreonACL.class.php';
require_once __DIR__ . '/centreonHost.class.php';
require_once __DIR__ . '/centreonService.class.php';
require_once __DIR__ . '/centreonSession.class.php';
require_once __DIR__ . '/../include/common/common-Func.php';

/**
 * Class
 *
 * @class MetricUtils
 * @description Singleton Class for topological sorting
 */
class MetricUtils
{
    /** @var MetricUtils|null */
    private static $instance = null;

    /**
     * MetricUtils constructor
     */
    private function __construct()
    {
    }

    /**
     * Singleton create method
     *
     * @return MetricUtils
     */
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new MetricUtils();
        }

        return self::$instance;
    }

    /**
     * Function to do a topological sort on a directed acyclic graph
     *
     * @param mixed $data nodes listing
     * @param mixed $dependency nodes link between them
     *
     * @return mixed nodes listing sorted
     */
    public function topologicalSort($data, $dependency)
    {
        $order = [];
        $preProcessing = [];
        $order = array_diff_key($data, $dependency);
        $data = array_diff_key($data, $order);
        foreach ($data as $i => $v) {
            if (! $this->processTopoSort($i, $dependency, $order, $preProcessing)) {
                return false;
            }
        }

        return $order;
    }

    /**
     * Process Topological Sort
     *
     * @param int $pointer
     * @param mixed $dependency
     * @param mixed $order
     * @param mixed $preProcessing
     *
     * @return bool
     */
    private function processTopoSort($pointer, &$dependency, &$order, &$preProcessing)
    {
        if (isset($preProcessing[$pointer])) {
            return false;
        }
        $preProcessing[$pointer] = $pointer;

        foreach ($dependency[$pointer] as $i => $v) {
            if (isset($dependency[$v])) {
                if (! $this->processTopoSort($v, $dependency, $order, $preProcessing)) {
                    return false;
                }
            }
            $order[$v] = $v;
            unset($preProcessing[$v]);
        }
        $order[$pointer] = $pointer;
        unset($preProcessing[$pointer]);

        return true;
    }
}

/**
 * Class
 *
 * @class CentreonGraphNg
 */
class CentreonGraphNg
{
    /** @var array */
    public $listMetricsId = [];

    /** @var array */
    public $vmetrics = [];

    /** @var false */
    public $multipleServices = false;

    /** @var */
    protected $db;

    /** @var */
    protected $dbCs;

    /** @var MetricUtils|null */
    protected $metricUtils;

    /** @var */
    protected $rrdOptions;

    /** @var array */
    protected $arguments = [];

    /** @var */
    protected $debug;

    /** @var int */
    protected $userId;

    /** @var */
    protected $generalOpt;

    /** @var mixed */
    protected $dbPath;

    /** @var mixed */
    protected $dbStatusPath;

    /** @var null[] */
    protected $indexData = [
        'host_id' => null,
        'host_name' => null,
        'service_id' => null,
        'service_description' => null,
    ];

    /** @var */
    protected $templateId;

    /** @var array */
    protected $templateInformations = [];

    /** @var array */
    protected $metrics = [];

    /** @var array */
    protected $indexIds = [];

    /** @var null */
    protected $dsDefault = null;

    /** @var null */
    protected $colorCache = null;

    /** @var null */
    protected $componentsDsCache = null;

    /** @var array */
    protected $extraDatas = [];

    /** @var array */
    protected $cacheAllMetrics = [];

    /** @var array */
    protected $vnodes = [];

    /** @var array */
    protected $vnodesDependencies = [];

    /** @var array */
    protected $vmetricsOrder = [];

    /** @var */
    protected $graphData;

    /** @var */
    protected $rrdCachedOptions;

    /**
     * CentreonGraphNg constructor
     *
     * @param int $userId
     */
    public function __construct($userId)
    {
        $this->initDatabase();
        $this->metricUtils = MetricUtils::getInstance();
        $this->userId = $userId;

        $stmt = $this->dbCs->prepare('SELECT RRDdatabase_path, RRDdatabase_status_path FROM config');
        $stmt->execute();
        $config = $stmt->fetch(PDO::FETCH_ASSOC);
        $this->dbPath = $config['RRDdatabase_path'];
        $this->dbStatusPath = $config['RRDdatabase_status_path'];

        $stmt = $this->db->prepare('SELECT `key`, `value` FROM options');
        $stmt->execute();
        $this->generalOpt = $stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC);

        // Get RRDCacheD options
        $stmt = $this->db->query(
            "SELECT config_key, config_value
            FROM cfg_centreonbroker_info AS cbi
            INNER JOIN cfg_centreonbroker AS cb ON (cb.config_id = cbi.config_id)
            INNER JOIN nagios_server AS ns ON (ns.id = cb.ns_nagios_server)
            WHERE ns.localhost = '1'
            AND cbi.config_key IN ('rrd_cached_option', 'rrd_cached')"
        );

        while ($row = $stmt->fetch()) {
            $this->rrdCachedOptions[$row['config_key']] = $row['config_value'];
        }
    }

    /**
     * Set if a graph has multiple services
     *
     * @param int $multiple set multiple value
     *
     * @return void
     */
    public function setMultipleServices($multiple): void
    {
        $this->multipleServices = $multiple;
    }

    /**
     * Get graph result
     *
     * @param int $start unix timestamp start date
     * @param int $end unix timestamp end date
     *
     * @return array graph result
     */
    public function getGraph($start, $end)
    {
        /**
         * For the title and also get the graph template
         * With multiple index_id, we get the last
         * Need to think about it
         */
        $this->getIndexData();
        $this->extraDatas['start'] = $start;
        $this->extraDatas['end'] = $end;
        $this->setRRDOption('start', $start);
        $this->setRRDOption('end', $end);
        $this->setTemplate();
        $this->init();
        $this->initCurveList();
        $this->createLegend();

        return $this->getJsonStream();
    }

    /**
     * Initiate the Graph objects
     *
     * @return void
     */
    public function init(): void
    {
        $this->setRRDOption('imgformat', 'JSONTIME');
        if (isset($this->templateInformations['vertical_label'])) {
            $this->extraDatas['vertical-label'] = $this->templateInformations['vertical_label'];
        }

        $this->setRRDOption('slope-mode');

        if (isset($this->templateInformations['base']) && $this->templateInformations['base']) {
            $this->extraDatas['base'] = $this->templateInformations['base'];
        }
        if (isset($this->templateInformations['width']) && $this->templateInformations['width']) {
            $this->extraDatas['width'] = $this->templateInformations['width'];
            $this->setRRDOption('width', $this->templateInformations['width']);
        }
        if (isset($this->templateInformations['height']) && $this->templateInformations['height']) {
            $this->extraDatas['height'] = $this->templateInformations['height'];
            $this->setRRDOption('height', $this->templateInformations['height']);
        }

        if (isset($this->templateInformations['lower_limit']) && $this->templateInformations['lower_limit'] != null) {
            $this->extraDatas['lower-limit'] = $this->templateInformations['lower_limit'];
            $this->setRRDOption('lower-limit', $this->templateInformations['lower_limit']);
        }
        if (isset($this->templateInformations['upper_limit']) && $this->templateInformations['upper_limit'] != '') {
            $this->extraDatas['upper-limit'] = $this->templateInformations['upper_limit'];
            $this->setRRDOption('upper-limit', $this->templateInformations['upper_limit']);
        } elseif (isset($this->templateInformations['']) && $this->templateInformations['size_to_max']) {
            $this->extraDatas['size-to-max'] = $this->templateInformations['size_to_max'];
        }

        $this->extraDatas['scaled'] = 1;
        if (isset($this->templateInformations['scaled']) && $this->templateInformations['scaled'] == '0') {
            $this->extraDatas['scaled'] = 0;
        }
    }

    /**
     * Add metrics for a service
     *
     * @param int $hostId
     * @param int $serviceId
     *
     * @return void
     */
    public function addServiceMetrics($hostId, $serviceId): void
    {
        $indexId = null;
        $stmt = $this->dbCs->prepare(
            "SELECT
                m.index_id, host_id, service_id, metric_id, metric_name,
                unit_name, min, max, warn, warn_low, crit, crit_low
            FROM metrics AS m, index_data AS i
            WHERE i.host_id = :host_id
                AND i.service_id = :service_id
                AND i.id = m.index_id
                AND m.hidden = '0'"
        );
        $stmt->bindParam(':host_id', $hostId, PDO::PARAM_INT);
        $stmt->bindParam(':service_id', $serviceId, PDO::PARAM_INT);
        $stmt->execute();
        $metrics = $stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach ($metrics as $metric) {
            $indexId = $metric['index_id'];
            $this->addIndexId($metric['index_id']);
            $this->addRealMetric($metric);
        }

        $stmt = $this->db->prepare(
            "SELECT *
             FROM virtual_metrics
             WHERE index_id = :index_id
             AND vmetric_activate = '1'"
        );
        $stmt->bindParam(':index_id', $indexId, PDO::PARAM_INT);
        $stmt->execute();
        $vmetrics = $stmt->fetchAll(PDO::FETCH_ASSOC);

        foreach ($vmetrics as $vmetric) {
            $this->addVirtualMetric($vmetric);
        }
    }

    /**
     * Add metrics for a service
     *
     * @param int $hostId
     * @param int $serviceId
     * @param mixed $metricsSelected
     *
     * @return void
     */
    public function addServiceCustomMetrics($hostId, $serviceId, $metricsSelected): void
    {
        $indexId = null;
        $stmt = $this->dbCs->prepare(
            "SELECT
                m.index_id, host_id, service_id, metric_id, metric_name,
                unit_name, min, max, warn, warn_low, crit, crit_low
            FROM metrics AS m, index_data AS i
            WHERE i.host_id = :host_id
                AND i.service_id = :service_id
                AND i.id = m.index_id
                AND m.hidden = '0'"
        );
        $stmt->bindParam(':host_id', $hostId, PDO::PARAM_INT);
        $stmt->bindParam(':service_id', $serviceId, PDO::PARAM_INT);
        $stmt->execute();
        $metrics = $stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach ($metrics as $metric) {
            $indexId = $metric['index_id'];
            $this->addIndexId($metric['index_id']);
            if (isset($metricsSelected[$metric['metric_id']])) {
                $this->addRealMetric($metric);
            } else {
                // this metric will be hidden
                $this->addRealMetric($metric, 1);
            }
        }

        $stmt = $this->db->prepare(
            "SELECT *
             FROM virtual_metrics
             WHERE index_id = :index_id
             AND vmetric_activate = '1'"
        );
        $stmt->bindParam(':index_id', $indexId, PDO::PARAM_INT);
        $stmt->execute();
        $vmetrics = $stmt->fetchAll(PDO::FETCH_ASSOC);

        foreach ($vmetrics as $vmetric) {
            $this->addVirtualMetric($vmetric);
        }
    }

    /**
     * Add a metric
     *
     * @param int $metricId
     * @param int $isVirtual
     *
     * @return void
     */
    public function addMetric($metricId, $isVirtual = 0): void
    {
        if ($isVirtual == 0) {
            $stmt = $this->dbCs->prepare(
                "SELECT m.index_id, host_id, service_id, metric_id, metric_name,
                unit_name, min, max, warn, warn_low, crit, crit_low
                FROM metrics AS m, index_data AS i
                WHERE m.metric_id = :metric_id
                AND m.hidden = '0'
                AND m.index_id = i.id"
            );
            $stmt->bindParam(':metric_id', $metricId, PDO::PARAM_INT);
            $stmt->execute();
            $metric = $stmt->fetch(PDO::FETCH_ASSOC);
            if (is_null($metric)) {
                return;
            }

            $this->addIndexId($metric['index_id']);
            $this->addRealMetric($metric);

            return;
        }

        $stmt = $this->db->prepare(
            "SELECT *
             FROM virtual_metrics
             WHERE vmetric_id = :vmetric_id
                AND vmetric_activate = '1'"
        );
        $stmt->bindParam(':vmetric_id', $metricId, PDO::PARAM_INT);
        $stmt->execute();
        $vmetric = $stmt->fetch(PDO::FETCH_ASSOC);
        if (is_null($vmetric)) {
            return;
        }

        $this->addIndexId($vmetric['index_id']);
        $this->addVirtualMetric($vmetric);

        /**
         * Brutal: we get all vmetrics and metrics, with hidden
         */
        $metrics = $this->getRealMetricsByIndexId($vmetric['index_id']);
        foreach ($metrics as $metric) {
            $this->addIndexId($metric['index_id']);
            $this->addRealMetric($metric, 1);
        }

        $vmetrics = $this->getVirtualMetricsByIndexId($vmetric['index_id']);
        foreach ($vmetrics as $vmetric) {
            $this->addVirtualMetric($vmetric);
        }
    }

    /**
     * Create Legend on the graph
     *
     * @return void
     */
    public function createLegend(): void
    {
        foreach ($this->metrics as $metricId => $tm) {
            if ($tm['hidden'] == 1) {
                continue;
            }
            $arg = 'LINE1:v' . $metricId . '#0000ff:v' . $metricId;
            $this->addArgument($arg);
            $this->legendAddPrint($tm, $metricId);
        }

        foreach ($this->vmetricsOrder as $vmetricId) {
            if ($this->vmetrics[$vmetricId]['hidden'] == 1) {
                continue;
            }

            $arg = 'LINE1:vv' . $vmetricId . '#0000ff:vv' . $vmetricId;
            $this->addArgument($arg);
            $this->legendAddPrint($this->vmetrics[$vmetricId], $vmetricId, 1);
        }
    }

    /**
     * Assign graph template
     *
     * @param int $templateId
     *
     * @return void
     */
    public function setTemplate($templateId = null): void
    {
        if ($this->multipleServices) {
            return;
        }

        if (! isset($templateId) || ! $templateId) {
            if ($this->indexData['host_name'] != '_Module_Meta') {
                $this->getDefaultGraphTemplate();
            } else {
                $stmt = $this->db->prepare('SELECT graph_id FROM meta_service WHERE `meta_name` = :meta_name');
                $stmt->bindParam(':meta_name', $this->indexData['service_description'], PDO::PARAM_STR);
                $stmt->execute();
                $meta = $stmt->fetch(PDO::FETCH_ASSOC);
                $this->templateId = $meta['graph_id'];
            }
        } else {
            $this->templateId = htmlentities($_GET['template_id'], ENT_QUOTES, 'UTF-8');
        }

        $stmt = $this->db->prepare('SELECT * FROM giv_graphs_template WHERE graph_id = :graph_id');
        $stmt->bindParam(':graph_id', $this->templateId, PDO::PARAM_INT);
        $stmt->execute();
        $this->templateInformations = $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * Add argument rrdtool
     *
     * @param string $arg
     *
     * @return void
     */
    public function addArgument($arg): void
    {
        $this->arguments[] = $arg;
    }

    /**
     * Add argument rrdtool
     *
     * @param string $name the key
     * @param string $value
     *
     * @return void
     */
    public function setRRDOption($name, $value = null): void
    {
        if ($value !== null && str_contains($value, ' ')) {
            $value = "'" . $value . "'";
        }
        $this->rrdOptions[$name] = $value;
    }

    /**
     * Get rrdtool result
     *
     * @return mixed
     */
    public function getJsonStream()
    {
        $commandLine = '';

        $this->flushRrdcached($this->listMetricsId);

        $commandLine = ' graph - ';

        foreach ($this->rrdOptions as $key => $value) {
            $commandLine .= '--' . $key;
            if (isset($value)) {
                if (preg_match('/\'/', $value)) {
                    $value = "'" . preg_replace('/\'/', ' ', $value) . "'";
                }
                $commandLine .= '=' . $value;
            }
            $commandLine .= ' ';
        }

        foreach ($this->arguments as $arg) {
            $commandLine .= ' ' . $arg . ' ';
        }
        $commandLine = preg_replace('/(\\$|`)/', '', $commandLine);
        $this->log($commandLine);

        if (is_writable($this->generalOpt['debug_path']['value'])) {
            $stderr = ['file', $this->generalOpt['debug_path']['value'] . '/rrdtool.log', 'a'];
        } else {
            $stderr = ['pipe', 'a'];
        }
        $descriptorspec = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => $stderr];

        $process = proc_open(
            $this->generalOpt['rrdtool_path_bin']['value'] . ' - ',
            $descriptorspec,
            $pipes,
            null,
            null
        );
        $this->extraDatas['multiple_services'] = $this->multipleServices;
        $this->graphData = ['global' => $this->extraDatas, 'metrics' => []];
        foreach ($this->metrics as $metric) {
            if ($metric['hidden'] == 1) {
                continue;
            }
            $this->graphData['metrics'][] = $metric;
        }
        foreach ($this->vmetricsOrder as $vmetricId) {
            if ($this->vmetrics[$vmetricId]['hidden'] == 1) {
                continue;
            }

            $this->graphData['metrics'][] = $this->vmetrics[$vmetricId];
        }

        if (is_resource($process)) {
            fwrite($pipes[0], $commandLine);
            fclose($pipes[0]);

            $str = stream_get_contents($pipes[1]);
            $returnValue = proc_close($process);

            $str = preg_replace('/OK u:.*$/', '', $str);
            $rrdData = json_decode($str, true);
        }

        $this->formatByMetrics($rrdData);

        return $this->graphData;
    }

    /**
     * Check argument
     *
     * @param string $name
     * @param mixed $tab
     * @param string $defaultValue
     *
     * @return string
     */
    public function checkArgument($name, $tab, $defaultValue)
    {
        if (isset($name, $tab)) {
            if (isset($tab[$name])) {
                return htmlentities($tab[$name], ENT_QUOTES, 'UTF-8');
            }

            return htmlentities($defaultValue, ENT_QUOTES, 'UTF-8');
        }

        return '';
    }

    /**
     * Get curve color
     *
     * @param int $indexId
     * @param int $metricId
     *
     * @return string
     */
    public function getOVDColor($indexId, $metricId)
    {
        if (is_null($this->colorCache)) {
            $this->colorCache = [];
        }
        if (! isset($this->colorCache[$indexId])) {
            $stmt = $this->db->prepare(
                'SELECT metric_id, rnd_color FROM `ods_view_details` WHERE `index_id` = :index_id'
            );
            $stmt->bindParam(':index_id', $indexId, PDO::PARAM_INT);
            $stmt->execute();
            $this->colorCache[$indexId] = $stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC);
        }

        if (
            isset($this->colorCache[$indexId][$metricId])
            && preg_match("/^\#[a-f0-9]{6,6}/i", $this->colorCache[$indexId][$metricId]['rnd_color'])
        ) {
            return $this->colorCache[$indexId][$metricId]['rnd_color'];
        }
        $lRndcolor = $this->getRandomWebColor();
        $stmt = $this->db->prepare(
            'INSERT INTO `ods_view_details` (rnd_color, index_id, metric_id) VALUES (:rnd_color, :index_id, :metric_id)'
        );
        $stmt->bindParam(':rnd_color', $lRndcolor, PDO::PARAM_STR);
        $stmt->bindParam(':index_id', $indexId, PDO::PARAM_INT);
        $stmt->bindParam(':metric_id', $metricId, PDO::PARAM_INT);
        $stmt->execute();

        return $lRndcolor;
    }

    /**
     * Get random color in a predefined list
     *
     * @return string
     */
    public function getRandomWebColor()
    {
        $webSafeColors = ['#000033', '#000066', '#000099', '#0000cc', '#0000ff', '#003300', '#003333', '#003366', '#003399', '#0033cc', '#0033ff', '#006600', '#006633', '#006666', '#006699', '#0066cc', '#0066ff', '#009900', '#009933', '#009966', '#009999', '#0099cc', '#0099ff', '#00cc00', '#00cc33', '#00cc66', '#00cc99', '#00cccc', '#00ccff', '#00ff00', '#00ff33', '#00ff66', '#00ff99', '#00ffcc', '#00ffff', '#330000', '#330033', '#330066', '#330099', '#3300cc', '#3300ff', '#333300', '#333333', '#333366', '#333399', '#3333cc', '#3333ff', '#336600', '#336633', '#336666', '#336699', '#3366cc', '#3366ff', '#339900', '#339933', '#339966', '#339999', '#3399cc', '#3399ff', '#33cc00', '#33cc33', '#33cc66', '#33cc99', '#33cccc', '#33ccff', '#33ff00', '#33ff33', '#33ff66', '#33ff99', '#33ffcc', '#33ffff', '#660000', '#660033', '#660066', '#660099', '#6600cc', '#6600ff', '#663300', '#663333', '#663366', '#663399', '#6633cc', '#6633ff', '#666600', '#666633', '#666666', '#666699', '#6666cc', '#6666ff', '#669900', '#669933', '#669966', '#669999', '#6699cc', '#6699ff', '#66cc00', '#66cc33', '#66cc66', '#66cc99', '#66cccc', '#66ccff', '#66ff00', '#66ff33', '#66ff66', '#66ff99', '#66ffcc', '#66ffff', '#990000', '#990033', '#990066', '#990099', '#9900cc', '#9900ff', '#993300', '#993333', '#993366', '#993399', '#9933cc', '#9933ff', '#996600', '#996633', '#996666', '#996699', '#9966cc', '#9966ff', '#999900', '#999933', '#999966', '#999999', '#9999cc', '#9999ff', '#99cc00', '#99cc33', '#99cc66', '#99cc99', '#99cccc', '#99ccff', '#99ff00', '#99ff33', '#99ff66', '#99ff99', '#99ffcc', '#99ffff', '#cc0000', '#cc0033', '#cc0066', '#cc0099', '#cc00cc', '#cc00ff', '#cc3300', '#cc3333', '#cc3366', '#cc3399', '#cc33cc', '#cc33ff', '#cc6600', '#cc6633', '#cc6666', '#cc6699', '#cc66cc', '#cc66ff', '#cc9900', '#cc9933', '#cc9966', '#cc9999', '#cc99cc', '#cc99ff', '#cccc00', '#cccc33', '#cccc66', '#cccc99', '#cccccc', '#ccccff', '#ccff00', '#ccff33', '#ccff66', '#ccff99', '#ccffcc', '#ccffff', '#ff0000', '#ff0033', '#ff0066', '#ff0099', '#ff00cc', '#ff00ff', '#ff3300', '#ff3333', '#ff3366', '#ff3399', '#ff33cc', '#ff33ff', '#ff6600', '#ff6633', '#ff6666', '#ff6699', '#ff66cc', '#ff66ff', '#ff9900', '#ff9933', '#ff9966', '#ff9999', '#ff99cc', '#ff99ff', '#ffcc00', '#ffcc33', '#ffcc66', '#ffcc99', '#ffcccc', '#ffccff'];

        return $webSafeColors[rand(0, sizeof($webSafeColors) - 1)];
    }

    /**
     * Returns index data id
     *
     * @param int $hostId
     * @param int $serviceId
     *
     * @return int
     */
    public function getIndexDataId($hostId, $serviceId)
    {
        $stmt = $this->dbCs->prepare(
            'SELECT id FROM index_data WHERE host_id = :host_id AND service_id = :service_id'
        );
        $stmt->bindParam(':host_id', $hostId, PDO::PARAM_INT);
        $stmt->bindParam(':service_id', $serviceId, PDO::PARAM_INT);
        $stmt->execute();
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return is_null($row) ? 0 : $row['id'];
    }

    /**
     * Returns true if status graph exists
     *
     * @param int $hostId
     * @param int $serviceId
     *
     * @return bool
     */
    public function statusGraphExists($hostId, $serviceId)
    {
        $id = $this->getIndexDataId($hostId, $serviceId);

        return (bool) (is_file($this->dbStatusPath . '/' . $id . '.rrd'));
    }

    /**
     * Get Curve Config
     *
     * @param mixed $metric
     *
     * @return mixed curve config
     */
    protected function getCurveDsConfig($metric)
    {
        $dsData = null;

        if (is_null($this->componentsDsCache)) {
            $stmt = $this->db->prepare('SELECT * FROM giv_components_template ORDER BY host_id DESC');
            $stmt->execute();
            $this->componentsDsCache = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }

        $dsDataAssociated = null;
        $dsDataRegular = null;
        foreach ($this->componentsDsCache as $dsVal) {
            $metricPattern = '/^' . str_replace('/', '\/', $dsVal['ds_name']) . '$/i';
            $metricPattern = str_replace('*', '.*', $metricPattern);

            if (
                isset($metric['host_id'], $metric['service_id'])
                && ($dsVal['host_id'] == $metric['host_id'] || $dsVal['host_id'] == '')
                && ($dsVal['service_id'] == $metric['service_id'] || $dsVal['service_id'] == '')
                && preg_match($metricPattern, $metric['metric_name'])
            ) {
                $dsDataAssociated = $dsVal;
                break;
            }

            if (
                is_null($dsDataRegular)
                && preg_match('/^' . preg_quote($dsVal['ds_name'], '/') . '$/i', $metric['metric_name'])
            ) {
                $dsDataRegular = $dsVal;
            }
        }

        if (! is_null($dsDataAssociated)) {
            $dsData = $dsDataAssociated;
        } elseif (! is_null($dsDataRegular)) {
            $dsData = $dsDataRegular;
        }

        if (is_null($dsData)) {
            if (is_null($this->dsDefault)) {
                $stmt = $this->db->prepare(
                    "SELECT ds_min, ds_max, ds_minmax_int, ds_last, ds_average, ds_total,
                        ds_tickness, ds_color_line_mode, ds_color_line, ds_invert
                     FROM giv_components_template WHERE default_tpl1 = '1'"
                );
                $stmt->execute();
                $this->dsDefault = $stmt->fetch(PDO::FETCH_ASSOC);
            }
            $dsData = $this->dsDefault;
        }

        if ($dsData['ds_color_line_mode'] == '1') {
            $dsData['ds_color_line'] = $this->getOVDColor($metric['index_id'], $metric['metric_id']);
        }

        return $dsData;
    }

    /**
     * Clean up ds name in Legend
     *
     * @param string $dsname
     *
     * @return string
     */
    protected function cleanupDsNameForLegend($dsname)
    {
        return str_replace(["'", '\\'], [' ', '\\\\'], $dsname);
    }

    /**
     * Flush metrics in rrdcached
     *
     * @param mixed $metricsId The list of metrics
     *
     * @return bool
     */
    protected function flushRrdcached($metricsId)
    {
        if (
            ! isset($this->rrdCachedOptions['rrd_cached_option'])
            || ! in_array($this->rrdCachedOptions['rrd_cached_option'], ['unix', 'tcp'])
        ) {
            return true;
        }

        $errno = 0;
        $errstr = '';
        if ($this->rrdCachedOptions['rrd_cached_option'] === 'tcp') {
            $sock = fsockopen('127.0.0.1', trim($this->rrdCachedOptions['rrd_cached']), $errno, $errstr);
        } elseif ($this->rrdCachedOptions['rrd_cached_option'] === 'unix') {
            $sock = fsockopen('unix://' . trim($this->rrdCachedOptions['rrd_cached']), $errno, $errstr);
        } else {
            return false;
        }

        if ($sock === false) {
            $this->log('socket connection: ' . $errstr);

            return false;
        }

        if (fputs($sock, "BATCH\n") === false) {
            fclose($sock);

            return false;
        }
        if (fgets($sock) === false) {
            fclose($sock);

            return false;
        }

        foreach ($metricsId as $metricId) {
            $fullpath = realpath($this->dbPath . $metricId . '.rrd');
            $cmd = 'FLUSH ' . $fullpath;
            if (fputs($sock, $cmd . "\n") === false) {
                fclose($sock);

                return false;
            }
        }

        if (fputs($sock, ".\n") === false) {
            fclose($sock);

            return false;
        }
        if (fgets($sock) === false) {
            fclose($sock);

            return false;
        }

        fputs($sock, "QUIT\n");
        fclose($sock);

        return true;
    }

    /**
     * Connect to databases
     *
     * @return void
     */
    private function initDatabase(): void
    {
        $this->db = new CentreonDB(CentreonDB::LABEL_DB_CONFIGURATION);
        $this->dbCs = new CentreonDB(CentreonDB::LABEL_DB_REALTIME);
    }

    /**
     * Get Legend
     *
     * @param mixed $metric
     *
     * @return string
     */
    private function getLegend($metric)
    {
        $legend = '';
        if (isset($metric['ds_data']['ds_legend']) && strlen($metric['ds_data']['ds_legend']) > 0) {
            $legend = str_replace('"', '\"', $metric['ds_data']['ds_legend']);
        } else {
            if (! isset($metric['ds_data']['ds_name']) || ! preg_match('/DS/', $metric['ds_data']['ds_name'], $matches)) {
                $legend = $this->cleanupDsNameForLegend($metric['metric']);
            } else {
                $legend = ($metric['ds_data']['ds_name'] ?? '');
            }
            $legend = str_replace(':', "\:", $legend);
        }

        return $legend;
    }

    /**
     * Manage Virtual Metrics
     *
     * @return int|void
     */
    private function manageMetrics()
    {
        $this->vmetricsOrder = [];

        if (count($this->vmetrics) == 0) {
            return 0;
        }
        foreach ($this->vmetrics as $vmetricId => &$tm) {
            $this->vnodes[$vmetricId] = $vmetricId;

            $rpns = explode(',', $tm['rpn_function']);
            foreach ($rpns as &$rpn) {
                if (isset($this->cacheAllMetrics['r:' . $rpn])) {
                    $rpn = 'v' . $this->cacheAllMetrics['r:' . $rpn];
                } elseif (isset($this->cacheAllMetrics['v:' . $rpn])) {
                    $vmetricIdChild = $this->cacheAllMetrics['v:' . $rpn];
                    $this->vnodesDependencies[$vmetricId][] = $vmetricIdChild;
                    $rpn = 'vv' . $vmetricIdChild;
                }
            }

            $tm['rpn_function'] = implode(',', $rpns);
        }

        $this->vmetricsOrder = $this->metricUtils->topologicalSort($this->vnodes, $this->vnodesDependencies);
    }

    /**
     * Add a regular metric (not virtual)
     *
     * @param mixed $metric
     * @param int $hidden
     *
     * @return void
     */
    private function addRealMetric($metric, $hidden = null): void
    {
        if (! $this->CheckDBAvailability($metric['metric_id'])) {
            return;
        }
        if (isset($this->metrics[$metric['metric_id']])) {
            return;
        }

        $this->log('found metric ' . $metric['metric_id']);

        /**
         * List of id metrics for rrdcached
         */
        $this->listMetricsId[] = $metric['metric_id'];

        $this->metrics[$metric['metric_id']] = ['index_id' => $metric['index_id'], 'metric_id' => $metric['metric_id'], 'metric' => $metric['metric_name'], 'metric_legend' => $this->cleanupDsNameForLegend($metric['metric_name']), 'unit' => $metric['unit_name'], 'hidden' => 0, 'min' => $metric['min'], 'max' => $metric['max'], 'virtual' => 0];

        $this->cacheAllMetrics['r:' . $metric['metric_name']] = $metric['metric_id'];

        $dsData = $this->getCurveDsConfig($metric);
        $this->metrics[$metric['metric_id']]['ds_data'] = $dsData;

        $this->metrics[$metric['metric_id']]['legend'] = $this->getLegend($this->metrics[$metric['metric_id']]);

        $this->metrics[$metric['metric_id']]['stack']
            = (isset($dsData['ds_stack']) && $dsData['ds_stack'] ? $dsData['ds_stack'] : 0);

        $this->metrics[$metric['metric_id']]['warn'] = $metric['warn'];
        $this->metrics[$metric['metric_id']]['warn_low'] = $metric['warn_low'];
        $this->metrics[$metric['metric_id']]['crit'] = $metric['crit'];
        $this->metrics[$metric['metric_id']]['crit_low'] = $metric['crit_low'];
        if (! isset($dsData['ds_color_area_warn']) || empty($dsData['ds_color_area_warn'])) {
            $this->metrics[$metric['metric_id']]['ds_color_area_warn'] = '#ff9a13';
        }
        if (! isset($dsData['ds_color_area_crit']) || empty($dsData['ds_color_area_crit'])) {
            $this->metrics[$metric['metric_id']]['ds_color_area_crit'] = '#e00b3d';
        }

        $this->metrics[$metric['metric_id']]['ds_order']
            = (isset($dsData['ds_order']) && $dsData['ds_order'] ? $dsData['ds_order'] : 0);

        $this->metrics[$metric['metric_id']]['hidden'] = is_null($hidden) ? 0 : $hidden;

        if (isset($dsData['ds_invert']) && $dsData['ds_invert']) {
            if (
                ! is_null($this->metrics[$metric['metric_id']]['min'])
                && is_numeric($this->metrics[$metric['metric_id']]['min'])
            ) {
                $this->metrics[$metric['metric_id']]['min'] = $metric['min'] * -1;
            }
            if (
                ! is_null($this->metrics[$metric['metric_id']]['max'])
                && is_numeric($this->metrics[$metric['metric_id']]['max'])
            ) {
                $this->metrics[$metric['metric_id']]['max'] = $metric['max'] * -1;
            }
        }
    }

    /**
     * Add a virtual metric
     *
     * @param mixed $vmetric
     * @param int $hidden
     *
     * @return void
     */
    private function addVirtualMetric($vmetric, $hidden = null): void
    {
        if (isset($this->vmetrics[$vmetric['vmetric_id']])) {
            return;
        }

        $this->log('found vmetric ' . $vmetric['vmetric_id']);
        $this->vmetrics[$vmetric['vmetric_id']] = ['index_id' => $vmetric['index_id'], 'vmetric_id' => $vmetric['vmetric_id'], 'metric' => $vmetric['vmetric_name'], 'metric_legend' => $vmetric['vmetric_name'], 'unit' => $vmetric['unit_name'], 'hidden' => isset($vmetric['hidden']) && $vmetric['hidden'] == 1 ? 1 : 0, 'warn' => $vmetric['warn'], 'crit' => $vmetric['crit'], 'def_type' => $vmetric['def_type'] == 1 ? 'VDEF' : 'CDEF', 'rpn_function' => $vmetric['rpn_function'], 'virtual' => 1];

        if (! is_null($hidden)) {
            $this->vmetrics[$vmetric['vmetric_id']]['hidden'] = $hidden;
        }

        $this->cacheAllMetrics['v:' . $vmetric['vmetric_name']] = $vmetric['vmetric_id'];

        if ($this->vmetrics[$vmetric['vmetric_id']]['hidden'] == 0) {
            // Not cleaning. Should have its own metric_id for ods_view_details
            $vmetric['metric_name'] = $vmetric['vmetric_name'];
            $vmetric['metric_id'] = $vmetric['vmetric_id'];
            $dsData = $this->getCurveDsConfig($vmetric);
            $this->vmetrics[$vmetric['vmetric_id']]['ds_data'] = $dsData;

            $this->vmetrics[$vmetric['vmetric_id']]['legend']
                = $this->getLegend($this->vmetrics[$vmetric['vmetric_id']]);
            $this->vmetrics[$vmetric['vmetric_id']]['ds_order']
                = (isset($dsData['ds_order']) && $dsData['ds_order'] ? $dsData['ds_order'] : 0);
        }
    }

    /**
     * Initiate rrdtool curve arguments
     *
     * @return void
     */
    private function initCurveList(): void
    {
        uasort($this->metrics, ['CentreonGraphNg', 'cmpmultiple']);

        foreach ($this->metrics as $metricId => &$tm) {
            if (isset($tm['ds_data']['ds_invert']) && $tm['ds_data']['ds_invert']) {
                $this->addArgument('DEF:vi' . $metricId . '=' . $this->dbPath . $metricId . '.rrd:value:AVERAGE CDEF:v'
                    . $metricId . '=vi' . $metricId . ',-1,*');
            } else {
                $this->addArgument('DEF:v' . $metricId . '=' . $this->dbPath . $metricId . '.rrd:value:AVERAGE');
            }
        }

        $this->manageMetrics();

        foreach ($this->vmetricsOrder as $vmetricId) {
            $this->addArgument($this->vmetrics[$vmetricId]['def_type'] . ':vv' . $vmetricId . '='
                . $this->vmetrics[$vmetricId]['rpn_function']);
        }
    }

    /**
     * Switch graph limits
     *
     * @param int $lower
     * @param int $upper
     *
     * @return void
     */
    private function switchRRDLimitOption($lower = null, $upper = null): void
    {
        if (is_null($lower)) {
            unset($this->rrdOptions['upper-limit'], $this->extraDatas['upper-limit']);

        } else {
            $this->rrdOptions['upper-limit'] = $lower;
            $this->extraDatas['upper-limit'] = $lower;
        }
        if (is_null($upper)) {
            unset($this->rrdOptions['lower-limit'], $this->extraDatas['lower-limit']);

        } else {
            $this->rrdOptions['lower-limit'] = $upper;
            $this->extraDatas['lower-limit'] = $upper;
        }
    }

    /**
     * Add rrdtool legends
     *
     * @param mixed $metric
     * @param int $metricId
     * @param int $isVirtual
     *
     * @return void
     */
    private function legendAddPrint($metric, $metricId, $isVirtual = 0): void
    {
        $vdefs = '';
        $prints = '';
        $prefix = 'v';
        if ($isVirtual == 1) {
            $prefix = 'vv';
        }

        foreach (
            ['last' => 'LAST', 'min' => 'MINIMUM', 'max' => 'MAXIMUM', 'average' => 'AVERAGE', 'total' => 'TOTAL'] as $name => $cf
        ) {
            if (! $metric['ds_data']['ds_' . $name]) {
                continue;
            }

            $dispname = ucfirst($name);
            if (isset($metric['ds_data']['ds_invert']) && $metric['ds_data']['ds_invert']) {
                $vdefs .= 'VDEF:' . $prefix . 'i' . $metricId . $dispname . '='
                . $prefix . 'i' . $metricId . ',' . $cf . ' ';
            } else {
                $vdefs .= 'VDEF:' . $prefix . $metricId . $dispname . '='
                    . $prefix . $metricId . ',' . $cf . ' ';
            }
            if (
                ($name == 'min' || $name == 'max')
                && (isset($metric['ds_data']['ds_minmax_int']) && $metric['ds_data']['ds_minmax_int'])
            ) {
                $displayformat = '%.0lf';
            } else {
                $displayformat = '%.2lf';
            }
            if (isset($metric['ds_data']['ds_invert']) && $metric['ds_data']['ds_invert']) {
                $prints .= 'GPRINT:' . $prefix . 'i' . $metricId . $dispname . ':"'
                    . $dispname . "\:" . $displayformat . '" ';
            } else {
                $prints .= 'GPRINT:' . $prefix . $metricId . $dispname . ':"'
                    . $dispname . "\:" . $displayformat . '" ';
            }
        }

        $this->addArgument($vdefs);
        $this->addArgument($prints);
    }

    /**
     * Get template configuration
     *
     * @return void
     */
    private function getDefaultGraphTemplate(): void
    {
        $templateId = $this->getServiceGraphID();
        if (! is_null($templateId) && $templateId != '') {
            $this->templateId = $templateId;

            return;
        }
        $commandId = getMyServiceField($this->indexData['service_id'], 'command_command_id');
        $stmt = $this->db->prepare('SELECT graph_id FROM command WHERE `command_id` = :command_id');
        $stmt->bindParam(':command_id', $commandId, PDO::PARAM_INT);
        $stmt->execute();
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($row !== false && $row['graph_id'] != 0) {
            $this->templateId = $row['graph_id'];

            return;
        }

        $stmt = $this->db->prepare("SELECT graph_id FROM giv_graphs_template WHERE default_tpl1 = '1'");
        $stmt->execute();
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        $this->templateId = $row['graph_id'];
    }

    /**
     * Get graph ID for the service
     *
     * @return void
     */
    private function getServiceGraphID()
    {
        $serviceId = $this->indexData['service_id'];

        $stmt = $this->db->prepare('SELECT
                esi.graph_id, service_template_model_stm_id FROM service
            LEFT JOIN extended_service_information esi
                ON esi.service_service_id = service_id
                WHERE service_id = :service_id');
        $tab = [];
        while (1) {
            $stmt->bindParam(':service_id', $serviceId, PDO::PARAM_INT);
            $stmt->execute();
            if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                if ($row['graph_id']) {
                    return $row['graph_id'];
                }
                if ($row['service_template_model_stm_id']) {
                    if (isset($tab[$row['service_template_model_stm_id']])) {
                        break;
                    }
                    $serviceId = $row['service_template_model_stm_id'];
                    $tab[$serviceId] = 1;
                } else {
                    break;
                }
            } else {
                break;
            }
        }

        return null;
    }

    /**
     * Get index data
     *
     * @return void
     */
    private function getIndexData(): void
    {
        if ($this->multipleServices) {
            return;
        }
        /**
         * We take the first
         */
        $keys = array_keys($this->indexIds);
        $indexId = array_shift($keys);

        $this->log('index_data for ' . $indexId);
        $stmt = $this->dbCs->prepare(
            'SELECT host_id, host_name, service_id, service_description FROM index_data WHERE id = :index_id'
        );
        $stmt->bindParam(':index_id', $indexId, PDO::PARAM_INT);
        $stmt->execute();
        if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $this->indexData = $row;
        }

        if (
            ! empty($this->indexData['service_description'])
            && preg_match('/meta_([0-9]*)/', $this->indexData['service_description'], $matches)
        ) {
            $stmt = $this->db->prepare('SELECT meta_name FROM meta_service WHERE `meta_id` = :meta_id');
            $stmt->bindParam(':meta_id', $matches[1], PDO::PARAM_INT);
            $stmt->execute();
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            $this->indexData['service_description'] = $row['meta_name'];
        }

        if ($this->indexData['host_name'] != '_Module_Meta') {
            $this->extraDatas['host_name'] = $this->indexData['host_name'];
            $this->extraDatas['service_description'] = $this->indexData['service_description'];
            $this->extraDatas['title'] = $this->indexData['service_description'] . ' ' . _('graph on') . ' '
                . $this->indexData['host_name'];
        } else {
            $this->extraDatas['service_description'] = $this->indexData['service_description'];
            $this->extraDatas['host_name'] = '';
            $this->extraDatas['title'] = _('Graph') . ' ' . $this->indexData['service_description'];
        }
    }

    /**
     * Parse rrdtool result
     *
     * @param array $rrdData
     *
     * @return void
     */
    private function formatByMetrics($rrdData): void
    {
        $this->graphData['times'] = [];

        $size = isset($rrdData['data']) && (is_countable($rrdData['data']))
            ? count($rrdData['data'])
            : 0;

        $gprintsSize = isset($rrdData['meta']['gprints'])
            && (is_countable($rrdData['meta']['gprints']))
            ? count($rrdData['meta']['gprints'])
            : 0;

        for ($i = 0; $i < $size; $i++) {
            $this->graphData['times'][] = $rrdData['data'][$i][0];
        }

        $metricIndex = 1;
        $gprintsPos = 0;

        foreach ($this->graphData['metrics'] as &$metric) {
            $metric['data'] = [];
            $metric['prints'] = [];

            $insert = 0;
            $metricFullname = $metric['virtual'] == 0 ? 'v' . $metric['metric_id'] : 'vv' . $metric['vmetric_id'];

            $metric['last_value'] = null;
            $metric['minimum_value'] = null;
            $metric['maximum_value'] = null;
            $metric['average_value'] = null;
            $lastValue = null;
            $minimumValue = null;
            $maximumValue = null;
            $averageValue = null;
            $isCurveInverted = (int) $this->metrics[$metric['metric_id']]['ds_data']['ds_invert'] === 1;

            for (; $gprintsPos < $gprintsSize; $gprintsPos++) {
                if (isset($rrdData['meta']['gprints'][$gprintsPos]['line'])) {
                    if ($rrdData['meta']['gprints'][$gprintsPos]['line'] == $metricFullname) {
                        $insert = 1;
                    } else {
                        break;
                    }
                } elseif ($insert == 1) {
                    $metric['prints'][] = array_values($rrdData['meta']['gprints'][$gprintsPos]);
                    foreach (array_values($rrdData['meta']['gprints'][$gprintsPos]) as $gprintValue) {
                        if (preg_match('/^(.+):(-?(?:\d|\.)+)$/', $gprintValue, $matches)) {
                            [, $valueType, $value] = $matches;
                            switch ($valueType) {
                                case 'Last':
                                    $lastValue = $value;
                                    break;
                                case 'Min':
                                    $minimumValue = $value;
                                    break;
                                case 'Max':
                                    $maximumValue = $value;
                                    break;
                                case 'Average':
                                    $averageValue = $value;
                                    break;
                            }
                        }

                        $metric['last_value'] = (float) $lastValue;

                        if ($isCurveInverted) {
                            // Avoid case when one of the value is NULL. The product by -1 will result in a float 0 (unwanted)
                            $metric['minimum_value'] = $maximumValue !== null ? (float) ($maximumValue * -1) : $maximumValue;
                            $metric['maximum_value'] = $minimumValue !== null ? (float) ($minimumValue * -1) : $minimumValue;
                            $metric['average_value'] = $averageValue !== null ? (float) ($averageValue * -1) : $averageValue;
                        } else {
                            $metric['minimum_value'] = $minimumValue !== null ? (float) $minimumValue : $minimumValue;
                            $metric['maximum_value'] = $maximumValue !== null ? (float) $maximumValue : $maximumValue;
                            $metric['average_value'] = $averageValue !== null ? (float) $averageValue : $averageValue;
                        }
                    }
                }
            }

            for ($dataIndex = 0; $dataIndex < $size; $dataIndex++) {
                $metric['data'][] = $rrdData['data'][$dataIndex][$metricIndex];
            }

            $metricIndex++;
        }
    }

    /**
     * Order method
     *
     * @param mixed $a
     * @param mixed $b
     *
     * @return bool
     */
    private function cmpmultiple($a, $b)
    {
        if (isset($a['ds_order'], $b['ds_order'])) {
            if ($a['ds_order'] < $b['ds_order']) {
                return -1;
            }
            if ($a['ds_order'] > $b['ds_order']) {
                return 1;
            }
        }

        return strnatcasecmp(
            (isset($a['legend']) && $a['legend']) ? $a['legend'] : null,
            (isset($b['legend']) && $b['legend']) ? $b['legend'] : null
        );
    }

    /**
     * Log message
     *
     * @param string $message
     *
     * @return void
     */
    private function log($message): void
    {
        if (
            $this->generalOpt['debug_rrdtool']['value']
            && is_writable($this->generalOpt['debug_path']['value'])
        ) {
            error_log(
                '[' . date('d/m/Y H:i') . '] RDDTOOL : ' . $message . " \n",
                3,
                $this->generalOpt['debug_path']['value'] . 'rrdtool.log'
            );
        }
    }

    /**
     * Check rrd file is present
     *
     * @param int $metricId
     *
     * @return bool
     */
    private function checkDBAvailability($metricId)
    {
        if (! file_exists($this->dbPath . $metricId . '.rrd') && ! preg_match('/^v/', $metricId)) {
            return 0;
        }

        return 1;
    }

    /**
     * Add Index ID
     *
     * @param int $indexId
     *
     * @return void
     */
    private function addIndexId($indexId): void
    {
        if (! isset($this->indexIds[$indexId])) {
            $this->indexIds[$indexId] = 1;
        }
    }

    /**
     * Get regular metrics
     *
     * @param int $indexId
     *
     * @return mixed
     */
    private function getRealMetricsByIndexId($indexId)
    {
        $stmt = $this->dbCs->prepare(
            "SELECT m.index_id, host_id, service_id, metric_id, metric_name,
                unit_name, min, max, warn, warn_low, crit, crit_low
             FROM metrics AS m, index_data AS i
             WHERE i.id = :index_id
                AND i.id = m.index_id
                AND m.hidden = '0'"
        );
        $stmt->bindParam(':index_id', $indexId, PDO::PARAM_INT);
        $stmt->execute();

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * Get virtual metrics
     *
     * @param int $indexId
     *
     * @return mixed
     */
    private function getVirtualMetricsByIndexId($indexId)
    {
        $stmt = $this->db->prepare(
            "SELECT *
             FROM virtual_metrics
             WHERE index_id = :index_id
                AND vmetric_activate = '1'"
        );
        $stmt->bindParam(':index_id', $indexId, PDO::PARAM_INT);
        $stmt->execute();

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}
