Source of file PluginManager.php

Size: 15,918 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
<?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\DaemonState;
use NextDom\Enums\DateFormat;
use NextDom\Enums\PluginManagerCron;
use NextDom\Exceptions\CoreException;
use NextDom\Helpers\DBHelper;
use NextDom\Helpers\FileSystemHelper;
use NextDom\Helpers\LogHelper;
use NextDom\Helpers\Utils;
use NextDom\Model\Entity\Plugin;

/**
 * Class PluginManager
 * @package NextDom\Managers
 */
class PluginManager
{
    private static $cache = [];
    private static $enabledPlugins = null;

    /**
     * @param bool $activatedOnly
     * @param bool $nameOnly
     * @return array
     * @throws \Exception
     */
    public static function getPluginsByCategory(bool $activatedOnly = false, bool $nameOnly = false): array
    {
        return self::listPlugin($activatedOnly, true, $nameOnly);
    }

    /**
     * Get the list of plugins
     *
     * @param bool $activatedOnly Filter only activated plugins
     * @param bool $orderByCategory Sort by category
     * @param bool $nameOnly Get only plugin names
     * @return Plugin[] List of plugins
     *
     * @throws \Exception
     */
    public static function listPlugin(bool $activatedOnly = false, bool $orderByCategory = false, bool $nameOnly = false): array
    {
        $listPlugin = [];
        if ($activatedOnly) {
            $sql = "SELECT plugin
                    FROM `config`
                    WHERE `key` = 'active'
                    AND `value` = '1'";
            $queryResults = DBHelper::getAll($sql);
            if ($nameOnly) {
                foreach ($queryResults as $row) {
                    $listPlugin[] = $row['plugin'];
                }
                return $listPlugin;
            } else {
                foreach ($queryResults as $row) {
                    try {
                        $listPlugin[] = self::byId($row['plugin']);
                    } catch (\Throwable $e) {
                        LogHelper::addError('plugin', $e->getMessage(), 'pluginNotFound::' . $row['plugin']);
                    }
                }
            }
        } else {
            $rootPluginPath = NEXTDOM_ROOT . '/plugins';
            foreach (FileSystemHelper::ls($rootPluginPath, '*') as $dirPlugin) {
                if (is_dir($rootPluginPath . '/' . $dirPlugin)) {
                    $pathInfoPlugin = $rootPluginPath . '/' . $dirPlugin . 'plugin_info/info.json';
                    if (file_exists($pathInfoPlugin)) {
                        try {
                            $listPlugin[] = self::byId($pathInfoPlugin);
                        } catch (\Throwable $e) {
                            LogHelper::addError('plugin', $e->getMessage(), 'pluginNotFound::' . $pathInfoPlugin);
                        }
                    }
                }
            }
        }
        $returnValue = [];
        if ($orderByCategory) {
            if (count($listPlugin) > 0) {
                foreach ($listPlugin as $plugin) {
                    $category = $plugin->getCategory();
                    if ($category == '') {
                        $category = __('Autre');
                    }
                    if (!isset($returnValue[$category])) {
                        $returnValue[$category] = [];
                    }
                    $returnValue[$category][] = $plugin;
                }
                foreach ($returnValue as &$category) {
                    usort($category, 'plugin::orderPlugin');
                }
                ksort($returnValue);
            }
        } else {
            if (isset($listPlugin) && is_array($listPlugin) && count($listPlugin) > 0) {
                usort($listPlugin, 'plugin::orderPlugin');
                $returnValue = $listPlugin;
            }
        }
        return $returnValue;
    }

    /**
     * Get a plugin from his username
     *
     * @param string $pluginId Plugin Id or path to info.json
     *
     * @return mixed|Plugin Plugin
     *
     * @throws \Exception
     */
    public static function byId($pluginId)
    {
        global $NEXTDOM_INTERNAL_CONFIG;
        $pluginInfoFilePath = '';

        // Check plugin data in cache
        if (is_string($pluginId) && isset(self::$cache[$pluginId])) {
            return self::$cache[$pluginId];
        }
        if (!file_exists($pluginId) || strpos($pluginId, '/') === false) {
            $pluginInfoFilePath = self::getPathById($pluginId);
        } else {
            // Info.json passed directly
            $pluginInfoFilePath = realpath((string)$pluginId);
        }

        if (!Utils::checkPath($pluginInfoFilePath) || !file_exists($pluginInfoFilePath)) {
            self::forceDisablePlugin($pluginId);
            throw new CoreException(__('Plugin introuvable : ') . $pluginId);
        }
        $pluginData = json_decode(file_get_contents($pluginInfoFilePath), true);
        if (!is_array($pluginData)) {
            self::forceDisablePlugin($pluginId);
            throw new CoreException(__('Plugin introuvable (json invalide) : ') . $pluginInfoFilePath . ' => ' . print_r($pluginData, true));
        }
        $plugin = new Plugin();
        $plugin->initPluginFromData($pluginData);
        self::$cache[$plugin->getId()] = $plugin;
        if (!isset($NEXTDOM_INTERNAL_CONFIG['plugin']['category'][$plugin->getCategory()])) {
            foreach ($NEXTDOM_INTERNAL_CONFIG['plugin']['category'] as $key => $value) {
                if (!isset($value['alias'])) {
                    continue;
                }
                if (in_array($plugin->getCategory(), $value['alias'])) {
                    $plugin->setCategory($key);
                    break;
                }
            }
        }
        return $plugin;
    }

    /**
     * Get the path of the info.json file from the plugin ID
     *
     * @param string $id Plugin ID
     * @return string Path to the info.json file
     */
    public static function getPathById(string $id): string
    {
        return realpath(NEXTDOM_ROOT . '/plugins/' . $id . '/plugin_info/info.json');
    }

    /**
     * @param $_id
     * @throws \NextDom\Exceptions\CoreException
     */
    public static function forceDisablePlugin($_id)
    {
        ConfigManager::save('active', 0, $_id);
        $values = [
            'eqType_name' => $_id,
        ];
        $sql = 'UPDATE eqLogic
                SET isEnable = 0
                WHERE `eqType_name` = :eqType_name';
        DBHelper::exec($sql, $values);
    }

    /**
     * Comparaison entre 2 plugins pour un tri
     *
     * @param $firstPlugin
     * @param $secondPluginName
     *
     * @return int Résultat de la comparaison
     */
    public static function orderPlugin(Plugin $firstPlugin, Plugin $secondPluginName): int
    {
        return strcmp(strtolower($firstPlugin->getName()), strtolower($secondPluginName->getName()));
    }

    /**
     * @throws \Exception
     */
    public static function heartbeat()
    {
        foreach (self::listPlugin(true) as $plugin) {
            try {
                $heartbeat = ConfigManager::byKey('heartbeat::delay::' . $plugin->getId(), 'core', 0);
                if ($heartbeat == 0 || is_nan($heartbeat)) {
                    continue;
                }
                $eqLogics = EqLogicManager::byType($plugin->getId(), true);
                if (count($eqLogics) == 0) {
                    continue;
                }
                $ok = false;
                foreach ($eqLogics as $eqLogic) {
                    if ($eqLogic->getStatus('lastCommunication', date(DateFormat::FULL)) > date(DateFormat::FULL, strtotime('-' . $heartbeat . ' minutes' . date(DateFormat::FULL)))) {
                        $ok = true;
                        break;
                    }
                }
                if (!$ok) {
                    $message = __('Attention le plugin ') . ' ' . $plugin->getName();
                    $message .= __(' n\'a recu de message depuis ') . $heartbeat . __(' min');
                    $logicalId = 'heartbeat' . $plugin->getId();
                    MessageManager::add($plugin->getId(), $message, '', $logicalId, true);
                    if ($plugin->getHasOwnDeamon() && ConfigManager::byKey('heartbeat::restartDeamon::' . $plugin->getId(), 'core', 0) == 1) {
                        $plugin->deamon_start(true);
                    }
                }
            } catch (\Exception $e) {
            }
        }
    }

    /**
     * Tâche exécutée toutes les minutes
     *
     * @throws \Exception
     */
    public static function cron()
    {
        self::startCronTask(PluginManagerCron::CRON);
    }

    /**
     * Start a cron job
     *
     * @param string $cronType Cron job type, see PluginManagerCronEnum
     * // @TODO Rajouter un test sur l'enum ???
     * @throws \Exception
     */
    private static function startCronTask(string $cronType = '')
    {
        $cache = CacheManager::byKey('plugin::' . $cronType . '::inprogress');
        if (is_array($cache->getValue(0))) {
            CacheManager::set('plugin::' . $cronType . '::inprogress', -1);
            $cache = CacheManager::byKey('plugin::' . $cronType . '::inprogress');
        }
        if ($cache->getValue(0) > 3) {
            MessageManager::add('core', __('La tache plugin::' . $cronType . ' n\'arrive pas à finir à cause du plugin : ') . CacheManager::byKey('plugin::' . $cronType . '::last')->getValue() . __(' nous vous conseillons de désactiver le plugin et de contacter l\'auteur'));
        }
        CacheManager::set('plugin::' . $cronType . '::inprogress', $cache->getValue(0) + 1);
        foreach (self::listPlugin(true) as $plugin) {
            if (method_exists($plugin->getId(), $cronType)) {
                if (ConfigManager::byKey('functionality::cron::enable', $plugin->getId(), 1) == 1) {
                    $pluginId = $plugin->getId();
                    CacheManager::set('plugin::' . $cronType . '::last', $pluginId);
                    //try {
                    $pluginId::$cronType();
                    //} catch (\Throwable $e) {
//                        LogHelper::addError($pluginId, __('Erreur sur la fonction cron du plugin : ') . $e->getMessage());
                    //}
                }
            }
        }
        CacheManager::set('plugin::' . $cronType . '::inprogress', 0);
    }

    /**
     * Tâche exécutée toutes les 5 minutes
     *
     * @throws \Exception
     */
    public static function cron5()
    {
        self::startCronTask(PluginManagerCron::CRON_5);
    }

    /**
     * Tâche exécutée toutes les 10 minutes
     *
     * @throws \Exception
     */
    public static function cron10()
    {
        self::startCronTask(PluginManagerCron::CRON_10);
    }

    /**
     * Tâche exécutée toutes les 15 minutes
     *
     * @throws \Exception
     */
    public static function cron15()
    {
        self::startCronTask(PluginManagerCron::CRON_15);
    }

    /**
     * Tâche exécutée toutes les 30 minutes
     *
     * @throws \Exception
     */
    public static function cron30()
    {
        self::startCronTask(PluginManagerCron::CRON_30);
    }

    /**
     * Task performed every day
     *
     * @throws \Exception
     */
    public static function cronDaily()
    {
        self::startCronTask(PluginManagerCron::CRON_DAILY);
    }

    /**
     * Tâche exécutée toutes les heures
     *
     * @throws \Exception
     */
    public static function cronHourly()
    {
        self::startCronTask(PluginManagerCron::CRON_HOURLY);
    }

    /**
     * Start plugin daemons
     *
     * @throws \Exception
     */
    public static function start()
    {
        foreach (self::listPlugin(true) as $plugin) {
            $plugin->deamon_start(false, true);
            if (method_exists($plugin->getId(), 'start')) {
                $pluginId = $plugin->getId();
                try {
                    $pluginId::start();
                } catch (\Throwable $e) {
                    LogHelper::addError($pluginId, __('Erreur sur la fonction start du plugin : ') . $e->getMessage());
                }
            }
        }
    }

    /**
     * Arrête les daemons des plugins
     *
     * @throws \Exception
     */
    public static function stop()
    {
        foreach (self::listPlugin(true) as $plugin) {
            $plugin->deamon_stop();
            if (method_exists($plugin->getId(), 'stop')) {
                $pluginId = $plugin->getId();
                try {
                    $pluginId::stop();
                } catch (\Throwable $e) {
                    LogHelper::addError($pluginId, __('Erreur sur la fonction stop du plugin : ') . $e->getMessage());
                }
            }
        }
    }

    /**
     * Test le daemon @TODO ??
     *
     * @throws \Exception
     */
    public static function checkDeamon()
    {
        foreach (self::listPlugin(true) as $plugin) {
            if (ConfigManager::byKey('deamonAutoMode', $plugin->getId(), 1) != 1) {
                continue;
            }
            $dependancy_info = $plugin->getDependencyInfo();
            if ($dependancy_info['state'] == DaemonState::NOT_OK) {
                try {
                    $plugin->dependancy_install();
                } catch (\Exception $e) {

                }
            } elseif ($dependancy_info['state'] == DaemonState::IN_PROGRESS && $dependancy_info['duration'] > $plugin->getMaxDependancyInstallTime()) {
                if (isset($dependancy_info['progress_file']) && file_exists($dependancy_info['progress_file'])) {
                    shell_exec('rm ' . $dependancy_info['progress_file']);
                }
                ConfigManager::save('deamonAutoMode', 0, $plugin->getId());
                LogHelper::addError($plugin->getId(), __('Attention : l\'installation des dépendances a dépassé le temps maximum autorisé : ') . $plugin->getMaxDependancyInstallTime() . 'min');
            }
            try {
                $plugin->deamon_start(false, true);
            } catch (\Exception $e) {

            }
        }
    }

    /**
     * Test si le plugin est actif
     * @TODO: Doit passer en static
     * @param $id
     * @return int
     * @throws \Exception
     */
    public static function isActive($id)
    {
        $result = 0;
        if (self::$enabledPlugins === null) {
            self::$enabledPlugins = ConfigManager::getEnabledPlugins();
        }
        if (isset(self::$enabledPlugins[$id])) {
            $result = self::$enabledPlugins[$id];
        }
        return $result;
    }
}