first commit
This commit is contained in:
506
inc/marketplace/api/plugins.class.php
Normal file
506
inc/marketplace/api/plugins.class.php
Normal file
@ -0,0 +1,506 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI 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 GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Marketplace\Api;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access directly to this file");
|
||||
}
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use \GuzzleHttp\Client as Guzzle_Client;
|
||||
use \GLPINetwork;
|
||||
use \Toolbox;
|
||||
use \Session;
|
||||
|
||||
class Plugins {
|
||||
protected $httpClient = null;
|
||||
protected $last_error = "";
|
||||
|
||||
public const COL_PAGE = 200;
|
||||
protected const TIMEOUT = 5;
|
||||
|
||||
static $plugins = [];
|
||||
|
||||
function __construct(bool $connect = false) {
|
||||
global $CFG_GLPI;
|
||||
|
||||
$options = [
|
||||
'base_uri' => GLPI_MARKETPLACE_PLUGINS_API_URI,
|
||||
'connect_timeout' => self::TIMEOUT,
|
||||
];
|
||||
|
||||
// add proxy string if configured in glpi
|
||||
if (!empty($CFG_GLPI["proxy_name"])) {
|
||||
$proxy_creds = !empty($CFG_GLPI["proxy_user"])
|
||||
? $CFG_GLPI["proxy_user"].":".Toolbox::sodiumDecrypt($CFG_GLPI["proxy_passwd"])."@"
|
||||
: "";
|
||||
$proxy_string = "http://{$proxy_creds}".$CFG_GLPI['proxy_name'].":".$CFG_GLPI['proxy_port'];
|
||||
$options['proxy'] = $proxy_string;
|
||||
}
|
||||
|
||||
// init guzzle client with base options
|
||||
$this->httpClient = new Guzzle_Client($options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a http request to services api
|
||||
* using the base url set in constructor and the current endpoint
|
||||
*
|
||||
* @param string $endpoint which resource whe need to query
|
||||
* @param array $options array of options for guzzle lib
|
||||
* @param string $method GET/POST, etc
|
||||
*
|
||||
* @return Psr\Http\Message\ResponseInterface|false
|
||||
*/
|
||||
private function request(
|
||||
string $endpoint = '',
|
||||
array $options = [],
|
||||
string $method = 'GET'
|
||||
) {
|
||||
if (!GLPINetwork::isRegistered()) {
|
||||
// Simulate empty response if registration key is not valid
|
||||
return new Response(200, [], '[]');
|
||||
}
|
||||
|
||||
$options['headers'] = array_merge_recursive(
|
||||
[
|
||||
'Accept' => 'application/json',
|
||||
'User-Agent' => GLPINetwork::getGlpiUserAgent(),
|
||||
'X-Registration-Key' => GLPINetwork::getRegistrationKey(),
|
||||
'X-Glpi-Network-Uid' => GLPINetwork::getGlpiNetworkUid(),
|
||||
],
|
||||
$options['headers'] ?? []
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request($method, $endpoint, $options);
|
||||
|
||||
} catch (RequestException $e) {
|
||||
$this->last_error = [
|
||||
'title' => "Plugins API error",
|
||||
'exception' => $e->getMessage(),
|
||||
'request' => Psr7\str($e->getRequest()),
|
||||
];
|
||||
if ($e->hasResponse()) {
|
||||
$this->last_error['response'] = Psr7\str($e->getResponse());
|
||||
}
|
||||
|
||||
if ($_SESSION['glpi_use_mode'] == Session::DEBUG_MODE) {
|
||||
Toolbox::logDebug($this->last_error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an http request on an endpoint accepting paginated queries
|
||||
*
|
||||
* @param string $endpoint which resource whe need to query
|
||||
* @param array $options array of options for guzzle lib
|
||||
* @param string $method GET/POST, etc
|
||||
*
|
||||
* @return array full collection
|
||||
*/
|
||||
private function getPaginatedCollection(
|
||||
string $endpoint = '',
|
||||
array $options = [],
|
||||
string $method = 'GET'
|
||||
): array {
|
||||
$collection = [];
|
||||
$i = 0;
|
||||
do {
|
||||
$request_options = array_merge_recursive([
|
||||
'headers' => [
|
||||
'X-Range' => ($i * self::COL_PAGE)."-".(($i + 1) * self::COL_PAGE - 1),
|
||||
],
|
||||
], $options);
|
||||
$response = $this->request($endpoint, $request_options, $method);
|
||||
|
||||
if ($current = ($response !== false ? json_decode($response->getBody(), true) : false)) {
|
||||
$collection = array_merge($collection, $current);
|
||||
}
|
||||
|
||||
$i++;
|
||||
} while ($current !== false && count($current));
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the full list of avaibles plugins on services API
|
||||
*
|
||||
* @param bool $force_refresh if false, we will return results stored in local cache
|
||||
* @param string $tag_filter filter the plugin list by given tag
|
||||
* @param string $string_filter filter the plugin list by given string
|
||||
* @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note
|
||||
*
|
||||
* @return array collection of plugins
|
||||
*/
|
||||
function getAllPlugins(
|
||||
bool $force_refresh = false,
|
||||
string $tag_filter = "",
|
||||
string $string_filter = "",
|
||||
string $sort = 'sort-alpha-asc'
|
||||
) {
|
||||
global $GLPI_CACHE;
|
||||
|
||||
$plugins_colct = [];
|
||||
if (!$force_refresh && $GLPI_CACHE->has('marketplace_all_plugins')) {
|
||||
$plugins_colct = $GLPI_CACHE->get('marketplace_all_plugins');
|
||||
}
|
||||
|
||||
if (!count($plugins_colct)) {
|
||||
$plugins = $this->getPaginatedCollection('plugins');
|
||||
|
||||
// replace keys indexes by system names
|
||||
$plugins_keys = array_column($plugins, 'key');
|
||||
$plugins_colct = array_combine($plugins_keys, $plugins);
|
||||
|
||||
foreach ($plugins_colct as &$plugin) {
|
||||
if (count($plugin['versions']) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!GLPI_MARKETPLACE_PRERELEASES) {
|
||||
$plugin['versions'] = array_filter($plugin['versions'], function($version) {
|
||||
return !isset($version['stability']) || $version['stability'] === "stable";
|
||||
});
|
||||
}
|
||||
|
||||
$first_found_version = current($plugin['versions']);
|
||||
if (is_array($first_found_version)) {
|
||||
$plugin['installation_url'] = $first_found_version['download_url'];
|
||||
$plugin['version'] = $first_found_version['num'];
|
||||
}
|
||||
}
|
||||
|
||||
self::$plugins = $plugins_colct;
|
||||
$GLPI_CACHE->set('marketplace_all_plugins', self::$plugins, HOUR_TIMESTAMP);
|
||||
}
|
||||
|
||||
if (strlen($tag_filter) > 0) {
|
||||
$tagged_plugins = array_column($this->getPluginsForTag($tag_filter), 'key');
|
||||
$plugins_colct = array_intersect_key($plugins_colct, array_flip($tagged_plugins));
|
||||
}
|
||||
|
||||
if (strlen($string_filter) > 0) {
|
||||
$plugins_colct = array_filter($plugins_colct, function($plugin) use ($string_filter) {
|
||||
return strpos(strtolower(json_encode($plugin)), strtolower($string_filter)) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
// manage sorting of collection
|
||||
uasort($plugins_colct, function($plugin1, $plugin2) use ($sort) {
|
||||
switch ($sort) {
|
||||
case "sort-alpha-asc":
|
||||
return strnatcasecmp($plugin1['name'], $plugin2['name']);
|
||||
case "sort-alpha-desc":
|
||||
return strnatcasecmp($plugin2['name'], $plugin1['name']);
|
||||
case "sort-dl":
|
||||
return strnatcmp($plugin2['download_count'], $plugin1['download_count']);
|
||||
case "sort-update":
|
||||
return strnatcmp($plugin2['date_updated'], $plugin1['date_updated']);
|
||||
case "sort-added":
|
||||
return strnatcmp($plugin2['date_added'], $plugin1['date_added']);
|
||||
case "sort-note":
|
||||
return strnatcmp($plugin2['note'], $plugin1['note']);
|
||||
}
|
||||
});
|
||||
|
||||
return $plugins_colct;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return plugins list for the given page
|
||||
*
|
||||
* @param bool $force_refresh if false, we will return results stored in local cache
|
||||
* @param string $tag_filter filter the plugin list by given tag
|
||||
* @param string $string_filter filter the plugin list by given string
|
||||
* @param int $page which page to query
|
||||
* @param int $nb_per_page how manyu per page we want
|
||||
* @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note
|
||||
*
|
||||
* @return array full collection
|
||||
*/
|
||||
function getPaginatedPlugins(
|
||||
bool $force_refresh = false,
|
||||
string $tag_filter = "",
|
||||
string $string_filter = "",
|
||||
int $page = 1,
|
||||
int $nb_per_page = 15,
|
||||
string $sort = 'sort-alpha-asc'
|
||||
) {
|
||||
$plugins = $this->getAllPlugins($force_refresh, $tag_filter, $string_filter, $sort);
|
||||
|
||||
$plugins_page = array_splice($plugins, max($page - 1, 0) * $nb_per_page, $nb_per_page);
|
||||
return $plugins_page;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return the number of available plugins in distant API
|
||||
*
|
||||
* @param string $tag_filter filter the plugin list by given tag
|
||||
*
|
||||
* @return int number of plugins
|
||||
*/
|
||||
function getNbPlugins(string $tag_filter = "") {
|
||||
$plugins = $this->getAllPlugins(false, $tag_filter);
|
||||
|
||||
return count($plugins);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get top 10 plugins sorted by trending (most downloaded in the last month) criterion
|
||||
*
|
||||
* @return array collection of plugins
|
||||
*/
|
||||
function getTrendingPlugins() {
|
||||
return $this->getTopPlugins("trending");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get top 10 plugins sorted by popular (most downloaded all time) criterion
|
||||
*
|
||||
* @return array collection of plugins
|
||||
*/
|
||||
function getPopularPlugins() {
|
||||
return $this->getTopPlugins("popular");
|
||||
}
|
||||
|
||||
/**
|
||||
* get top 10 plugins sorted by their submition date (DESC sort) criterion
|
||||
*
|
||||
* @return array collection of plugins
|
||||
*/
|
||||
function getNewPlugins() {
|
||||
return $this->getTopPlugins("new");
|
||||
}
|
||||
|
||||
/**
|
||||
* get top 10 plugins sorted by their update date (DESC sort) criterion
|
||||
*
|
||||
* @return array collection of plugins
|
||||
*/
|
||||
function getUpdatedPlugins() {
|
||||
return $this->getTopPlugins("updated");
|
||||
}
|
||||
|
||||
/**
|
||||
* get top 10 plugins sorted by given criterion (see other getTopXXX methods)
|
||||
*
|
||||
* @param string $endpoint criterion to filter plugsin
|
||||
*
|
||||
* @return array collection of plugins
|
||||
*/
|
||||
function getTopPlugins(string $endpoint = "") {
|
||||
$response = $this->request("plugin/{$endpoint}");
|
||||
|
||||
if ($response === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$top = json_decode($response->getBody(), true);
|
||||
$key_list = array_column($top, 'key', 'key');
|
||||
$plugins = $this->getAllPlugins();
|
||||
|
||||
$top_plugins = array_filter($plugins, function($plugin) use($key_list) {
|
||||
return in_array($plugin['key'], $key_list);
|
||||
});
|
||||
|
||||
return $top_plugins;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a single plugin array
|
||||
*
|
||||
* @param string $key plugin system name
|
||||
* @param bool $force_refresh if false, we will return results stored in local cache
|
||||
*
|
||||
* @return array plugin data
|
||||
*/
|
||||
public function getPlugin(string $key = "", bool $force_refresh = false): array {
|
||||
$plugins_list = [];
|
||||
if ($force_refresh || !count(self::$plugins)) {
|
||||
$plugins_list = $this->getAllPlugins($force_refresh);
|
||||
} else {
|
||||
$plugins_list = self::$plugins;
|
||||
}
|
||||
|
||||
return $plugins_list[$key] ?? [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inform plugins API that a plugin (by its key) has been downloaded
|
||||
* and the download counter must be incremented
|
||||
*
|
||||
* @param string $key plugin system key
|
||||
*
|
||||
* @return void we don't wait for a response, this a fire and forget request
|
||||
*/
|
||||
public function incrementPluginDownload(string $key = "") {
|
||||
$this->request(
|
||||
"plugin/{$key}/download",
|
||||
[
|
||||
'allow_redirects' => false, // Prevent follow redirects to download page sent by Plugins API
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get top list of tags for current session language
|
||||
*
|
||||
* @return array top tags
|
||||
*/
|
||||
public function getTopTags(): array {
|
||||
global $CFG_GLPI;
|
||||
|
||||
$response = $this->request('tags/top', [
|
||||
'headers' => [
|
||||
'X-Lang' => $CFG_GLPI['languages'][$_SESSION['glpilanguage']][2]
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$toptags = json_decode($response->getBody(), true);
|
||||
|
||||
return $toptags;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get a plugins collection for the givent tag
|
||||
*
|
||||
* @param string $tag to filter plugins
|
||||
* @param bool $force_refresh if false, we will return results stored in local cache
|
||||
*
|
||||
* @return array filtered plugin collection
|
||||
*/
|
||||
public function getPluginsForTag(string $tag = "", bool $force_refresh = false): array {
|
||||
global $GLPI_CACHE;
|
||||
|
||||
$plugins_colct = [];
|
||||
if (!$force_refresh && $GLPI_CACHE->has("marketplace_tag_$tag")) {
|
||||
$plugins_colct = $GLPI_CACHE->get("marketplace_tag_$tag");
|
||||
}
|
||||
|
||||
if (!count($plugins_colct)) {
|
||||
$plugins_colct = $this->getPaginatedCollection("tags/{$tag}/plugin");
|
||||
$GLPI_CACHE->set("marketplace_tag_$tag", $plugins_colct, HOUR_TIMESTAMP);
|
||||
}
|
||||
|
||||
return $plugins_colct;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download plugin archive and follow progress with a session var `marketplace_dl_progress`
|
||||
*
|
||||
* @param string $url where is the plugin
|
||||
* @param string $dest where we store it it
|
||||
* @param string $plugin_key plugin system name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function downloadArchive(string $url, string $dest, string $plugin_key, bool $track_progress = true): bool {
|
||||
if ($track_progress) {
|
||||
if (!isset($_SESSION['marketplace_dl_progress'])) {
|
||||
$_SESSION['marketplace_dl_progress'] = [];
|
||||
}
|
||||
$_SESSION['marketplace_dl_progress'][$plugin_key] = 0;
|
||||
}
|
||||
|
||||
// close session to permits polling of progress by frontend
|
||||
session_write_close();
|
||||
|
||||
$options = [
|
||||
'headers' => [
|
||||
'Accept' => '*/*',
|
||||
],
|
||||
'sink' => $dest,
|
||||
];
|
||||
if ($track_progress) {
|
||||
// track download progress
|
||||
$options['progress'] = function($downloadTotal, $downloadedBytes) use ($plugin_key) {
|
||||
// Prevent "net::ERR_RESPONSE_HEADERS_TOO_BIG" error
|
||||
// Each time Session::start() is called, PHP add a 'Set-Cookie' header,
|
||||
// so if a plugin takes more than a few seconds to be downloaded, PHP will set too many
|
||||
// 'Set-Cookie' headers and response will not be accepted by browser.
|
||||
// We can remove the 'Set-Cookie' here as it will be put back on next instruction (Session::start()).
|
||||
header_remove('Set-Cookie');
|
||||
|
||||
// restart session to store percentage of download for this plugin
|
||||
Session::start();
|
||||
|
||||
// calculate percent based on the size and store it in session
|
||||
$percent = 0;
|
||||
if ($downloadTotal > 0) {
|
||||
$percent = round($downloadedBytes * 100 / $downloadTotal);
|
||||
}
|
||||
$_SESSION['marketplace_dl_progress'][$plugin_key] = $percent;
|
||||
|
||||
// reclose session to avoid blocking ajax requests
|
||||
session_write_close();
|
||||
};
|
||||
}
|
||||
|
||||
$response = $this->request($url, $options);
|
||||
|
||||
// restart session to permits write of vars
|
||||
// (later, we also may have some addMessageAfterRedirect to provider errors to user)
|
||||
Session::start();
|
||||
|
||||
if ($track_progress) {
|
||||
// force finish of download (to avoid keeping js loop in case of errors)
|
||||
$_SESSION['marketplace_dl_progress'][$plugin_key] = 100;
|
||||
}
|
||||
|
||||
return $response !== false && $response->getStatusCode() === 200;
|
||||
}
|
||||
}
|
||||
485
inc/marketplace/controller.class.php
Normal file
485
inc/marketplace/controller.class.php
Normal file
@ -0,0 +1,485 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI 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 GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Marketplace;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access directly to this file");
|
||||
}
|
||||
|
||||
|
||||
use Glpi\Marketplace\Api\Plugins as PluginsApi;
|
||||
use \wapmorgan\UnifiedArchive\UnifiedArchive;
|
||||
use \wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
|
||||
use \Plugin;
|
||||
use \Toolbox;
|
||||
use \Session;
|
||||
use \GLPINetwork;
|
||||
use \CommonGLPI;
|
||||
use \Config;
|
||||
use \NotificationEvent;
|
||||
use \CronTask;
|
||||
|
||||
class Controller extends CommonGLPI {
|
||||
protected $plugin_key = "";
|
||||
|
||||
static $rightname = 'config';
|
||||
static $api = null;
|
||||
|
||||
const MP_REPLACE_ASK = 1;
|
||||
const MP_REPLACE_YES = 2;
|
||||
const MP_REPLACE_NEVER = 3;
|
||||
|
||||
function __construct(string $plugin_key = "") {
|
||||
$this->plugin_key = $plugin_key;
|
||||
}
|
||||
|
||||
|
||||
static function getTypeName($nb = 0) {
|
||||
return __('Marketplace');
|
||||
}
|
||||
|
||||
/**
|
||||
* singleton return the current api instance
|
||||
*
|
||||
* @return PluginsApi
|
||||
*/
|
||||
static function getAPI(): PluginsApi {
|
||||
return self::$api ?? (self::$api = new PluginsApi());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download and uncompress plugin archive
|
||||
*
|
||||
* @return int plugin status, @see properties of \Plugin class
|
||||
*/
|
||||
function downloadPlugin():int {
|
||||
if (!self::hasWriteAccess()) {
|
||||
return Plugin::UNKNOWN;
|
||||
}
|
||||
|
||||
$api = self::getAPI();
|
||||
$plugin = $api->getPlugin($this->plugin_key, true);
|
||||
|
||||
$url = $plugin['installation_url'] ?? "";
|
||||
$filename = basename(parse_url($url, PHP_URL_PATH));
|
||||
$dest = GLPI_TMP_DIR . '/' . $filename;
|
||||
|
||||
if (!$api->downloadArchive($url, $dest, $this->plugin_key)) {
|
||||
Session::addMessageAfterRedirect(
|
||||
__('Unable to download plugin archive.'),
|
||||
false,
|
||||
ERROR
|
||||
);
|
||||
return Plugin::UNKNOWN;
|
||||
}
|
||||
|
||||
// extract the archive
|
||||
if (!UnifiedArchive::canOpenArchive($dest)) {
|
||||
$type = UnifiedArchive::detectArchiveType($dest);
|
||||
Session::addMessageAfterRedirect(
|
||||
sprintf(__('Plugin archive format is not supported by your system : %s.'), $type),
|
||||
false,
|
||||
ERROR
|
||||
);
|
||||
return Plugin::UNKNOWN;
|
||||
}
|
||||
$archive = UnifiedArchive::open($dest);
|
||||
$error = $archive === null;
|
||||
if (!$error) {
|
||||
// clean dir in case of update
|
||||
Toolbox::deleteDir(GLPI_MARKETPLACE_DIR."/{$this->plugin_key}");
|
||||
|
||||
try {
|
||||
// copy files
|
||||
$archive->extractFiles(GLPI_MARKETPLACE_DIR) !== false;
|
||||
} catch (ArchiveExtractionException $e) {
|
||||
$error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
Session::addMessageAfterRedirect(
|
||||
__('Unable to extract plugin archive.'),
|
||||
false,
|
||||
ERROR
|
||||
);
|
||||
return Plugin::UNKNOWN;
|
||||
}
|
||||
|
||||
$plugin_inst = new Plugin();
|
||||
|
||||
if ($plugin_inst->getFromDBbyDir($this->plugin_key)
|
||||
&& !in_array($plugin_inst->fields['state'], [Plugin::ANEW, Plugin::NOTINSTALLED, Plugin::NOTUPDATED])) {
|
||||
// Plugin was already existing, make it "not updated" before checking its state
|
||||
// to prevent message like 'Plugin "xxx" version changed. It has been deactivated as its update process has to be launched.'.
|
||||
$plugin_inst->update([
|
||||
'id' => $plugin_inst->fields['id'],
|
||||
'state' => Plugin::NOTUPDATED
|
||||
]);
|
||||
}
|
||||
|
||||
$plugin_inst->checkPluginState($this->plugin_key);
|
||||
$plugin_inst->getFromDBbyDir($this->plugin_key);
|
||||
|
||||
// inform api the plugin has been downloaded
|
||||
$api->incrementPluginDownload($this->plugin_key);
|
||||
|
||||
// try to install (or update) directly the plugin
|
||||
return $this->installPlugin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get plugin archive from its download URL and serve it to the browser.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function proxifyPluginArchive(): void {
|
||||
// close session to prevent blocking other requests
|
||||
session_write_close();
|
||||
|
||||
$api = self::getAPI();
|
||||
$plugin = $api->getPlugin($this->plugin_key, true);
|
||||
|
||||
if (!array_key_exists('installation_url', $plugin) || empty($plugin['installation_url'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $plugin['installation_url'];
|
||||
$filename = basename(parse_url($url, PHP_URL_PATH));
|
||||
$dest = GLPI_TMP_DIR . '/' . mt_rand() . '.' . $filename;
|
||||
|
||||
if (!$api->downloadArchive($url, $dest, $this->plugin_key, false)) {
|
||||
http_response_code(500);
|
||||
echo(__('Unable to download plugin archive.'));
|
||||
return;
|
||||
}
|
||||
|
||||
Toolbox::sendFile($dest, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if plugin can be overwritten.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeOverwritten(): bool {
|
||||
foreach (PLUGINS_DIRECTORIES as $base_dir) {
|
||||
$is_in_marketplace_dir = realpath($base_dir) !== false
|
||||
&& realpath($base_dir) === realpath(GLPI_MARKETPLACE_DIR);
|
||||
|
||||
$plugin_dir = $base_dir . '/' . $this->plugin_key;
|
||||
$found_in_dir = file_exists($plugin_dir . '/setup.php');
|
||||
|
||||
if ($found_in_dir && !$is_in_marketplace_dir) {
|
||||
// Plugin will not be loaded from marketplace directory as
|
||||
// it has been found in an higher priority directory.
|
||||
// So it cannot be overrided.
|
||||
return false;
|
||||
} else if ($found_in_dir && $is_in_marketplace_dir) {
|
||||
return is_writable($plugin_dir);
|
||||
}
|
||||
|
||||
if ($is_in_marketplace_dir) {
|
||||
// Current directory is GLPI_MARKETPLACE_DIR, meaning that following
|
||||
// checked directories will have a lower priority than GLPI_MARKETPLACE_DIR
|
||||
// in autoload process.
|
||||
// No need to check them.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return self::hasWriteAccess();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given plugin has on update online
|
||||
*
|
||||
* @param Plugin $plugin_inst
|
||||
*
|
||||
* @return string|false new version number
|
||||
*/
|
||||
function checkUpdate(Plugin $plugin_inst = null) {
|
||||
$api = self::getAPI();
|
||||
$api_plugin = $api->getPlugin($this->plugin_key);
|
||||
$local_plugin = $plugin_inst->fields;
|
||||
|
||||
$api_version = $api_plugin['version'] ?? "";
|
||||
$local_version = $local_plugin['version'] ?? "";
|
||||
|
||||
if (strlen($api_version) && $api_version !== $local_version) {
|
||||
return $api_version;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for plugins updates
|
||||
* Parse all installed plugin and check against API if a news version is available
|
||||
*
|
||||
* @return array of [plugin_key => new_version_num]
|
||||
*/
|
||||
static function getAllUpdates() {
|
||||
$plugin_inst = new Plugin;
|
||||
$plugin_inst->init(true);
|
||||
$installed = $plugin_inst->getList();
|
||||
|
||||
$updates = [];
|
||||
|
||||
foreach ($installed as $plugin) {
|
||||
$plugin_key = $plugin['directory'];
|
||||
$plugin_inst->getFromDBbyDir($plugin_key);
|
||||
|
||||
$mk_controller = new self($plugin_key);
|
||||
if (false !== ($api_version = $mk_controller->checkUpdate($plugin_inst))) {
|
||||
$updates[$plugin_key] = $api_version;
|
||||
}
|
||||
}
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
|
||||
static function cronInfo($name) {
|
||||
return ['description' => __('Check all plugin updates')];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Crontask : Check for plugins updates
|
||||
*
|
||||
* @param CronTask|null $task to log, if NULL display (default NULL)
|
||||
*
|
||||
* @return integer 0 : nothing to do 1 : done with success
|
||||
*/
|
||||
static function cronCheckAllUpdates(CronTask $task = null):int {
|
||||
global $CFG_GLPI;
|
||||
|
||||
$cron_status = 0;
|
||||
|
||||
if (!GLPINetwork::isRegistered()) {
|
||||
return $cron_status;
|
||||
}
|
||||
|
||||
$updates = self::getAllUpdates();
|
||||
if (count($updates)) {
|
||||
$cron_status = 1;
|
||||
$task->addVolume(count($updates));
|
||||
foreach ($updates as $plugin_key => $version) {
|
||||
$task->log(sprintf(__("New version for plugin %s: %s"), $plugin_key, $version));
|
||||
}
|
||||
|
||||
if (!$CFG_GLPI["use_notifications"]) {
|
||||
return $cron_status;
|
||||
}
|
||||
|
||||
NotificationEvent::raiseEvent('checkpluginsupdate', new self(), [
|
||||
'plugins' => $updates
|
||||
]);
|
||||
}
|
||||
|
||||
return $cron_status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Do the current plugin requires some Glpi Network offers
|
||||
*
|
||||
* @return array [offer ref => offer title]
|
||||
*/
|
||||
function getRequiredOffers(): array {
|
||||
$api = self::getAPI();
|
||||
$api_plugin = $api->getPlugin($this->plugin_key);
|
||||
$offers = array_column(GLPINetwork::getOffers(), 'title', 'offer_reference');
|
||||
|
||||
$trans_offers = array_intersect_key($offers, array_flip($api_plugin['required_offers'] ?? []));
|
||||
|
||||
return $trans_offers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check a plugin can be download
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function canBeDownloaded() {
|
||||
$api = self::getAPI();
|
||||
$api_plugin = $api->getPlugin($this->plugin_key);
|
||||
|
||||
return strlen($api_plugin['installation_url'] ?? "") > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if plugin is eligible inside an higher offer.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function requiresHigherOffer(): bool {
|
||||
$api_plugin = self::getAPI()->getPlugin($this->plugin_key);
|
||||
|
||||
if (!isset($api_plugin['required_offers'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$registration_informations = GLPINetwork::getRegistrationInformations();
|
||||
if ($registration_informations['subscription'] !== null
|
||||
&& $registration_informations['subscription']['is_running']) {
|
||||
if (in_array($registration_informations['subscription']['offer_reference'], $api_plugin['required_offers'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Install current plugin
|
||||
*
|
||||
* @param bool $disable_messages drop any messages after plugin installation
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function installPlugin(bool $disable_messages = false):bool {
|
||||
$state = $this->setPluginState("install");
|
||||
|
||||
if ($disable_messages) {
|
||||
$_SESSION['MESSAGE_AFTER_REDIRECT'] = [];
|
||||
}
|
||||
|
||||
return $state == Plugin::NOTACTIVATED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ununstall current plugin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function uninstallPlugin():bool {
|
||||
return $this->setPluginState("uninstall") == Plugin::NOTINSTALLED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable current plugin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function enablePlugin():bool {
|
||||
return $this->setPluginState("activate") == Plugin::ACTIVATED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disable current plugin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function disablePlugin():bool {
|
||||
return $this->setPluginState("unactivate") == Plugin::NOTACTIVATED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clean (remove database data) current plugin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function cleanPlugin():bool {
|
||||
$plugin = new Plugin;
|
||||
if ($plugin->getFromDBbyDir($this->plugin_key)) {
|
||||
$plugin->clean($plugin->fields['id']);
|
||||
}
|
||||
|
||||
if (!$plugin->getFromDBbyDir($this->plugin_key)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if marketplace controller has write access to install/update plugins source code.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasWriteAccess(): bool {
|
||||
return is_dir(GLPI_MARKETPLACE_DIR) && is_writable(GLPI_MARKETPLACE_DIR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call an action method (install/enable/...) for the current plugin
|
||||
* method called internally by installPlugin, uninstallPlugin, enablePlugin, disablePlugin
|
||||
*
|
||||
* @param string $method
|
||||
*
|
||||
* @return int plugin status, @see properties of \Plugin class
|
||||
*/
|
||||
private function setPluginState(string $method = ""): int {
|
||||
ob_start();
|
||||
$plugin = new Plugin;
|
||||
$plugin->checkPluginState($this->plugin_key);
|
||||
if ($plugin->getFromDBbyDir($this->plugin_key)) {
|
||||
call_user_func([$plugin, $method], $plugin->fields['id']);
|
||||
}
|
||||
|
||||
$plugin->checkPluginState($this->plugin_key);
|
||||
$plugin->getFromDBbyDir($this->plugin_key);
|
||||
|
||||
// reload plugins
|
||||
$plugin->init(true);
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
return $plugin->fields['state'] ?? -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return current config of for the replacement of former plugins list
|
||||
*
|
||||
* @return int config status (self::MP_REPLACE_ASK, self::MP_REPLACE_YES, self::MP_REPLACE_NEVER)
|
||||
*/
|
||||
static function getPluginPageConfig() {
|
||||
$config = Config::getConfigurationValues('core', ['marketplace_replace_plugins']);
|
||||
|
||||
return (int) ($config['marketplace_replace_plugins'] ?? self::MP_REPLACE_ASK);
|
||||
}
|
||||
}
|
||||
133
inc/marketplace/notificationtargetcontroller.class.php
Normal file
133
inc/marketplace/notificationtargetcontroller.class.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI 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 GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Marketplace;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access directly to this file");
|
||||
}
|
||||
|
||||
use \Notification;
|
||||
use \NotificationTarget;
|
||||
use \Plugin;
|
||||
use \Session;
|
||||
|
||||
// Class NotificationTarget
|
||||
class NotificationTargetController extends NotificationTarget {
|
||||
|
||||
/**
|
||||
* Overwrite the function in NotificationTarget because there's only one target to be notified
|
||||
*
|
||||
* @see NotificationTarget::addNotificationTargets()
|
||||
**/
|
||||
function addNotificationTargets($entity) {
|
||||
|
||||
$this->addProfilesToTargets();
|
||||
$this->addGroupsToTargets($entity);
|
||||
$this->addTarget(Notification::GLOBAL_ADMINISTRATOR, __('Administrator'));
|
||||
}
|
||||
|
||||
|
||||
function getEvents() {
|
||||
return ['checkpluginsupdate' => __('Check all plugin updates')];
|
||||
}
|
||||
|
||||
|
||||
function addDataForTemplate($event, $options = []) {
|
||||
$updated_plugins = $options['plugins'];
|
||||
$plugin = new Plugin;
|
||||
foreach ($updated_plugins as $plugin_key => $version) {
|
||||
$plugin_info = $plugin->getInformationsFromDirectory($plugin_key);
|
||||
|
||||
$this->data['plugins'][] = [
|
||||
'##plugin.name##' => $plugin_info['name'],
|
||||
'##plugin.key##' => $plugin_key,
|
||||
'##plugin.version##' => $version,
|
||||
'##plugin.old_version##' => $plugin_info['version'],
|
||||
];
|
||||
}
|
||||
|
||||
$this->getTags();
|
||||
foreach ($this->tag_descriptions[NotificationTarget::TAG_LANGUAGE] as $tag => $values) {
|
||||
if (!isset($this->data[$tag])) {
|
||||
$this->data[$tag] = $values['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getTags() {
|
||||
//Tags with just lang
|
||||
$tags = [
|
||||
'plugins_updates_available' => __('Some updates are available for your installed plugins!')
|
||||
];
|
||||
|
||||
foreach ($tags as $tag => $label) {
|
||||
$this->addTagToList([
|
||||
'tag' => $tag,
|
||||
'label' => $label,
|
||||
'value' => false,
|
||||
'lang' => true
|
||||
]);
|
||||
}
|
||||
|
||||
//Foreach global tags
|
||||
$tags = [
|
||||
'plugins' => _n('Plugin', 'Plugins', Session::getPluralNumber()),
|
||||
];
|
||||
|
||||
foreach ($tags as $tag => $label) {
|
||||
$this->addTagToList([
|
||||
'tag' => $tag,
|
||||
'label' => $label,
|
||||
'value' => false,
|
||||
'foreach' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// sub tags
|
||||
$tags = [
|
||||
'plugin.name' => __('Plugin name'),
|
||||
'plugin.key' => __('Plugin directory'),
|
||||
'plugin.version' => __('Plugin new version number'),
|
||||
'plugin.old_version' => __('Plugin old version number')
|
||||
];
|
||||
|
||||
foreach ($tags as $tag => $label) {
|
||||
$this->addTagToList([
|
||||
'tag' => $tag,
|
||||
'label' => $label,
|
||||
'value' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
969
inc/marketplace/view.class.php
Normal file
969
inc/marketplace/view.class.php
Normal file
@ -0,0 +1,969 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI 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 GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Marketplace;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access directly to this file");
|
||||
}
|
||||
|
||||
use Glpi\Marketplace\Api\Plugins as PluginsApi;
|
||||
use Glpi\Marketplace\Controller as Controller;
|
||||
use \Html;
|
||||
use \Plugin;
|
||||
use \Config;
|
||||
use \CommonGLPI;
|
||||
use \GLPINetwork;
|
||||
use \Toolbox;
|
||||
|
||||
class View extends CommonGLPI {
|
||||
static $rightname = 'config';
|
||||
static $api = null;
|
||||
|
||||
public $get_item_to_display_tab = true;
|
||||
|
||||
|
||||
public const COL_PAGE = 12;
|
||||
|
||||
/**
|
||||
* singleton return the current api instance
|
||||
*
|
||||
* @return PluginsApi
|
||||
*/
|
||||
static function getAPI(): PluginsApi {
|
||||
return self::$api ?? (self::$api = new PluginsApi());
|
||||
}
|
||||
|
||||
|
||||
static function getTypeName($nb = 0) {
|
||||
return __('Marketplace');
|
||||
}
|
||||
|
||||
|
||||
static function canCreate() {
|
||||
return self::canUpdate();
|
||||
}
|
||||
|
||||
|
||||
static function getIcon() {
|
||||
return "fas fa-store";
|
||||
}
|
||||
|
||||
|
||||
static function getSearchURL($full = true) {
|
||||
global $CFG_GLPI;
|
||||
|
||||
$dir = ($full ? $CFG_GLPI['root_doc'] : '');
|
||||
return "$dir/front/marketplace.php";
|
||||
}
|
||||
|
||||
|
||||
function defineTabs($options = []) {
|
||||
$tabs = [
|
||||
'no_all_tab' => true
|
||||
];
|
||||
$this->addStandardTab(__CLASS__, $tabs, $options);
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
|
||||
function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
|
||||
if ($item->getType() == __CLASS__) {
|
||||
return [
|
||||
self::createTabEntry(__("Installed")),
|
||||
self::createTabEntry(__("Discover")),
|
||||
];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
|
||||
if ($item->getType() == __CLASS__) {
|
||||
switch ($tabnum) {
|
||||
case 0:
|
||||
self::installed();
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
self::discover();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check current reigstration status and display warning messages
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function checkRegister() {
|
||||
global $CFG_GLPI;
|
||||
|
||||
$messages = [];
|
||||
$registered = false;
|
||||
|
||||
if (!GLPINetwork::isServicesAvailable()) {
|
||||
array_push(
|
||||
$messages,
|
||||
sprintf(__("%1$s services website seems not available from your network or offline"), 'GLPI Network'),
|
||||
"<a href='".$CFG_GLPI['root_doc']."/front/config.form.php?forcetab=Config$5'>".
|
||||
__("Maybe you could setup a proxy").
|
||||
"</a> ".
|
||||
__("or please check later")
|
||||
);
|
||||
} else {
|
||||
$registered = GLPINetwork::isRegistered();
|
||||
if (!$registered) {
|
||||
$config_url = $CFG_GLPI['root_doc']."/front/config.form.php?forcetab=".
|
||||
urlencode('GLPINetwork$1');
|
||||
|
||||
array_push(
|
||||
$messages,
|
||||
sprintf(__('Your %1$s registration is not valid.'), 'GLPI Network'),
|
||||
__('A registration, at least a free one, is required to use marketplace!'),
|
||||
"<a href='".GLPI_NETWORK_SERVICES."'>".sprintf(__('Register on %1$s'), 'GLPI Network')."</a> ".
|
||||
__('and'). " ".
|
||||
"<a href='$config_url'>".__("fill your registration key in setup.")."</a>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($messages)) {
|
||||
echo "<div class='warning'>";
|
||||
echo "<i class='fa fa-exclamation-triangle fa-5x'></i>";
|
||||
echo "<ul><li>" . implode('</li><li>', $messages) . "</li></ul>";
|
||||
echo "<div class='sep'></div>";
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
return $registered;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display installed tab (only currently installed plugins)
|
||||
*
|
||||
* @param bool $force_refresh do not rely on cache to get plugins list
|
||||
* @param bool $only_lis display only the li tags in return html (used by ajax queries)
|
||||
* @param string $tag_filter filter the plugin list by given tag
|
||||
* @param string $string_filter filter the plugin by given string
|
||||
*
|
||||
* @return void display things
|
||||
*/
|
||||
static function installed(
|
||||
bool $force_refresh = false,
|
||||
bool $only_lis = false,
|
||||
string $string_filter = ""
|
||||
) {
|
||||
|
||||
$plugin_inst = new Plugin;
|
||||
$plugin_inst->init(true); // reload plugins
|
||||
$installed = $plugin_inst->getList();
|
||||
|
||||
$apiplugins = [];
|
||||
if (self::checkRegister()) {
|
||||
$api = self::getAPI();
|
||||
$apiplugins = $api->getAllPlugins($force_refresh);
|
||||
}
|
||||
|
||||
$plugins = [];
|
||||
foreach ($installed as $plugin) {
|
||||
$key = $plugin['directory'];
|
||||
$apidata = $apiplugins[$key] ?? [];
|
||||
|
||||
if (strlen($string_filter)
|
||||
&& strpos(strtolower(json_encode($plugin)), strtolower($string_filter)) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$clean_plugin = [
|
||||
'key' => $key,
|
||||
'name' => $plugin['name'],
|
||||
'logo_url' => $apidata['logo_url'] ?? "",
|
||||
'description' => $apidata['descriptions'][0]['short_description'] ?? "",
|
||||
'authors' => $apidata['authors'] ?? [['id' => 'all', 'name' => $plugin['author'] ?? ""]],
|
||||
'license' => $apidata['license'] ?? $plugin['license'] ?? "",
|
||||
'note' => $apidata['note'] ?? -1,
|
||||
'homepage_url' => $apidata['homepage_url'] ?? "",
|
||||
'issues_url' => $apidata['issues_url'] ?? "",
|
||||
'readme_url' => $apidata['readme_url'] ?? "",
|
||||
'version' => $plugin['version'] ?? "",
|
||||
];
|
||||
|
||||
$plugins[] = $clean_plugin;
|
||||
}
|
||||
|
||||
self::displayList($plugins, "installed", $only_lis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display discover tab (all availble plugins)
|
||||
*
|
||||
* @param bool $force_refresh do not rely on cache to get plugins list
|
||||
* @param bool $only_lis display only the li tags in return html (used by ajax queries)
|
||||
* @param string $tag_filter filter the plugin list by given tag
|
||||
* @param string $string_filter filter the plugin by given string
|
||||
* @param int $page What's sub page of plugin we want to display
|
||||
* @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note
|
||||
*
|
||||
* @return void display things
|
||||
*/
|
||||
static function discover(
|
||||
bool $force = false,
|
||||
bool $only_lis = false,
|
||||
string $tag_filter = "",
|
||||
string $string_filter = "",
|
||||
int $page = 1,
|
||||
string $sort = 'sort-alpha-asc'
|
||||
) {
|
||||
if (!self::checkRegister()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$api = self::getAPI();
|
||||
$plugins = $api->getPaginatedPlugins(
|
||||
$force,
|
||||
$tag_filter,
|
||||
$string_filter,
|
||||
$page,
|
||||
self::COL_PAGE,
|
||||
$sort
|
||||
);
|
||||
|
||||
if (strlen($string_filter) > 0) {
|
||||
$nb_plugins = count($plugins);
|
||||
} else {
|
||||
$nb_plugins = $api->getNbPlugins($tag_filter);
|
||||
}
|
||||
|
||||
header("X-GLPI-Marketplace-Total: $nb_plugins");
|
||||
self::displayList($plugins, "discover", $only_lis, $nb_plugins, $sort);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return HTML part for tags list
|
||||
*
|
||||
* @return string tags list
|
||||
*/
|
||||
static function getTagsHtml() {
|
||||
$api = self::getAPI();
|
||||
$tags = $api->getTopTags();
|
||||
|
||||
$tags_li = "<li class='tag active' data-tag=''>".__("All")."</li>";
|
||||
foreach ($tags as $tag) {
|
||||
$tags_li.= "<li class='tag' data-tag='{$tag['key']}'>".ucfirst($tag['tag'])."</li>";
|
||||
}
|
||||
|
||||
return "<ul class='plugins-tags'>{$tags_li}</ul>";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display a list of plugins
|
||||
*
|
||||
* @param array $plugins list of plugins returned by
|
||||
* - \Plugin::getList
|
||||
* - \Glpi\Marketplace\Api\Plugins::getPaginatedPlugins
|
||||
* @param string $tab current display tab (discover or installed)
|
||||
* @param bool $only_lis display only the li tags in return html (used by ajax queries)
|
||||
* @param int $nb_plugins total of plugins ($plugins contains only the current page)
|
||||
* @param string $sort sort-alpha-asc|sort-alpha-desc|sort-dl|sort-update|sort-added|sort-note
|
||||
*
|
||||
* @return false|void displays things
|
||||
*/
|
||||
static function displayList(
|
||||
array $plugins = [],
|
||||
string $tab = "",
|
||||
bool $only_lis = false,
|
||||
int $nb_plugins = 0,
|
||||
string $sort = 'sort-alpha-asc'
|
||||
) {
|
||||
if (!self::canView()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$plugins_li = "";
|
||||
foreach ($plugins as $plugin) {
|
||||
$plugin['description'] = self::getLocalizedDescription($plugin);
|
||||
$plugins_li.= self::getPluginCard($plugin, $tab);
|
||||
}
|
||||
|
||||
if (!$only_lis) {
|
||||
// check writable state
|
||||
if (!Controller::hasWriteAccess()) {
|
||||
echo "<div class='warning'>
|
||||
<i class='fa fa-exclamation-triangle fa-5x'></i>".
|
||||
sprintf(__("We can't write on the markeplace directory (%s)."), GLPI_MARKETPLACE_DIR)."<br>".
|
||||
__("If you want to ease the plugins download, please check permissions and ownership of this directory.")."<br>".
|
||||
__("Otherwise, you will need to download and unzip the plugins archives manually.")."<br>".
|
||||
"<br></div>";
|
||||
}
|
||||
|
||||
$tags_list = $tab != "installed"
|
||||
? "<div class='left-panel'>".self::getTagsHtml()."</div>"
|
||||
: "";
|
||||
$pagination = $tab != "installed"
|
||||
? self::getPaginationHtml(1, $nb_plugins)
|
||||
: "";
|
||||
$sort_controls = "";
|
||||
if ($tab === "discover") {
|
||||
$sort_controls = "
|
||||
<select class='sort-control'>
|
||||
<option value='sort-alpha-asc'
|
||||
".($sort == "sort-alpha-asc" ? "selected" : "")."
|
||||
data-icon='fas fa-lg fa-sort-alpha-down'>
|
||||
".__("Alpha ASC")."
|
||||
</option>
|
||||
<option value='sort-alpha-desc'
|
||||
".($sort == "sort-alpha-desc" ? "selected" : "")."
|
||||
data-icon='fas fa-lg fa-sort-alpha-down-alt'>
|
||||
".__("Alpha DESC")."
|
||||
</option>
|
||||
<option value='sort-dl'
|
||||
".($sort == "sort-dl'" ? "selected" : "")."
|
||||
data-icon='fas fa-lg fa-cloud-download-alt'>
|
||||
".__("Most popular")."
|
||||
</option>
|
||||
<option value='sort-update'
|
||||
".($sort == "sort-update'" ? "selected" : "")."
|
||||
data-icon='fas fa-lg fa-history'>
|
||||
".__("Last updated")."
|
||||
</option>
|
||||
<option value='sort-added'
|
||||
".($sort == "sort-added'" ? "selected" : "")."
|
||||
data-icon='fas fa-lg fa-calendar-plus'>
|
||||
".__("Most recent")."
|
||||
</option>
|
||||
<option value='sort-note'
|
||||
".($sort == "sort-note'" ? "selected" : "")."
|
||||
data-icon='fas fa-lg fa-star'>
|
||||
".__("Best notes")."
|
||||
</option>
|
||||
</select>";
|
||||
}
|
||||
|
||||
$yourplugin = __("Your plugin here ? Contact us.");
|
||||
$networkmail = GLPI_NETWORK_MAIL;
|
||||
$refresh_lbl = __("Refresh plugin list");
|
||||
$search_label = __("Filter plugin list");
|
||||
|
||||
$marketplace = <<<HTML
|
||||
<div class='marketplace $tab' data-tab='{$tab}'>
|
||||
{$tags_list}
|
||||
<div class='right-panel'>
|
||||
<div class='top-panel'>
|
||||
<input type='search' class='filter-list' placeholder='{$search_label}'>
|
||||
<div class='controls'>
|
||||
$sort_controls
|
||||
<i class='fas fa-sync-alt refresh-plugin-list'
|
||||
title='{$refresh_lbl}'></i>
|
||||
</div>
|
||||
</div>
|
||||
<ul class='plugins'>
|
||||
{$plugins_li}
|
||||
</ul>
|
||||
$pagination
|
||||
<a href="mailto:{$networkmail}" class="network-mail" target="_blank">
|
||||
$yourplugin <i class="far fa-envelope"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var marketplace_total_plugin = {$nb_plugins};
|
||||
</script>
|
||||
HTML;
|
||||
echo $marketplace;
|
||||
} else {
|
||||
echo $plugins_li;
|
||||
}
|
||||
|
||||
$js = <<<JS
|
||||
$(document).ready(function() {
|
||||
// load button tooltips
|
||||
addTooltips();
|
||||
|
||||
var displaySortIcon = function(option) {
|
||||
if (!option.element) {
|
||||
return option.element;
|
||||
}
|
||||
var element = option.element;
|
||||
var icon = $(element).data('icon');
|
||||
|
||||
return $("<span><i class='"+icon+"'></i> "+option.text+"</span>");
|
||||
};
|
||||
|
||||
$('.sort-control').select2({
|
||||
templateResult: displaySortIcon,
|
||||
templateSelection: displaySortIcon,
|
||||
width: 135,
|
||||
});
|
||||
});
|
||||
JS;
|
||||
echo Html::scriptBlock($js);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTML part for plugin card
|
||||
*
|
||||
* @param array $plugin informations (title, description, etc) of the plugins
|
||||
* @param string $tab current displayed tab (installed or discover)
|
||||
*
|
||||
* @return string the plugin card
|
||||
*/
|
||||
static function getPluginCard(array $plugin = [], string $tab = "discover"):string {
|
||||
$plugin_key = $plugin['key'];
|
||||
$plugin_inst = new Plugin;
|
||||
$plugin_inst->getFromDBbyDir($plugin_key);
|
||||
$plugin_state = Plugin::getStateKey($plugin_inst->fields['state'] ?? -1);
|
||||
$buttons = self::getButtons($plugin_key);
|
||||
|
||||
$authors = implode(', ', array_column($plugin['authors'] ?? [], 'name', 'id'));
|
||||
$authors_title = Html::clean($authors);
|
||||
$authors = strlen($authors)
|
||||
? "<i class='fas fa-fw fa-user-friends'></i>$authors"
|
||||
: "";
|
||||
$licence = is_string($plugin['license']) && isset($plugin['license']) && strlen($plugin['license'])
|
||||
? "<i class='fas fa-fw fa-balance-scale'></i>{$plugin['license']}"
|
||||
: "";
|
||||
$version = strlen($plugin['version'] ?? "")
|
||||
? "<i class='fas fa-fw fa-code-branch'></i>".$plugin['version']
|
||||
: "";
|
||||
$stars = ($plugin['note'] ?? -1) > 0
|
||||
? self::getStarsHtml($plugin['note'])
|
||||
: "";
|
||||
|
||||
$home_url = strlen($plugin['homepage_url'] ?? "")
|
||||
? "<a href='{$plugin['homepage_url']}' target='_blank' >
|
||||
<i class='fas fa-home add_tooltip' title='".__s("Homepage")."'></i>
|
||||
</a>"
|
||||
: "";
|
||||
$issues_url = strlen($plugin['issues_url'] ?? "")
|
||||
? "<a href='{$plugin['issues_url']}' target='_blank' >
|
||||
<i class='fas fa-bug add_tooltip' title='".__s("Get help")."'></i>
|
||||
</a>"
|
||||
: "";
|
||||
$readme_url = strlen($plugin['readme_url'] ?? "")
|
||||
? "<a href='{$plugin['readme_url']}' target='_blank' >
|
||||
<i class='fas fa-book add_tooltip' title='".__s("Readme")."'></i>
|
||||
</a>"
|
||||
: "";
|
||||
$icon = self::getPluginIcon($plugin);
|
||||
$network = self::getNetworkInformations($plugin);
|
||||
|
||||
if ($tab === "discover") {
|
||||
$card = <<<HTML
|
||||
<li class="plugin {$plugin_state}" data-key="{$plugin_key}">
|
||||
<div class="main">
|
||||
<span class="icon">{$icon}</span>
|
||||
<span class="details">
|
||||
<h3 class="title">{$plugin['name']}</h3>
|
||||
$network
|
||||
<p class="description">{$plugin['description']}</p>
|
||||
</span>
|
||||
<span class="buttons">
|
||||
{$buttons}
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span class="misc-left">
|
||||
<div class="note">{$stars}</div>
|
||||
<div class="links">
|
||||
{$home_url}
|
||||
{$issues_url}
|
||||
{$readme_url}
|
||||
</div>
|
||||
</span>
|
||||
<span class='misc-right'>
|
||||
<div class="license">{$licence}</div>
|
||||
<div class="authors" title="{$authors_title}">{$authors}</div>
|
||||
<div class="version">{$version}</div>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
HTML;
|
||||
} else {
|
||||
$card = <<<HTML
|
||||
<li class="plugin {$plugin_state}" data-key="{$plugin_key}">
|
||||
<div class="main">
|
||||
<span class="icon">{$icon}</span>
|
||||
<span class="details">
|
||||
<h3 class="title">{$plugin['name']}</h3>
|
||||
<span class='misc-right'>
|
||||
<div class="license">{$licence}</div>
|
||||
<div class="authors" title="{$authors_title}">{$authors}</div>
|
||||
<div class="version">{$version}</div>
|
||||
</span>
|
||||
</span>
|
||||
<span class="buttons">
|
||||
{$buttons}
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span class="misc-left">
|
||||
<div class="links">
|
||||
{$home_url}
|
||||
{$issues_url}
|
||||
{$readme_url}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
HTML;
|
||||
}
|
||||
|
||||
return $card;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTML part for plugin stars
|
||||
*
|
||||
* @param float|int $value current stars note on 5
|
||||
*
|
||||
* @return string plugins stars html
|
||||
*/
|
||||
static function getStarsHtml(float $value = 0):string {
|
||||
$value = min(floor($value * 2) / 2, 5);
|
||||
|
||||
$stars = "";
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
if ($value >= $i) {
|
||||
$stars.= "<i class='fas fa-star'></i>";
|
||||
} else if ($value + 0.5 == $i) {
|
||||
$stars.= "<i class='fas fa-star-half-alt'></i>";
|
||||
} else {
|
||||
$stars.= "<i class='far fa-star'></i>";
|
||||
}
|
||||
}
|
||||
|
||||
return $stars;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return HTML part for plugin buttons
|
||||
*
|
||||
* @param string $plugin_key system name for the plugin
|
||||
*
|
||||
* @return string the buttons html
|
||||
*/
|
||||
static function getButtons(string $plugin_key = ""): string {
|
||||
global $CFG_GLPI, $PLUGIN_HOOKS;
|
||||
|
||||
$rand = mt_rand();
|
||||
$plugin_inst = new Plugin;
|
||||
$exists = $plugin_inst->getFromDBbyDir($plugin_key);
|
||||
$is_installed = $plugin_inst->isInstalled($plugin_key);
|
||||
$is_actived = $plugin_inst->isActivated($plugin_key);
|
||||
$mk_controller = new Controller($plugin_key);
|
||||
$web_update_version = $mk_controller->checkUpdate($plugin_inst);
|
||||
$has_web_update = $web_update_version !== false;
|
||||
$has_loc_update = $plugin_inst->isUpdatable($plugin_key);
|
||||
$can_be_overwritten = $mk_controller->canBeOverwritten();
|
||||
$can_be_downloaded = $mk_controller->canBeDownloaded();
|
||||
$required_offers = $mk_controller->getRequiredOffers();
|
||||
$can_be_updated = $has_web_update && $can_be_overwritten;
|
||||
$can_be_cleaned = $exists && !$plugin_inst->isLoadable($plugin_key);
|
||||
$config_page = $PLUGIN_HOOKS['config_page'][$plugin_key] ?? "";
|
||||
|
||||
$error = "";
|
||||
if ($exists) {
|
||||
ob_start();
|
||||
$do_activate = $plugin_inst->checkVersions($plugin_key);
|
||||
if (!$do_activate) {
|
||||
$error.= "<span class='error'>" . ob_get_contents() . "</span>";
|
||||
}
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
$buttons = "";
|
||||
|
||||
if (strlen($error)) {
|
||||
$buttons .="<i class='fas fa-exclamation-triangle plugin-error' id='plugin-error-$rand'></i>";
|
||||
Html::showToolTip($error, [
|
||||
'applyto' => "plugin-error-$rand",
|
||||
]);
|
||||
}
|
||||
|
||||
if ($can_be_cleaned) {
|
||||
$buttons .="<button class='modify_plugin'
|
||||
data-action='clean_plugin'
|
||||
title='".__s("Clean")."'>
|
||||
<i class='fas fa-broom'></i>
|
||||
</button>";
|
||||
} else if ((!$exists && !$mk_controller->hasWriteAccess())
|
||||
|| ($has_web_update && !$can_be_overwritten)) {
|
||||
$plugin_data = $mk_controller->getAPI()->getPlugin($plugin_key);
|
||||
if (array_key_exists('installation_url', $plugin_data) && $can_be_downloaded) {
|
||||
$warning = "";
|
||||
if ($has_web_update) {
|
||||
$warning = __s("The plugin has an available update but its directory is not writable.")."<br>";
|
||||
}
|
||||
|
||||
$warning.= sprintf(
|
||||
__s("Download archive manually, you must uncompress it in plugins directory (%s)"),
|
||||
GLPI_ROOT . '/plugins'
|
||||
);
|
||||
|
||||
// Use "marketplace.download.php" proxy if archive is downloadable from GLPI marketplace plugins API
|
||||
// as this API will refuse to serve the archive if registration key is not set in headers.
|
||||
$download_url = Toolbox::startsWith($plugin_data['installation_url'], GLPI_MARKETPLACE_PLUGINS_API_URI)
|
||||
? $CFG_GLPI['root_doc'] . '/front/marketplace.download.php?key=' . $plugin_key
|
||||
: $plugin_data['installation_url'];
|
||||
|
||||
$buttons .="<a href='{$download_url}' target='_blank'>
|
||||
<button title='$warning' class='add_tooltip download_manually'><i class='fas fa-archive'></i></button>
|
||||
</a>";
|
||||
}
|
||||
} else if ($can_be_downloaded) {
|
||||
if (!$exists) {
|
||||
$buttons .="<button class='modify_plugin'
|
||||
data-action='download_plugin'
|
||||
title='".__s("Download")."'>
|
||||
<i class='fas fa-cloud-download-alt'></i>
|
||||
</button>";
|
||||
} else if ($can_be_updated) {
|
||||
$update_title = sprintf(
|
||||
__s("A new version (%s) is available, update ?", 'marketplace'),
|
||||
$web_update_version
|
||||
);
|
||||
$buttons .="<button class='modify_plugin'
|
||||
data-action='update_plugin'
|
||||
title='{$update_title}'>
|
||||
<i class='fas fa-cloud-download-alt'></i>
|
||||
</button>";
|
||||
}
|
||||
}
|
||||
|
||||
if ($mk_controller->requiresHigherOffer()) {
|
||||
$warning = sprintf(
|
||||
__s("You need a superior GLPI-Network offer to access to this plugin (%s)"),
|
||||
implode(', ', $required_offers)
|
||||
);
|
||||
|
||||
$buttons .="<a href='".GLPI_NETWORK_SERVICES."' target='_blank'>
|
||||
<button class='add_tooltip need_offers' title='$warning'>
|
||||
<i class='fas fa-exclamation-triangle'></i>
|
||||
</button>
|
||||
</a>";
|
||||
}
|
||||
|
||||
if ($exists && !$can_be_cleaned && !$is_installed && !strlen($error)) {
|
||||
$title = __s("Install");
|
||||
$icon = "fas fa-folder-plus";
|
||||
if ($has_loc_update) {
|
||||
$title = __s("Update");
|
||||
$icon = "far fa-caret-square-up";
|
||||
}
|
||||
$buttons .="<button class='modify_plugin'
|
||||
data-action='install_plugin'
|
||||
title='$title'>
|
||||
<i class='$icon'></i>
|
||||
</button>";
|
||||
}
|
||||
|
||||
if ($is_installed) {
|
||||
if (!strlen($error)) {
|
||||
if ($is_actived) {
|
||||
$buttons .="<button class='modify_plugin'
|
||||
data-action='disable_plugin'
|
||||
title='".__s("Disable")."'>
|
||||
<i class='fas fa-toggle-on'></i>
|
||||
</button>";
|
||||
} else {
|
||||
$buttons .="<button class='modify_plugin'
|
||||
data-action='enable_plugin'
|
||||
title='".__s("Enable")."'>
|
||||
<i class='fas fa-toggle-off'></i>
|
||||
</button>";
|
||||
}
|
||||
}
|
||||
|
||||
$buttons .="<button class='modify_plugin'
|
||||
data-action='uninstall_plugin'
|
||||
title='".__s("Uninstall")."'>
|
||||
<i class='fas fa-folder-minus'></i>
|
||||
</button>";
|
||||
|
||||
if (!strlen($error) && $is_actived && $config_page) {
|
||||
$plugin_dir = Plugin::getWebDir($plugin_key, true);
|
||||
$config_url = "$plugin_dir/$config_page";
|
||||
$buttons .="<a href='$config_url'>
|
||||
<button class='add_tooltip' title='".__s("Configure")."'>
|
||||
<i class='fas fa-wrench'></i>
|
||||
</button>
|
||||
</a>";
|
||||
}
|
||||
}
|
||||
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTML part for plugin logo/icon
|
||||
*
|
||||
* @param array $plugin data of the plugin.
|
||||
* If it contains a key logo_url, the current will be inserted in a img tag
|
||||
* else, it will use initials from plugin friendly name to construct
|
||||
* a short and colored logo
|
||||
*
|
||||
* @return string the jtml for plugin logo
|
||||
*/
|
||||
static function getPluginIcon(array $plugin = []) {
|
||||
$icon = "";
|
||||
if (strlen($plugin['logo_url'])) {
|
||||
$icon = "<img src='{$plugin['logo_url']}'>";
|
||||
} else {
|
||||
$words = explode(" ", $plugin['name']);
|
||||
$initials = "";
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
if (isset($words[$i])) {
|
||||
$initials.= mb_substr($words[$i], 0, 1);
|
||||
}
|
||||
}
|
||||
$bg_color = Toolbox::getColorForString($initials);
|
||||
$fg_color = Toolbox::getFgColor($bg_color);
|
||||
$icon = "<span style='background-color: $bg_color; color: $fg_color'
|
||||
class='icon-text'>$initials</span>";
|
||||
}
|
||||
|
||||
return $icon;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return HTML part for Glpi Network informations for a given plugin
|
||||
* @param array $plugin data of the plugin.
|
||||
* if check agains plugin key if we need some subscription to use it
|
||||
* @return string the subscription information html
|
||||
*/
|
||||
static function getNetworkInformations(array $plugin = []): string {
|
||||
$mk_controller = new Controller($plugin['key']);
|
||||
$require_offers = $mk_controller->getRequiredOffers();
|
||||
|
||||
$html = "";
|
||||
if (count($require_offers)) {
|
||||
$fst_offer = array_splice($require_offers, 0, 1);
|
||||
$offerkey = key($fst_offer);
|
||||
$offerlabel = current($fst_offer);
|
||||
|
||||
$html = "<div class='offers'>
|
||||
<a href='".GLPI_NETWORK_SERVICES."' target='_blank'
|
||||
class='badge glpi-network'
|
||||
title='".sprintf(__s("You must have a %s subscription to get this plugin"), 'GLPI Network')."'>
|
||||
<i class='fas fa-star'></i>GLPI Network
|
||||
</a>
|
||||
<a href='".GLPI_NETWORK_SERVICES."' target='_blank'
|
||||
class='badge $offerkey'
|
||||
title='".sprintf(__s("You need at least the %s subscription level to get this plugin"), $offerlabel)."'>
|
||||
$offerlabel
|
||||
</a>
|
||||
</div>";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve localized description for a given plugin and matching the session lang
|
||||
*
|
||||
* @param array $plugin data of the plugin.
|
||||
* in the `description` key, we must found an array of localized descirption
|
||||
* indexed by lang key, return the good one
|
||||
* @param string $version short_description or long_description
|
||||
*
|
||||
* @return string the localized description
|
||||
*/
|
||||
static function getLocalizedDescription(array $plugin = [], string $version = 'short_description'): string {
|
||||
global $CFG_GLPI;
|
||||
|
||||
$userlang = $CFG_GLPI['languages'][$_SESSION['glpilanguage']][3] ?? "en";
|
||||
|
||||
if (!isset($plugin['descriptions'])) {
|
||||
return "";
|
||||
}
|
||||
|
||||
$description = "";
|
||||
$fallback = "";
|
||||
foreach ($plugin['descriptions'] as $current) {
|
||||
if ($current['lang'] == $userlang) {
|
||||
$description = $current[$version];
|
||||
break;
|
||||
}
|
||||
|
||||
if ($current['lang'] == "en") {
|
||||
$fallback = $current[$version];
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($description) === 0) {
|
||||
$description = $fallback;
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return HTML part for plugins pagination
|
||||
*
|
||||
* @param int $current_page
|
||||
* @param int $total
|
||||
* @param bool $only_li display only the li tags in return html (used by ajax queries)
|
||||
*
|
||||
* @return string the pagination html
|
||||
*/
|
||||
static function getPaginationHtml(int $current_page = 1, int $total = 1, bool $only_li = false): string {
|
||||
if ($total <= self::COL_PAGE) {
|
||||
return "";
|
||||
}
|
||||
|
||||
$nb_pages = ceil($total / self::COL_PAGE);
|
||||
|
||||
$prev = max($current_page - 1, 1);
|
||||
$next = min($current_page + 1, $nb_pages);
|
||||
|
||||
$p_cls = $current_page === 1
|
||||
? "class='nav-disabled'"
|
||||
: "";
|
||||
$n_cls = $current_page == $nb_pages
|
||||
? "class='nav-disabled'"
|
||||
: "";
|
||||
|
||||
$html = "";
|
||||
if (!$only_li) {
|
||||
$html.= "<ul class='pagination'>";
|
||||
}
|
||||
$html.= "<li data-page='$prev' $p_cls><i class='fas fa-angle-left'></i></li>";
|
||||
$dots = false;
|
||||
for ($i = 1; $i <= $nb_pages; $i++) {
|
||||
if ($i >= 3
|
||||
&& ($i < $current_page - 1
|
||||
|| $i > $current_page + 1)
|
||||
&& $i < $nb_pages - 1) {
|
||||
if (!$dots) {
|
||||
$html.= "<li class='nav-disabled dots'>...</li>";
|
||||
}
|
||||
$dots = true;
|
||||
continue;
|
||||
}
|
||||
$dots = false;
|
||||
|
||||
$current = ($current_page === $i)
|
||||
? "class='current'"
|
||||
: "";
|
||||
$html.= "<li data-page='$i' $current>$i</li>";
|
||||
}
|
||||
$html.= "<li data-page='$next' $n_cls><i class='fas fa-angle-right'></i></li>";
|
||||
$html.= "<li class='nb_plugin'>".sprintf(_n("%s plugin", "%s plugins", $total), $total)."</li>";
|
||||
if (!$only_li) {
|
||||
$html.= "</ul>";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display a dialog inviting the user to switch from former plugin list to marketplace new view.
|
||||
*
|
||||
* @return void display things
|
||||
*/
|
||||
static function showFeatureSwitchDialog() {
|
||||
global $CFG_GLPI;
|
||||
|
||||
if (isset($_POST['marketplace_replace'])) {
|
||||
$mp_value = isset($_POST['marketplace_replace_plugins_yes'])
|
||||
? Controller::MP_REPLACE_YES
|
||||
: (isset($_POST['marketplace_replace_plugins_never'])
|
||||
? Controller::MP_REPLACE_NEVER
|
||||
: Controller::MP_REPLACE_ASK);
|
||||
Config::setConfigurationValues('core', [
|
||||
'marketplace_replace_plugins' => $mp_value
|
||||
]);
|
||||
|
||||
// is user agree, redirect him to marketplace
|
||||
if ($mp_value === Controller::MP_REPLACE_YES) {
|
||||
Html::redirect($CFG_GLPI["root_doc"]."/front/marketplace.php");
|
||||
}
|
||||
|
||||
// avoid annoying user for the current session
|
||||
$_SESSION['skip_marketplace_invitation'] = true;
|
||||
}
|
||||
|
||||
// show modal for asking user preference
|
||||
if (Controller::getPluginPageConfig() == Controller::MP_REPLACE_ASK
|
||||
&& !isset($_SESSION['skip_marketplace_invitation'])
|
||||
&& GLPI_INSTALL_MODE !== 'CLOUD') {
|
||||
echo "<form id='marketplace_dialog' method='POST'>";
|
||||
echo Html::image($CFG_GLPI['root_doc']."/pics/screenshots/marketplace.png", [
|
||||
'style' => 'width: 600px',
|
||||
]);
|
||||
echo "<br><br>";
|
||||
echo __("GLPI provides a new marketplace to download and install plugins.");
|
||||
echo "<br><br>";
|
||||
echo "<b>".__("Do you want to replace the plugins setup page by the new marketplace ?")."</b>";
|
||||
echo "<hr><br>";
|
||||
echo Html::submit("<i class='fa fa-check'></i> ".__('Yes'), [
|
||||
'name' => 'marketplace_replace_plugins_yes'
|
||||
]);
|
||||
echo " ";
|
||||
echo Html::submit("<i class='fa fa-times'></i> ".__('No'), [
|
||||
'name' => 'marketplace_replace_plugins_never',
|
||||
'class' => 'secondary'
|
||||
]);
|
||||
echo " ";
|
||||
echo Html::submit("<i class='fa fa-clock'></i> ".__('Later'), [
|
||||
'name' => 'marketplace_replace_plugins_later',
|
||||
'class' => 'secondary'
|
||||
]);
|
||||
echo Html::hidden('marketplace_replace');
|
||||
|
||||
Html::closeForm();
|
||||
|
||||
echo Html::scriptBlock("$(document).ready(function() {
|
||||
$('#marketplace_dialog').dialog({
|
||||
'modal': true,
|
||||
'width': 'auto',
|
||||
'title': \"".__s("Switch to marketplace")."\"
|
||||
});
|
||||
});");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user