Source of file UpdateManager.php

Size: 12,628 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
<?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\DateFormat;
use NextDom\Enums\LogTarget;
use NextDom\Enums\NextDomObj;
use NextDom\Enums\UpdateStatus;
use NextDom\Helpers\DBHelper;
use NextDom\Helpers\FileSystemHelper;
use NextDom\Helpers\LogHelper;
use NextDom\Managers\Parents\BaseManager;
use NextDom\Managers\Parents\CommonManager;
use NextDom\Model\Entity\Update;

/**
 * Class UpdateManager
 *
 * Manage updates
 *
 * @package NextDom\Managers
 */
class UpdateManager extends BaseManager
{
    use CommonManager;
    const REPO_CLASS_PATH = '\\NextDom\\Repo\\';
    const DB_CLASS_NAME = '`update`';
    const CLASS_NAME = Update::class;

    /**
     * Check all updates
     *
     * @param string $filter Type of update
     * @param bool $findNewObjects Find if new objects are presents
     *
     * @throws \Exception
     */
    public static function checkAllUpdate($filter = '', $findNewObjects = true)
    {
        if ($findNewObjects) {
            self::findNewUpdateObject();
        }
        $updatesList = self::all($filter);
        $updatesToCheckBySource = [];
        // Arrange updates by source
        if (is_array($updatesList)) {
            foreach ($updatesList as $update) {
                if ($update->getStatus() != UpdateStatus::HOLD) {
                    $updateSource = $update->getSource();
                    if (!isset($updatesToCheckBySource[$updateSource])) {
                        $updatesToCheckBySource[$updateSource] = [];
                    }
                    $updatesToCheckBySource[$updateSource][] = $update;
                }
            }
        }

        // Check all updates
        foreach ($updatesToCheckBySource as $source => $updates) {
            if (ConfigManager::byKey($source . '::enable') == 1) {
                $repoData = self::getRepoDataFromName($source);
                if (array_key_exists('phpClass', $repoData)) {
                    $repoPhpClass = $repoData['phpClass'];
                    if (class_exists($repoPhpClass) && method_exists($repoPhpClass, 'checkUpdate')) {
                        $repoPhpClass::checkUpdate($updates);
                    }
                }
            }
        }

        // Save last update in database
        ConfigManager::save('update::lastCheck', date(DateFormat::FULL));
    }

    /**
     * Find if new items are presents (installed manually)
     *
     * @throws \Exception
     */
    public static function findNewUpdateObject()
    {
        // Look for plugins
        foreach (PluginManager::listPlugin() as $plugin) {
            $pluginId = $plugin->getId();
            $update = self::byTypeAndLogicalId(NextDomObj::PLUGIN, $pluginId);
            // Add update data if plugin not exists
            if (!is_object($update)) {
                $update = (new Update())
                    ->setLogicalId($pluginId)
                    ->setType(NextDomObj::PLUGIN)
                    ->setLocalVersion(date(DateFormat::FULL));
                $update->save();
            }
            $find = [];
            // Check for plugin with market
            if (method_exists($pluginId, 'listMarketObject')) {
                $pluginIdListMarketObject = $pluginId::listMarketObject();
                // Check all object from this market
                foreach ($pluginIdListMarketObject as $logicalId) {
                    $find[$logicalId] = true;
                    $update = self::byTypeAndLogicalId($pluginId, $logicalId);
                    // Add update if not exists
                    if (!is_object($update)) {
                        $update = (new Update())
                            ->setLogicalId($logicalId)
                            ->setType($pluginId)
                            ->setLocalVersion(date(DateFormat::FULL));
                        $update->save();
                    }
                }
                $byTypePluginId = self::byType($pluginId);
                foreach ($byTypePluginId as $update) {
                    if (!isset($find[$update->getLogicalId()])) {
                        $update->remove();
                    }
                }
            } else {
                // Remove all update if plugin is removed
                $params = [
                    'type' => $pluginId,
                ];
                $sql = 'DELETE FROM ' . self::DB_CLASS_NAME . '
                        WHERE `type` = :type';
                DBHelper::exec($sql, $params);
            }
        }
    }

    /**
     * Get updates from their type and logicalId
     *
     * @param $type
     * @param $logicalId
     *
     * @return Update|null
     *
     * @throws \Exception
     */
    public static function byTypeAndLogicalId($type, $logicalId)
    {
        return static::getOneByClauses(['logicalId' => $logicalId, 'type' => $type]);
    }

    /**
     * Get updates by type
     *
     * @param $type
     *
     * @return Update[]|null
     *
     * @throws \Exception
     */
    public static function byType($type)
    {
        return static::getMultipleByClauses(['type' => $type]);
    }

    /**
     * Get all the updates.
     *
     * @param string $filter
     *
     * @return Update[]|null List of all objects
     *
     * @throws \Exception
     */
    public static function all($filter = '')
    {
        $params = [];
        $sql = static::getBaseSQL() . ' ';
        if ($filter != '') {
            $params['type'] = $filter;
            $sql .= 'WHERE `type` = :type ';
        }
        $sql .= 'ORDER BY FIELD(`status`, "update", "ok", "depreciated") ASC, FIELD(`type`, "plugin", "core") DESC, `name` ASC';
        return DBHelper::getAllObjects($sql, $params, self::CLASS_NAME);
    }

    /**
     * Get the class of the repo by the name
     *
     * @param string $name Name of the repo in jeedom format
     *
     * @return array Associative array
     *
     * @throws \Exception
     */
    public static function getRepoDataFromName($name): array
    {
        $repoList = self::listRepo();
        foreach ($repoList as $repoData) {
            if (ucfirst($repoData['name']) == ucfirst($name)) {
                return [
                    'className' => str_replace(self::REPO_CLASS_PATH, '', $repoData['class']),
                    'phpClass' => $repoData['class']
                ];
            }
        }
        return [];
    }

    /**
     * List of repositories
     *
     * @return array Repositories data
     *
     * @throws \Exception
     */
    public static function listRepo(): array
    {
        $result = [];
        foreach (FileSystemHelper::ls(NEXTDOM_ROOT . '/src/Repo/', '*.php') as $repoFile) {
            $repoClassName = str_replace('.php', '', $repoFile);
            $fullNameClass = self::REPO_CLASS_PATH . $repoClassName;
            if (class_exists($fullNameClass) && is_subclass_of($fullNameClass, '\\NextDom\\Interfaces\\BaseRepo')) {
                $repoCode = strtolower(str_replace('Repo', '', $repoClassName));
                $result[$repoCode] = [
                    'name' => $fullNameClass::$_name,
                    'class' => $fullNameClass,
                    'configuration' => $fullNameClass::$_configuration,
                    'scope' => $fullNameClass::$_scope,
                    'description' => $fullNameClass::$_description,
                    'icon' => $fullNameClass::$_icon
                ];
                $result[$repoCode]['enable'] = ConfigManager::byKey($repoCode . '::enable');
            }
        }
        return $result;
    }

    /**
     * Get a repo by its identifier
     *
     * @param string $id Repo identifier
     *
     * @return array Repo data
     *
     * @throws \Exception
     */
    public static function repoById($id)
    {
        $repoClassData = self::getRepoDataFromName($id);
        $phpClass = $repoClassData['phpClass'];
        $result = [
            'name' => $phpClass::$_name,
            'class' => $repoClassData['className'],
            'configuration' => $phpClass::$_configuration,
            'scope' => $phpClass::$_scope,
            'description' => $phpClass::$_description,
            'icon' => $phpClass::$_icon
        ];
        $result['enable'] = ConfigManager::byKey($id . '::enable');
        return $result;
    }

    /**
     * Update all items
     *
     * @param string $filter Type of updates
     *
     * @return bool True if all update pass
     *
     * @throws \NextDom\Exceptions\CoreException
     * @throws \Throwable
     */
    public static function updateAll(string $filter = '')
    {
        $error = false;
        if ($filter == 'core') {
            foreach (self::byType($filter) as $update) {
                $update->doUpdate();
            }
        } else {
            if ($filter == '') {
                $updatesList = self::all();
            } else {
                $updatesList = self::byType($filter);
            }
            if (is_array($updatesList)) {
                foreach ($updatesList as $update) {
                    if ($update->getStatus() != UpdateStatus::HOLD && $update->getStatus() == UpdateStatus::UPDATE && $update->getType() != 'core') {
                        try {
                            $update->doUpdate();
                        } catch (\Exception $e) {
                            LogHelper::addUpdate(LogTarget::UPDATE, $e->getMessage());
                            $error = true;
                        }
                    }
                }
            }
        }
        return $error;
    }

    /**
     * Get updates by their status
     *
     * @param string $status Status of the update (@see UpdateStatus)
     *
     * @return Update[] List of updates of the required status
     *
     * @throws \Exception
     */
    public static function byStatus($status)
    {
        return static::getMultipleByClauses(['status' => $status]);
    }

    /**
     * Get the bets from its logical identifier
     *
     * @param string $logicalId Logical Id of the update (plugin id)
     *
     * @return Update[]|null List of updates
     *
     * @throws \Exception
     */
    public static function byLogicalId($logicalId)
    {
        return static::getOneByClauses(['logicalId' => $logicalId]);
    }

    /**
     * Get the number of pending updates
     *
     * @param string $filter Type filter
     *
     * @return int Count of pending updates
     *
     * @throws \NextDom\Exceptions\CoreException
     */
    public static function nbNeedUpdate($filter = '')
    {
        $params = [
            'status' => 'update',
            'configuration' => '%"doNotUpdate":"1"%'
        ];
        $sql = 'SELECT count(*)
               FROM ' . self::DB_CLASS_NAME . '
               WHERE `status` = :status
               AND `configuration` NOT LIKE :configuration';
        if ($filter != '') {
            $params['type'] = $filter;
            $sql .= ' AND `type` = :type';
        }

        $result = DBHelper::getOne($sql, $params);
        return $result['count(*)'];
    }

    /**
     * List core updates
     *
     * @return array List of updates
     */
    public static function listCoreUpdate()
    {
        return FileSystemHelper::ls(NEXTDOM_ROOT . '/install/update', '*');
    }
}