Source of file ScenarioManager.php

Size: 26,659 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

/home/travis/build/NextDom/nextdom-core/src/Managers/ScenarioManager.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
<?php
/* This file is part of Jeedom.
 *
 * Jeedom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jeedom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
 */

/* This file is part of NextDom Software.
 *
 * NextDom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NextDom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NextDom. If not, see <http://www.gnu.org/licenses/>.
 */

namespace NextDom\Managers;

use NextDom\Enums\ScenarioState;
use NextDom\Exceptions\CoreException;
use NextDom\Helpers\DBHelper;
use NextDom\Helpers\FileSystemHelper;
use NextDom\Helpers\LogHelper;
use NextDom\Helpers\NextDomHelper;
use NextDom\Helpers\Utils;
use NextDom\Managers\Parents\BaseManager;
use NextDom\Managers\Parents\CommonManager;
use NextDom\Model\Entity\Scenario;
use NextDom\Enums\LogTarget;

/**
 * Class ScenarioManager
 * @package NextDom\Managers
 */
class ScenarioManager extends BaseManager
{
    use CommonManager;

    const DB_CLASS_NAME = '`scenario`';
    const CLASS_NAME = Scenario::class;
    const INITIAL_TRANSLATION_FILE = '';

    /**
     * Get scenario by his name
     *
     * @param string $name Name of the scenario
     *
     * @return Scenario|null Requested scenario or null
     *
     * @throws \Exception
     */
    public static function byName(string $name)
    {
        return static::getOneByClauses(['name' => $name]);
    }

    /**
     * Obtenir un objet scenario
     *
     * @param string $scenarioName Chaine identifiant le scénario
     *
     * @param $commandNotFoundString
     * @return Scenario Objet demandé
     *
     * @throws \ReflectionException
     * @throws CoreException
     */
    public static function byString(string $scenarioName, $commandNotFoundString)
    {
        $scenario = static::byId(str_replace('#scenario', '', self::fromHumanReadable($scenarioName)));
        if (!is_object($scenario)) {
            throw new CoreException($commandNotFoundString . $scenarioName . ' => ' . self::fromHumanReadable($scenarioName));
        }
        return $scenario;
    }

    /**
     * @TODO: Ca fait l'inverse, mais je sais pas quoi
     *
     * @param $input
     * @return array|mixed
     * @throws \ReflectionException
     */
    public static function fromHumanReadable($input)
    {
        $isJson = false;
        if (Utils::isJson($input)) {
            $isJson = true;
            $input = json_decode($input, true);
        }
        if (is_object($input)) {
            $reflections = [];
            $uuid = spl_object_hash($input);
            if (!isset($reflections[$uuid])) {
                $reflections[$uuid] = new \ReflectionClass($input);
            }
            $reflection = $reflections[$uuid];
            $properties = $reflection->getProperties();
            foreach ($properties as $property) {
                $property->setAccessible(true);
                $value = $property->getValue($input);
                $property->setValue($input, self::fromHumanReadable($value));
                $property->setAccessible(false);
            }
            return $input;
        }
        if (is_array($input)) {
            foreach ($input as $key => $value) {
                $input[$key] = self::fromHumanReadable($value);
            }
            if ($isJson) {
                return json_encode($input, JSON_UNESCAPED_UNICODE);
            }
            return $input;
        }
        $text = $input;

        preg_match_all("/#\[(.*?)\]\[(.*?)\]\[(.*?)\]#/", $text, $matches);
        if (count($matches) == 4) {
            $countMatches = count($matches[0]);
            for ($i = 0; $i < $countMatches; $i++) {
                if (isset($matches[1][$i]) && isset($matches[2][$i]) && isset($matches[3][$i])) {
                    $scenario = self::byObjectNameGroupNameScenarioName($matches[1][$i], $matches[2][$i], $matches[3][$i]);
                    if (is_object($scenario)) {
                        $text = str_replace($matches[0][$i], '#scenario' . $scenario->getId() . '#', $text);
                    }
                }
            }
        }

        return $text;
    }

    /**
     * Liste des scénario triés par objet, group et nom du scénario
     *
     * @param $objectName
     * @param $groupName
     * @param $scenarioName
     *
     * @return mixed
     * @throws \Exception
     */
    public static function byObjectNameGroupNameScenarioName($objectName, $groupName, $scenarioName)
    {
        $values = [
            'scenario_name' => html_entity_decode($scenarioName),
        ];

        $sql = static::getPrefixedBaseSQL('s');

        if ($objectName == __('Aucun')) {
            $sql .= 'WHERE s.name = :scenario_name ';
            if ($groupName == __('Aucun')) {
                $sql .= 'AND (`group` IS NULL OR `group` = ""  OR `group` = "Aucun" OR `group` = "None")
                         AND s.object_id IS NULL';
            } else {
                $values['group_name'] = $groupName;
                $sql .= 'AND s.object_id IS NULL
                         AND `group` = :group_name';
            }
        } else {
            $values['object_name'] = $objectName;
            $sql .= 'INNER JOIN object ob ON s.object_id=ob.id
                     WHERE s.name = :scenario_name
                     AND ob.name = :object_name ';
            if ($groupName == __('Aucun')) {
                $sql .= 'AND (`group` IS NULL OR `group` = ""  OR `group` = "Aucun" OR `group` = "None")';
            } else {
                $values['group_name'] = $groupName;
                $sql .= 'AND `group` = :group_name';
            }
        }
        return DBHelper::getOneObject($sql, $values, self::CLASS_NAME);
    }

    /**
     *  Obtenir la liste des groupes de scénarios
     *
     * @param string $groupPattern Pattern de recherche
     *
     * @return array|mixed|null [] Liste des groupes
     * @throws \Exception
     */
    public static function listGroup($groupPattern = null)
    {
        $values = [];
        $sql = 'SELECT DISTINCT(`group`)
        FROM ' . self::DB_CLASS_NAME;
        if ($groupPattern !== null) {
            $values['group'] = '%' . $groupPattern . '%';
            $sql .= ' WHERE `group` LIKE :group';
        }
        $sql .= ' ORDER BY `group`';
        return DBHelper::getAll($sql, $values);
    }

    /**
     * Obtenir la liste des scénarios à partir d'un élément @TODO: Kesako
     *
     * @param string $elementId
     * @return mixed
     * @throws \Exception
     */
    public static function byElement(string $elementId)
    {
        // TODO: Vérifier, bizarre les guillemets dans le like
        return static::searchOneByClauses([
            'scenarioElement' => '%"' . $elementId . '"%',
        ]);
    }

    /**
     * Obtenir un scénario à partir de l'identifiant d'un objet //@TODO: Comprendre ce que c'est
     *
     * @param int $objectId Identifiant de l'objet
     * @param bool $onlyEnabled Filtrer uniquement les scénarios activés
     * @param bool $onlyVisible Filtrer uniquement les scénarios visibles
     *
     * @return array|mixed|null [] Liste des scénarios
     *
     * @throws \Exception
     */
    public static function byObjectId($objectId, $onlyEnabled = true, $onlyVisible = false)
    {
        $values = [];
        $sql = static::getBaseSQL();
        if ($objectId === null) {
            $sql .= ' WHERE object_id IS NULL';
        } else {
            $values['object_id'] = $objectId;
            $sql .= ' WHERE `object_id` = :object_id';
        }
        if ($onlyEnabled) {
            $sql .= ' AND `isActive` = 1';
        }
        if ($onlyVisible) {
            $sql .= ' AND `isVisible` = 1';
        }
        $sql .= ' ORDER BY `order`';
        return DBHelper::getAllObjects($sql, $values, self::CLASS_NAME);
    }

    /**
     * Vérifier un scénario
     * @TODO: Virer les strings
     *
     * @param string $event Evènement déclencheur
     * @param bool $forceSyncMode Forcer le mode synchrone
     *
     * @return bool Renvoie toujours true //@TODO: A voir
     * @throws \Exception
     */
    public static function check($event = null, $forceSyncMode = false)
    {
        $message = '';
        $scenarios = [];
        if ($event !== null) {
            // @TODO: Event ne peut pas être un objet
            if (is_object($event)) {
                $eventScenarios = self::byTrigger($event->getId());
                $trigger = '#' . $event->getId() . '#';
                $message = __('Scénario exécuté automatiquement sur événement venant de : ') . $event->getHumanName();
            } else {
                $eventScenarios = self::byTrigger($event);
                $trigger = $event;
                $message = __('Scénario exécuté sur événement : #') . $event . '#';
            }
            if (is_array($eventScenarios) && count($eventScenarios) > 0) {
                foreach ($eventScenarios as $scenario) {
                    if ($scenario->testTrigger($trigger)) {
                        $scenarios[] = $scenario;
                    }
                }
            }
        } else {
            $message = __('Scénario exécuté automatiquement sur programmation');
            $scheduledScenarios = self::schedule();
            $trigger = 'schedule';
            if (NextDomHelper::isDateOk()) {
                foreach ($scheduledScenarios as $scenario) {
                    if ($scenario->getState() != ScenarioState::IN_PROGRESS && $scenario->isDue()) {
                        $scenarios[] = $scenario;
                    }
                }
            }
        }
        if (count($scenarios) > 0) {
            foreach ($scenarios as $scenario) {
                $scenario->launch($trigger, $message, $forceSyncMode);
            }
        }
        return true;
    }

    /**
     * Obtenir la liste des scénarios en fonction d'un déclencheur
     *
     * @param string $cmdId Identifiant du déclencheur
     * @param bool $onlyEnabled Filtrer sur les scénarios activés
     *
     * @return array|mixed|null [] Liste des scénarios
     * @throws \Exception
     */
    public static function byTrigger($cmdId, $onlyEnabled = true)
    {
        $values = ['cmd_id' => '%#' . $cmdId . '#%'];
        $sql = static::getBaseSQL() . '
                WHERE `mode` != "schedule" AND `trigger` LIKE :cmd_id';
        if ($onlyEnabled) {
            $sql .= ' AND `isActive` = 1';
        }
        return DBHelper::getAllObjects($sql, $values, self::CLASS_NAME);
    }

    /**
     * Obtenir la liste des scénarios planifiés
     *
     * @return array|mixed|null [scenario] Liste des scénarios planifiés
     * @throws \Exception
     */
    public static function schedule()
    {
        $sql = static::getBaseSQL() . '
                WHERE `mode` != "provoke"
                AND `isActive` = 1';
        return DBHelper::getAllObjects($sql, [], self::CLASS_NAME);
    }

    /**
     * Contrôle des scénarios // @TODO: ???
     *
     */
    public static function control()
    {
        foreach (self::all() as $scenario) {
            if ($scenario->getState() != ScenarioState::IN_PROGRESS) {
                continue; // @TODO: To be or not to be
            }
            if (!$scenario->running()) {
                $scenario->setState(ScenarioState::ERROR);
                continue; // @TODO: To be or not to be
            }
            $runtime = strtotime('now') - strtotime($scenario->getLastLaunch());
            // @TODO: Optimisation
            if (is_numeric($scenario->getTimeout()) && $scenario->getTimeout() != '' && $scenario->getTimeout() != 0 && $runtime > $scenario->getTimeout()) {
                $scenario->stop();
                $scenario->setLog(__('Arret du scénario car il a dépassé son temps de timeout : ') . $scenario->getTimeout() . 's');
                $scenario->persistLog();
            }
        }
    }

    /**
     * Obtenir tous les objets scenario
     *
     * @param string $groupName Filtrer sur un groupe
     * @param string $type Filtrer sur un type
     *
     * @return Scenario[] Liste des objets scenario
     * @throws \Exception
     */
    public static function all($groupName = '', $type = null): array
    {
        $values = [];
        $result1 = null;
        $result2 = null;

        $baseSql = static::getPrefixedBaseSQL('s');
        $sqlWhereTypeFilter = ' ';
        $sqlAndTypeFilter = ' ';
        if ($type !== null) {
            $sqlWhereTypeFilter = ' WHERE `type` = :type ';
            $sqlAndTypeFilter = ' AND `type` = :type ';
            $values['type'] = $type;
        }

        $sql1 = $baseSql . 'INNER JOIN object ob ON s.object_id = ob.id ';
        if ($groupName === '') {
            $sql1 .= $sqlWhereTypeFilter . 'ORDER BY ob.name, s.group, s.name';
            $sql2 = $baseSql . 'WHERE s.object_id IS NULL' . $sqlAndTypeFilter . 'ORDER BY s.group, s.name';
        } elseif ($groupName === null) {
            $sql1 .= 'WHERE (`group` IS NULL OR `group` = "")' . $sqlAndTypeFilter . 'ORDER BY s.group, s.name';
            $sql2 = $baseSql . 'WHERE (`group` IS NULL OR `group` = "") AND s.object_id IS NULL' . $sqlAndTypeFilter . ' ORDER BY s.name';
        } else {
            $values = ['group' => $groupName];
            $sql1 .= 'WHERE `group` = :group ' . $sqlAndTypeFilter . 'ORDER BY ob.name, s.group, s.name';
            $sql2 = $baseSql . 'WHERE `group` = :group AND s.object_id IS NULL' . $sqlAndTypeFilter . 'ORDER BY s.group, s.name';
        }
        $result1 = DBHelper::getAllObjects($sql1, $values, self::CLASS_NAME);
        $result2 = DBHelper::getAllObjects($sql2, $values, self::CLASS_NAME);
        if (!is_array($result1)) {
            $result1 = [];
        }
        if (!is_array($result2)) {
            $result2 = [];
        }
        return array_merge($result1, $result2);
    }

    /**
     * Fait dedans ??? @TODO: Trouver un nom explicite
     *
     * @param array $options ???
     *
     * @throws \Exception
     */
    public static function doIn(array $options)
    {
        $scenario = self::byId($options['scenario_id']);
        if (is_object($scenario)) {
            if ($scenario->getIsActive() == 0) {
                $scenario->setLog(__('Scénario désactivé non lancement de la sous tâche'));
                $scenario->persistLog();
            } else {
                $scenarioElement = ScenarioElementManager::byId($options['scenarioElement_id']);
                $scenario->setLog(__('************Lancement sous tâche**************'));
                if (isset($options['tags']) && is_array($options['tags']) && count($options['tags']) > 0) {
                    $scenario->setTags($options['tags']);
                    $scenario->setLog(__('Tags : ') . json_encode($scenario->getTags()));
                }
                if (is_object($scenarioElement)) {
                    if (is_numeric($options['second']) && $options['second'] > 0) {
                        sleep($options['second']);
                    }
                    $scenarioElement->getSubElement('do')->execute($scenario);
                    $scenario->setLog(__('************FIN sous tâche**************'));
                    $scenario->persistLog();
                }
            }
        }
    }

    /**
     * Nettoie la table @TODO: Avec l'éponge et grosse optimisation à faire
     */
    public static function cleanTable()
    {
        $ids = [
            'element' => [],
            'subelement' => [],
            'expression' => [],
        ];
        foreach (self::all() as $scenario) {
            foreach ($scenario->getElement() as $element) {
                $result = $element->getAllId();
                $ids['element'] = array_merge($ids['element'], $result['element']);
                $ids['subelement'] = array_merge($ids['subelement'], $result['subelement']);
                $ids['expression'] = array_merge($ids['expression'], $result['expression']);
            }
        }

        $tablesToClean = [
            'scenarioExpression' => 'expression',
            'scenarioSubElement' => 'subelement',
            'scenarioElement' => 'element'
        ];

        foreach ($tablesToClean as $table => $item) {
            $sql = 'DELETE FROM ' . $table . ' WHERE id NOT IN (-1';
            foreach ($ids[$item] as $expressionId) {
                $sql .= ',' . $expressionId;
            }
            $sql .= ')';
            DBHelper::getAll($sql);
        }
    }

    /**
     * Test la validité des scénarios @TODO: Je suppose
     *
     * @param bool $needsReturn Argument à virer
     *
     * @return array
     * @throws \ReflectionException
     */
    public static function consystencyCheck($needsReturn = false)
    {
        $result = [];
        foreach (self::all() as $scenario) {
            if ($scenario->getIsActive() != 1 && !$needsReturn) {
                continue;
            }
            if ($scenario->getMode() == 'provoke' || $scenario->getMode() == 'all') {
                $trigger_list = '';
                foreach ($scenario->getTrigger() as $trigger) {
                    $trigger_list .= CmdManager::cmdToHumanReadable($trigger) . '_';
                }
                preg_match_all("/#([0-9]*)#/", $trigger_list, $matches);
                foreach ($matches[1] as $cmd_id) {
                    if (is_numeric($cmd_id)) {
                        if ($needsReturn) {
                            $result[] = ['detail' => 'Scénario ' . $scenario->getHumanName(), 'help' => 'Déclencheur du scénario', 'who' => '#' . $cmd_id . '#'];
                        } else {
                            LogHelper::addError(LogTarget::SCENARIO, __('Un déclencheur du scénario : ') . $scenario->getHumanName() . __(' est introuvable'));
                        }
                    }
                }
            }
            $expression_list = '';
            foreach ($scenario->getElement() as $element) {
                $expression_list .= CmdManager::cmdToHumanReadable(json_encode($element->getAjaxElement()));
            }
            preg_match_all("/#([0-9]*)#/", $expression_list, $matches);
            foreach ($matches[1] as $cmd_id) {
                if (is_numeric($cmd_id)) {
                    if ($needsReturn) {
                        $result[] = ['detail' => 'Scénario ' . $scenario->getHumanName(), 'help' => 'Utilisé dans le scénario', 'who' => '#' . $cmd_id . '#'];
                    } else {
                        LogHelper::addError(LogTarget::SCENARIO, __('Une commande du scénario : ') . $scenario->getHumanName() . __(' est introuvable'));
                    }
                }
            }
        }
        if ($needsReturn) {
            return $result;
        }
        return null;
    }

    /**
     * /@TODO: Fatigué d'essayer de comprendre à quoi ça sert
     * Méthode appelée de façon recursive
     *
     * @param $input /@TODO: Ca entre en effect
     *
     * @return mixed @TODO: Quelque chose de lisible à priori
     *
     * @throws \Exception
     */
    public static function toHumanReadable($input)
    {
        if (is_object($input)) {
            $reflections = [];
            $uuid = spl_object_hash($input);
            if (!isset($reflections[$uuid])) {
                $reflections[$uuid] = new \ReflectionClass($input);
            }
            $reflection = $reflections[$uuid];
            $properties = $reflection->getProperties();
            foreach ($properties as $property) {
                $property->setAccessible(true);
                $value = $property->getValue($input);
                $property->setValue($input, self::toHumanReadable($value));
                $property->setAccessible(false);
            }
            return $input;
        }
        if (is_array($input)) {
            foreach ($input as $key => $value) {
                $input[$key] = self::toHumanReadable($value);
            }
            return $input;
        }
        $text = $input;
        preg_match_all("/#scenario([0-9]*)#/", $text, $matches);
        foreach ($matches[1] as $scenario_id) {
            if (is_numeric($scenario_id)) {
                $scenario = self::byId($scenario_id);
                if (is_object($scenario)) {
                    $text = str_replace('#scenario' . $scenario_id . '#', '#' . $scenario->getHumanName(true) . '#', $text);
                }
            }
        }
        return $text;
    }

    /**
     * @TODO:
     * @param array $searchs
     * @return array
     * @throws \Exception
     */
    public static function searchByUse(array $searchs)
    {
        $return = [];
        $expressions = [];
        $scenarios = [];
        foreach ($searchs as $search) {
            $_cmd_id = str_replace('#', '', $search['action']);
            $return = array_merge($return, self::byTrigger($_cmd_id, false));
            if (!isset($search['and'])) {
                $search['and'] = false;
            }
            if (!isset($search['option'])) {
                $search['option'] = $search['action'];
            }
            $expressions = array_merge($expressions, ScenarioExpressionManager::searchExpression($search['action'], $search['option'], $search['and']));
        }
        if (is_array($expressions) && count($expressions) > 0) {
            foreach ($expressions as $expression) {
                $scenarios[] = $expression->getSubElement()->getElement()->getScenario();
            }
        }
        if (is_array($scenarios) && count($scenarios) > 0) {
            foreach ($scenarios as $scenario) {
                if (is_object($scenario)) {
                    $find = false;
                    foreach ($return as $existScenario) {
                        if ($scenario->getId() == $existScenario->getId()) {
                            $find = true;
                            break;
                        }
                    }
                    if (!$find) {
                        $return[] = $scenario;
                    }
                }
            }
        }
        return $return;
    }

    /**
     *@TODO: PATH pointe vers rien
     *
     * @param string $template
     *
     * @return mixed
     */
    public static function getTemplate($template = '')
    {
        $path = NEXTDOM_DATA . '/data/scenario';
        /**
         * if (isset($template) && $template != '') {
         * // @TODO Magic trixxxxxx
         * }
         */
        return FileSystemHelper::ls($path, '*.json', false, ['files', 'quiet']);
    }

    /**
     * @TODO:
     *
     * @param $market
     *
     * @return string
     *
     * @throws \Exception
     */
    public static function shareOnMarket(&$market)
    {
        $moduleFile = NEXTDOM_DATA . '/data/scenario/' . $market->getLogicalId() . '.json';
        if (!file_exists($moduleFile)) {
            throw new CoreException('Impossible de trouver le fichier de configuration ' . $moduleFile);
        }
        $tmp = NextDomHelper::getTmpFolder('market') . '/' . $market->getLogicalId() . '.zip';
        if (file_exists($tmp)) {
            if (!unlink($tmp)) {
                throw new CoreException(__('Impossible de supprimer : ') . $tmp . __('. Vérifiez les droits'));
            }
        }
        if (!FileSystemHelper::createZip($moduleFile, $tmp)) {
            throw new CoreException(__('Echec de création du zip. Répertoire source : ') . $moduleFile . __(' / Répertoire cible : ') . $tmp);
        }
        return $tmp;
    }

    /**
     * @TODO:
     * @param mixed $market
     * @param mixed $path
     * @throws \Exception
     */
    public static function getFromMarket(&$market, $path)
    {
        $cibDir = NEXTDOM_DATA . '/data/scenario/';
        if (!file_exists($cibDir)) {
            mkdir($cibDir);
        }
        $zip = new \ZipArchive;
        if ($zip->open($path) === true) {
            $zip->extractTo($cibDir . '/');
            $zip->close();
        } else {
            throw new CoreException('Impossible de décompresser l\'archive zip : ' . $path);
        }
    }

    /**
     * @TODO: Trixxxxxx
     * @return array
     */
    public static function listMarketObject()
    {
        return [];
    }

    /**
     * @TODO: Le CSS C'est pour les faibles
     * @param array $event
     * @return array|null
     * @throws \Exception
     */
    public static function timelineDisplay(array $event)
    {
        $return = [];
        $return['date'] = $event['datetime'];
        $return['group'] = 'scenario';
        $return['type'] = $event['type'];
        $scenario = self::byId($event['id']);
        if (!is_object($scenario)) {
            return null;
        }
        $linkedObject = $scenario->getObject();
        $return['object'] = is_object($linkedObject) ? $linkedObject->getId() : 'aucun';
        $return['html'] = '<div class="timeline-item cmd" data-id="' . $event['id'] . '">'
            . '<span class="time"><i class="fa fa-clock-o spacing-right"></i>' . trim(substr($event['datetime'], -9)) . '</span>'
            . '<h3 class="timeline-header">' . $event['name'] . '</h3>'
            . '<div class="timeline-body">'
            . 'Déclenché par ' . $event['trigger']
            . ' <div class="timeline-footer">'
            . '</div>'
            . '</div>';
        return $return;
    }

    /**
     * State of the scenario engine
     *
     * @return bool True if enabled
     *
     * @throws \Exception
     */
    public static function isEnabled() {
        if (ConfigManager::byKey('enableScenario') != 1) {
            return false;
        }
        return true;
    }
}