.
* ---------------------------------------------------------------------
*/
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'),
"".
__("Maybe you could setup a proxy").
" ".
__("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!'),
"".sprintf(__('Register on %1$s'), 'GLPI Network')." ".
__('and'). " ".
"".__("fill your registration key in setup.").""
);
}
}
if (count($messages)) {
echo "
";
echo "";
echo "
" . implode('
', $messages) . "
";
echo "";
echo "
";
}
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 = "
".__("All")."
";
foreach ($tags as $tag) {
$tags_li.= "
".ucfirst($tag['tag'])."
";
}
return "
{$tags_li}
";
}
/**
* 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 "
".
sprintf(__("We can't write on the markeplace directory (%s)."), GLPI_MARKETPLACE_DIR)." ".
__("If you want to ease the plugins download, please check permissions and ownership of this directory.")." ".
__("Otherwise, you will need to download and unzip the plugins archives manually.")." ".
"
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.= "";
} else if ($value + 0.5 == $i) {
$stars.= "";
} else {
$stars.= "";
}
}
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.= "" . ob_get_contents() . "";
}
ob_end_clean();
}
$buttons = "";
if (strlen($error)) {
$buttons .="";
Html::showToolTip($error, [
'applyto' => "plugin-error-$rand",
]);
}
if ($can_be_cleaned) {
$buttons .="";
} 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.")." ";
}
$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 .="";
}
} else if ($can_be_downloaded) {
if (!$exists) {
$buttons .="";
} else if ($can_be_updated) {
$update_title = sprintf(
__s("A new version (%s) is available, update ?", 'marketplace'),
$web_update_version
);
$buttons .="";
}
}
if ($mk_controller->requiresHigherOffer()) {
$warning = sprintf(
__s("You need a superior GLPI-Network offer to access to this plugin (%s)"),
implode(', ', $required_offers)
);
$buttons .="";
}
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 .="";
}
if ($is_installed) {
if (!strlen($error)) {
if ($is_actived) {
$buttons .="";
} else {
$buttons .="";
}
}
$buttons .="";
if (!strlen($error) && $is_actived && $config_page) {
$plugin_dir = Plugin::getWebDir($plugin_key, true);
$config_url = "$plugin_dir/$config_page";
$buttons .="";
}
}
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 = "";
} 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 = "$initials";
}
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 = "
";
}
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 "