Source of file MarketItem.php

Size: 19,307 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

/home/travis/build/NextDom/nextdom-core/src/Market/MarketItem.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
<?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 Software 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 Software. If not, see <http://www.gnu.org/licenses/>.
 */

namespace NextDom\Market;

use NextDom\Helpers\DataStorage;
use NextDom\Managers\UpdateManager;

/**
 * Class MarketItem
 * @package NextDom\Market
 */
class MarketItem
{
    /**
     * @var int Refresh time of a deposit
     */
    private $REFRESH_TIME_LIMIT = 86400;

    /**
     * @var string Plugin ID
     */
    private $id;
    /**
     * @var string Plugin name on GitHub
     */
    private $gitName;
    /**
     * @var string User GitHub
     */
    private $gitId;
    /**
     * @var string Full name of his deposit
     */
    private $fullName;
    /**
     * @var string Description
     */
    private $description;
    /**
     * @var string URL Git
     */
    private $url;
    /**
     * @var string Name of plugin
     */
    private $name;
    /**
     * @var string Plugin author
     */
    private $author;
    /**
     * @var string Plugin category
     */
    private $category;
    /**
     * @var DataStorage Database Manager
     */
    private $dataStorage;
    /**
     * @var string Icon path
     */
    private $iconPath;
    /**
     * @var string Default branch
     */
    private $defaultBranch;
    /**
     * @var array List of branches
     */
    private $branchesList;
    /**
     * @var string Licence
     */
    private $licence;
    /**
     * @var string Link to the documentation
     */
    private $documentationLink;
    /**
     * @var string Link to the changelog
     */
    private $changelogLink;
    /**
     * @var string Name of the source
     */
    private $sourceName;
    /**
     * @var array Jeedom data on the plugin
     */
    private $updateData;
    /**
     * @var array Captures list
     */
    private $screenshots;

    /**
     * Builder initializing basic information
     *
     * @param string $sourceName Name of the source of the element
     */
    public function __construct($sourceName)
    {
        $this->dataStorage = new DataStorage('market');
        // @TODO: A supprimer
        if (!$this->dataStorage->isDataTableExists()) {
            $this->dataStorage->createDataTable();
        }

        $this->sourceName = $sourceName;
        $this->iconPath = false;
    }

    /**
     * Create an element from the data of a GitHub repository
     *
     * @param string $sourceName Create an element from the data of a GitHub repository
     * @param string[] $repositoryInformations Deposit Information
     *
     * @return MarketItem Element created
     */
    public static function createFromGit(string $sourceName, $repositoryInformations)
    {
        $result = new self($sourceName);
        $result->initWithGlobalInformations($repositoryInformations);
        return $result;
    }

    /**
     * Lire les informations obtenus par GitHub
     *
     * @param string[] $repositoryInformations Informations de GitHub
     */
    public function initWithGlobalInformations($repositoryInformations)
    {

        if (array_key_exists('name', $repositoryInformations)) $this->gitName = $repositoryInformations['name'];
        if (array_key_exists('full_name', $repositoryInformations)) $this->fullName = $repositoryInformations['full_name'];
        if (array_key_exists('html_url', $repositoryInformations)) $this->url = $repositoryInformations['html_url'];
        if (array_key_exists('git_id', $repositoryInformations)) $this->gitId = $repositoryInformations['git_id'];
        if (array_key_exists('description', $repositoryInformations)) $this->description = $repositoryInformations['description'];
        if (array_key_exists('default_branch', $repositoryInformations)) $this->defaultBranch = $repositoryInformations['default_branch'];
    }

    /**
     * Create an item from the cache
     *
     * @param string $sourceName Name of the source
     * @param string $fullName Full Name
     *
     * @return MarketItem Element created
     */
    public static function createFromCache(string $sourceName, string $fullName)
    {
        $result = (new self($sourceName))
            ->setFullName($fullName);
        $result->readCache();
        return $result;
    }

    /**
     * Read the cache file
     *
     * @return bool True if the reading was successful
     */
    public function readCache(): bool
    {
        $name = sprintf("repo_data_%s", str_replace("/", "_", $this->fullName));
        $json = $this->dataStorage->getJsonData($name);
        $attrs = ["name", "gitName", "gitId", "fullName",
            "description", "url", "id", "author",
            "category", "iconPath", "defaultBranch", "branchesList",
            "licence", "changelogLink", "documentationLink", "screenshots"];
        if ($json === null) {
            return false;
        }

        foreach ($attrs as $c_attr) {
            if (true === array_key_exists($c_attr, $json)) {
                $this->$c_attr = $json[$c_attr];
            }
        }

        if (false !== $this->iconPath) {
            $path = sprintf("%s/%s", NEXTDOM_ROOT, $this->iconPath);
            if (false === file_exists($path)) {
                $this->iconPath = false;
            }
        }
        return true;
    }

    /**
     * Create an element from data in a JSON
     *
     * @param string $sourceName Name of the source
     * @param string[] $jsonData Element data
     *
     * @return MarketItem Elément créé
     */
    public static function createFromJson(string $sourceName, $jsonData)
    {
        $result = new self($sourceName);
        $result->initWithJsonInformations($jsonData);
        return $result;
    }

    /**
     * @param $jsonInformations
     */
    public function initWithJsonInformations($jsonInformations)
    {
        if (array_key_exists('id', $jsonInformations)) $this->id = $jsonInformations['id'];
        if (array_key_exists('repository', $jsonInformations)) $this->gitName = $jsonInformations['repository'];
        if (array_key_exists('gitId', $jsonInformations)) {
            $this->gitId = $jsonInformations['gitId'];
            $this->fullName = $this->gitId . '/' . $this->gitName;
            $this->url = 'https://github.com/' . $this->gitId . '/' . $this->fullName;
        }
        if (array_key_exists('name', $jsonInformations)) $this->name = $jsonInformations['name'];
        if (array_key_exists('licence', $jsonInformations)) $this->licence = $jsonInformations['licence'];
        if (array_key_exists('category', $jsonInformations)) $this->category = $jsonInformations['category'];
        if (array_key_exists('documentation', $jsonInformations)) $this->documentationLink = $jsonInformations['documentation'];
        if (array_key_exists('changelog', $jsonInformations)) $this->changelogLink = $jsonInformations['changelog'];
        if (array_key_exists('author', $jsonInformations)) $this->author = $jsonInformations['author'];
        if (array_key_exists('description', $jsonInformations)) $this->description = $jsonInformations['description'];
        if (array_key_exists('defaultBranch', $jsonInformations)) $this->defaultBranch = $jsonInformations['defaultBranch'];
        if (array_key_exists('branches', $jsonInformations)) $this->branchesList = $jsonInformations['branches'];
        if (array_key_exists('screenshots', $jsonInformations)) $this->screenshots = $jsonInformations['screenshots'];
    }

    /**
     * Test if an update is needed
     *
     * @param array $repositoryInformations Informations from GitHub
     *
     * @return bool True if an update is needed
     */
    public function isNeedUpdate(array $repositoryInformations): bool
    {
        $result = true;
        $lastUpdate = $this->dataStorage->getRawData('repo_last_update_' . str_replace('/', '_', $repositoryInformations['full_name']));
        if ($lastUpdate !== null) {
            if (time() - $lastUpdate < $this->REFRESH_TIME_LIMIT) {
                $result = false;
            }
        }
        return $result;
    }

    /**
     * Updates the data of the element
     *
     * @return bool True if the update was done.
     * @throws \Exception
     */
    public function refresh(): bool
    {
        $result = false;
        $infoJsonUrl = 'https://raw.githubusercontent.com/' . $this->fullName . '/' . $this->defaultBranch . '/plugin_info/info.json';
        $infoJson = DownloadManager::downloadContent($infoJsonUrl);
        if (strpos($infoJson, '404: Not Found') === false) {
            $pluginData = json_decode($infoJson, true);
            if (is_array($pluginData) && array_key_exists('id', $pluginData)) {
                $this->addPluginInformations($pluginData);
                $this->downloadIcon();
                $this->branchesList = [];
                $this->writeCache();
                $result = true;
            }
        }
        return $result;
    }

    /**
     * Add the information contained in the plugin's info.json file
     *
     * @param string[] $pluginInfo Contenu du fichier info.json
     */
    public function addPluginInformations($pluginInfo)
    {
        if (array_key_exists('id', $pluginInfo)) $this->id = $pluginInfo['id'];
        if (array_key_exists('name', $pluginInfo)) $this->name = $pluginInfo['name'];
        if (array_key_exists('author', $pluginInfo)) $this->author = $pluginInfo['author'];
        if (array_key_exists('category', $pluginInfo)) $this->category = $pluginInfo['category'];
        if (array_key_exists('licence', $pluginInfo)) $this->licence = $pluginInfo['licence'];
        if (array_key_exists('changelog', $pluginInfo)) $this->changelogLink = $pluginInfo['changelog'];
        if (array_key_exists('documentation', $pluginInfo)) $this->documentationLink = $pluginInfo['documentation'];
        if (array_key_exists('description', $pluginInfo) && $pluginInfo['description'] !== null && $pluginInfo['description'] !== '') {
            $this->description = $pluginInfo['description'];
        }
    }

    /**
     * Download the plugin icon
     */
    public function downloadIcon()
    {
        $iconFilename = str_replace('/', '_', $this->fullName) . '.png';
        $iconUrl = 'https://raw.githubusercontent.com/' . $this->fullName . '/' . $this->defaultBranch . '/plugin_info/' . $this->id . '_icon.png';
        $targetPath = NEXTDOM_DATA . '/public/img/market_cache/' . $iconFilename;

        DownloadManager::downloadBinary($iconUrl, $targetPath);
        if (filesize($targetPath) < 100) {
            unlink($targetPath);
            $this->iconPath = '/public/img/unknown_icon.png';
        } else {
            $this->iconPath = '/var/public/img/market_cache/' . $iconFilename;
        }
        $this->writeCache();
    }

    /**
     * Write the cache file in JSON format
     */
    public function writeCache()
    {
        $dataArray = $this->getDataInArray();
        unset($dataArray['installed']);
        unset($dataArray['installedBranchData']);
        $this->dataStorage->storeJsonData('repo_data_' . str_replace('/', '_', $this->fullName), $dataArray);
        $this->dataStorage->storeRawData('repo_last_update_' . str_replace('/', '_', $this->fullName), time());
    }

    /**
     * Get all the information in an associative array
     *
     * @return array Data array
     */
    public function getDataInArray()
    {
        $dataArray = [];
        $dataArray['name'] = $this->name;
        $dataArray['gitName'] = $this->gitName;
        $dataArray['gitId'] = $this->gitId;
        $dataArray['fullName'] = $this->fullName;
        $dataArray['description'] = $this->description;
        $dataArray['url'] = $this->url;
        $dataArray['id'] = $this->id;
        $dataArray['author'] = $this->author;
        $dataArray['category'] = $this->category;
        $dataArray['iconPath'] = $this->iconPath;
        $dataArray['defaultBranch'] = $this->defaultBranch;
        $dataArray['branchesList'] = $this->branchesList;
        $dataArray['licence'] = $this->licence;
        $dataArray['sourceName'] = $this->sourceName;
        $dataArray['changelogLink'] = $this->changelogLink;
        $dataArray['documentationLink'] = $this->documentationLink;
        $dataArray['screenshots'] = $this->screenshots;

        $this->initUpdateData();
        $dataArray['installed'] = $this->isInstalled();
        $dataArray['installedBranchData'] = false;
        if ($dataArray['installed']) {
            $dataArray['installedBranchData'] = $this->getInstalledBranchData();
        }
        return $dataArray;
    }

    /**
     * Initialize the Jeedom data on the plugin
     */
    private function initUpdateData()
    {
        $this->updateData = UpdateManager::byLogicalId($this->id);
    }

    /**
     * Test if the plugin is installed
     *
     * @return bool True if the plugin is installed
     */
    public function isInstalled(): bool
    {
        $result = false;
        if ($this->updateData !== false) {
            $result = true;
        }
        return $result;
    }

    /**
     * @return array|bool
     */
    private function getInstalledBranchData()
    {
        $result = false;
        if ($this->updateData !== false && $this->updateData !== null) {
            $configuration = $this->updateData->getConfiguration();
            if (is_array($configuration) && array_key_exists('version', $configuration)) {
                $result = [];
                $result['branch'] = $configuration['version'];
                $result['hash'] = $this->updateData->getLocalVersion();
                $result['needUpdate'] = false;
                $result['id'] = $this->updateData->getId();
                if ($this->updateData->getSource() === 'github') {
                    if ($this->updateData->getStatus() === 'update') {
                        $result['needUpdate'] = true;
                    } else {
                        foreach ($this->branchesList as $branch) {
                            if ($branch['name'] === $result['branch'] &&
                                $branch['hash'] !== $result['hash']) {
                                $result['needUpdate'] = true;
                            }
                        }
                    }
                }
            }
        }
        return $result;
    }

    /**
     * Update branch data
     *
     * @return bool True if the data was found
     * @throws \Exception
     */
    public function downloadBranchesInformations(): bool
    {
        $result = false;
        $baseGitRepoUrl = 'https://api.github.com/repos/' . $this->fullName . '/branches';
        $branches = DownloadManager::downloadContent($baseGitRepoUrl);
        if ($branches !== false) {
            $branches = json_decode($branches, true);
            $this->branchesList = [];
            foreach ($branches as $branch) {
                if (is_array($branch) && array_key_exists('name', $branch)) {
                    $branchData = [];
                    $branchData['name'] = $branch['name'];
                    $branchData['hash'] = $branch['commit']['sha'];
                    array_push($this->branchesList, $branchData);
                }
            }
            $result = true;
        }
        return $result;
    }

    public function updateBranchDataFromInstalled()
    {
        $this->initUpdateData();
        $installedBranch = $this->getInstalledBranchData();
        $added = false;
        for ($branchIndex = 0; $branchIndex < count($this->branchesList); ++$branchIndex) {
            if ($this->branchesList[$branchIndex]['name'] == $installedBranch['branch']) {
                $this->branchesList[$branchIndex]['hash'] = $installedBranch['hash'];
                $added = true;
            }
        }
        if ($added === false) {
            $branch = [];
            $branch['name'] = $installedBranch['branch'];
            $branch['hash'] = $installedBranch['hash'];
            array_push($this->branchesList, $branch);
        }
        $this->writeCache();
    }

    /**
     * Get the name of the depot.
     *
     * @return string name of the depot
     */
    public function getGitName()
    {
        return $this->gitName;
    }

    /**
     * Get the full name
     *
     * @return string full name
     */
    public function getFullName()
    {
        return $this->fullName;
    }

    /**
     * Define the name of the deposit full
     *
     * @param string $fullName full name
     *
     * @return MarketItem Instance of the object
     */
    public function setFullName($fullName): MarketItem
    {
        $this->fullName = $fullName;
        return $this;
    }

    /**
     * Obtenir la description du dépot
     *
     * @return string Description
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Obtenir le lien
     *
     * @return string Lien
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * Obtenir l'identifiant
     *
     * @return string Identifiant
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Obtenir l'auteur
     *
     * @return string Auteur
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * Obtenir la catégorie
     *
     * @return string Catégorie
     */
    public function getCategory()
    {
        return $this->category;
    }

    /**
     * Obtenir le nom
     *
     * @return string Nom
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Obtenir l'utilisateur GitHub
     *
     * @return string Utilisateur GitHub
     */
    public function getGitId()
    {
        return $this->gitId;
    }

    /**
     * Get the list of branches of the plugin
     *
     * @return array List of branches
     */
    public function getBranchesList()
    {
        return $this->branchesList;
    }

    /**
     * @return string
     */
    public function getIconPath()
    {
        return $this->iconPath;
    }
}