Files
MYSOPHAL/inc/plugin.class.php
2025-08-07 13:15:31 +01:00

2485 lines
74 KiB
PHP

<?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/>.
* ---------------------------------------------------------------------
*/
/**
* Based on cacti plugin system
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
use Psr\SimpleCache\CacheInterface;
use Glpi\Marketplace\View as MarketplaceView;
use Glpi\Marketplace\Controller as MarketplaceController;
class Plugin extends CommonDBTM {
// Class constant : Plugin state
/**
* @var int Unknown plugin state
*/
const UNKNOWN = -1;
/**
* @var int Plugin was discovered but not installed
*
* @note Plugins are never actually set to this status?
*/
const ANEW = 0;
/**
* @var int Plugin is installed and enabled
*/
const ACTIVATED = 1;
/**
* @var int Plugin is not installed
*/
const NOTINSTALLED = 2;
/**
* @var int Plugin is installed but needs configured before it can be enabled
*/
const TOBECONFIGURED = 3;
/**
* @var int Plugin is installed but not enabled
*/
const NOTACTIVATED = 4;
/**
* @var int Plugin was previously discovered, but the plugin directory is missing now. The DB needs cleaned.
*/
const TOBECLEANED = 5;
/**
* @var int The plugin's files are for a newer version than installed. An update is needed.
*/
const NOTUPDATED = 6;
static $rightname = 'config';
/**
* Plugin init state.
*
* @var boolean
*/
private static $plugins_init = false;
/**
* Activated plugin list
*
* @var string[]
*/
private static $activated_plugins = [];
/**
* Loaded plugin list
*
* @var string[]
*/
private static $loaded_plugins = [];
static function getTypeName($nb = 0) {
return _n('Plugin', 'Plugins', $nb);
}
static function getMenuName() {
return static::getTypeName(Session::getPluralNumber());
}
static function getMenuContent() {
$menu = parent::getMenuContent() ?: [];
if (static::canView()) {
$redirect_mp = MarketplaceController::getPluginPageConfig();
$menu['title'] = self::getMenuName();
$menu['page'] = $redirect_mp == MarketplaceController::MP_REPLACE_YES
? '/front/marketplace.php'
: '/front/plugin.php';
$menu['icon'] = self::getIcon();
}
if (count($menu)) {
return $menu;
}
return false;
}
static function getAdditionalMenuLinks() {
if (!static::canView()) {
return false;
}
$mp_icon = MarketplaceView::getIcon();
$mp_title = MarketplaceView::getTypeName();
$marketplace = "<i class='$mp_icon pointer' title='$mp_title'></i><span class='sr-only'>$mp_title</span>";
$cl_icon = Plugin::getIcon();
$cl_title = Plugin::getTypeName();
$classic = "<i class='$cl_icon pointer' title='$cl_title'></i><span class='sr-only'>$cl_title</span>";
return [
$marketplace => MarketplaceView::getSearchURL(false),
$classic => Plugin::getSearchURL(false),
];
}
static function getAdditionalMenuOptions() {
if (static::canView()) {
return [
'marketplace' => [
'icon' => MarketplaceView::geticon(),
'title' => MarketplaceView::getTypeName(),
'page' => MarketplaceView::getSearchURL(false),
]
];
}
}
/**
* Retrieve an item from the database using its directory
*
* @param string $dir directory of the plugin
*
* @return boolean
**/
function getFromDBbyDir($dir) {
return $this->getFromDBByCrit([$this->getTable() . '.directory' => $dir]);
}
/**
* Init plugins list.
*
* @param boolean $load_plugins Whether to load active/configurable plugins or not.
* @param array $excluded_plugins List of plugins to exclude
*
* @return void
**/
function init(bool $load_plugins = false, array $excluded_plugins = []) {
global $DB;
self::$plugins_init = false;
self::$activated_plugins = [];
self::$loaded_plugins = [];
if (!isset($DB) || !$DB->connected) {
// Cannot init plugins list if DB is not connected
self::$plugins_init = true;
return;
}
$this->checkStates(false, $excluded_plugins);
$plugins = $this->find(['state' => [self::ACTIVATED, self::TOBECONFIGURED]]);
self::$plugins_init = true;
if ($load_plugins && count($plugins)) {
foreach ($plugins as $plugin) {
if (in_array($plugin['directory'], $excluded_plugins)) {
continue;
}
if (!$this->isLoadable($plugin['directory'])) {
continue;
}
Plugin::load($plugin['directory']);
if ((int)$plugin['state'] === self::ACTIVATED) {
self::$activated_plugins[] = $plugin['directory'];
}
}
// For plugins which require action after all plugin init
Plugin::doHook("post_init");
}
}
/**
* Init a plugin including setup.php file
* launching plugin_init_NAME function after checking compatibility
*
* @param string $plugin_key System name (Plugin directory)
* @param boolean $withhook Load hook functions (false by default)
*
* @return void
**/
static function load($plugin_key, $withhook = false) {
global $LOADED_PLUGINS;
$loaded = false;
foreach (PLUGINS_DIRECTORIES as $base_dir) {
if (!is_dir($base_dir)) {
continue;
}
if (file_exists("$base_dir/$plugin_key/setup.php")) {
$loaded = true;
$plugin_directory = "$base_dir/$plugin_key";
include_once("$plugin_directory/setup.php");
if (!in_array($plugin_key, self::$loaded_plugins)) {
self::$loaded_plugins[] = $plugin_key;
$init_function = "plugin_init_$plugin_key";
if (function_exists($init_function)) {
$init_function();
$LOADED_PLUGINS[$plugin_key] = $plugin_directory;
self::loadLang($plugin_key);
}
}
}
if ($withhook) {
self::includeHook($plugin_key);
}
if ($loaded) {
break;
}
}
}
/**
* Unload a plugin.
*
* @param string $plugin_key System name (Plugin directory)
*
* @return void
*/
private function unload($plugin_key) {
global $LOADED_PLUGINS;
if (($key = array_search($plugin_key, self::$activated_plugins)) !== false) {
unset(self::$activated_plugins[$key]);
}
if (($key = array_search($plugin_key, self::$loaded_plugins)) !== false) {
unset(self::$loaded_plugins[$key]);
}
if (isset($LOADED_PLUGINS[$plugin_key])) {
unset($LOADED_PLUGINS[$plugin_key]);
}
}
/**
* Load lang file for a plugin
*
* @param string $plugin_key System name (Plugin directory)
* @param string $forcelang Force a specific lang (default '')
* @param string $coretrytoload Lang trying to be loaded from core (default '')
*
* @return void
**/
static function loadLang($plugin_key, $forcelang = '', $coretrytoload = '') {
global $CFG_GLPI, $TRANSLATE;
// For compatibility for plugins using $LANG
$trytoload = 'en_GB';
if (isset($_SESSION['glpilanguage'])) {
$trytoload = $_SESSION["glpilanguage"];
}
// Force to load a specific lang
if (!empty($forcelang)) {
$trytoload = $forcelang;
}
// If not set try default lang file
if (empty($trytoload)) {
$trytoload = $CFG_GLPI["language"];
}
if (empty($coretrytoload)) {
$coretrytoload = $trytoload;
}
// New localisation system
$mofile = false;
foreach (PLUGINS_DIRECTORIES as $base_dir) {
if (!is_dir($base_dir)) {
continue;
}
$locales_dir = "$base_dir/$plugin_key/locales/";
if (file_exists($locales_dir.$CFG_GLPI["languages"][$trytoload][1])) {
$mofile = $locales_dir.$CFG_GLPI["languages"][$trytoload][1];
} else if (!empty($CFG_GLPI["language"])
&& file_exists($locales_dir.$CFG_GLPI["languages"][$CFG_GLPI["language"]][1])) {
$mofile = $locales_dir.$CFG_GLPI["languages"][$CFG_GLPI["language"]][1];
} else if (file_exists($locales_dir."en_GB.mo")) {
$mofile = $locales_dir."en_GB.mo";
}
if ($mofile !== false) {
break;
}
}
if ($mofile !== false) {
$TRANSLATE->addTranslationFile(
'gettext',
$mofile,
$plugin_key,
$coretrytoload
);
}
$mofile = str_replace($locales_dir, GLPI_LOCAL_I18N_DIR . '/'.$plugin_key . '/', $mofile);
$phpfile = str_replace('.mo', '.php', $mofile);
// Load local PHP file if it exists
if (file_exists($phpfile)) {
$TRANSLATE->addTranslationFile('phparray', $phpfile, $plugin_key, $coretrytoload);
}
// Load local MO file if it exists -- keep last so it gets precedence
if (file_exists($mofile)) {
$TRANSLATE->addTranslationFile('gettext', $mofile, $plugin_key, $coretrytoload);
}
}
/**
* Check plugins states and detect new plugins.
*
* @param boolean $scan_inactive_and_new_plugins
* @param array $excluded_plugins List of plugins to exclude
*
* @return void
*/
public function checkStates($scan_inactive_and_new_plugins = false, array $excluded_plugins = []) {
$directories = [];
// Add known plugins to the check list
$condition = $scan_inactive_and_new_plugins ? [] : ['state' => self::ACTIVATED];
$known_plugins = $this->find($condition);
foreach ($known_plugins as $plugin) {
$directories[] = $plugin['directory'];
}
if ($scan_inactive_and_new_plugins) {
// Add found directories to the check list
foreach (PLUGINS_DIRECTORIES as $plugins_directory) {
if (!is_dir($plugins_directory)) {
continue;
}
$directory_handle = opendir($plugins_directory);
while (false !== ($filename = readdir($directory_handle))) {
if (!in_array($filename, ['.svn', '.', '..'])
&& is_dir($plugins_directory . DIRECTORY_SEPARATOR . $filename)) {
$directories[] = $filename;
}
}
}
}
// Prevent duplicated checks
$directories = array_unique($directories);
// Check all directories from the checklist
foreach ($directories as $directory) {
if (in_array($directory, $excluded_plugins)) {
continue;
}
$this->checkPluginState($directory);
}
}
/**
* Check plugin state.
*
* @param string $plugin_key System name (Plugin directory)
*
* return void
*/
public function checkPluginState($plugin_key) {
$plugin = new self();
$is_already_known = $plugin->getFromDBByCrit(['directory' => $plugin_key]);
$informations = $this->getInformationsFromDirectory($plugin_key);
if (empty($informations)) {
if (!$is_already_known) {
// Plugin is not known and we are unable to load informations, we ignore it
return;
}
// Try to get information from a plugin that lists current name as its old name
// If something found, and not already registerd in DB,, base plugin informations on it
// If nothing found, mark plugin as "To be cleaned"
$new_specs = $this->getNewInfoAndDirBasedOnOldName($plugin_key);
if (null !== $new_specs
&& countElementsInTable(self::getTable(), ['directory' => $new_specs['directory']]) === 0) {
$plugin_key = $new_specs['directory'];
$informations = $new_specs['informations'];
} else {
trigger_error(
sprintf(
'Unable to load plugin "%s" informations.',
$plugin_key
),
E_USER_WARNING
);
// Plugin is known but we are unable to load informations, we ignore it
return;
}
}
if (!$is_already_known && array_key_exists('oldname', $informations)) {
// Plugin not known but was named differently before, we try to load state using old name
$is_already_known = $plugin->getFromDBByCrit(['directory' => $informations['oldname']]);
}
if (!$is_already_known) {
// Plugin not known, add it in DB
$this->add(
array_merge(
$informations,
[
'state' => self::NOTINSTALLED,
'directory' => $plugin_key,
]
)
);
return;
}
if ($informations['version'] != $plugin->fields['version']
|| $plugin_key != $plugin->fields['directory']) {
// Plugin known version differs from informations or plugin has been renamed,
// update informations in database
$input = $informations;
$input['id'] = $plugin->fields['id'];
$input['directory'] = $plugin_key;
if (!in_array($plugin->fields['state'], [self::ANEW, self::NOTINSTALLED, self::NOTUPDATED])) {
// mark it as 'updatable' unless it was not installed
trigger_error(
sprintf(
'Plugin "%s" version changed. It has been deactivated as its update process has to be launched.',
$plugin_key
),
E_USER_WARNING
);
$input['state'] = self::NOTUPDATED;
}
$this->update($input);
$this->unload($plugin_key);
// reset menu
if (isset($_SESSION['glpimenu'])) {
unset($_SESSION['glpimenu']);
}
return;
}
// Check if configuration state changed
if (in_array((int)$plugin->fields['state'], [self::ACTIVATED, self::TOBECONFIGURED, self::NOTACTIVATED], true)) {
$function = 'plugin_' . $plugin_key . '_check_config';
$is_config_ok = !function_exists($function) || $function();
if ((int)$plugin->fields['state'] === self::TOBECONFIGURED && $is_config_ok) {
// Remove TOBECONFIGURED state if configuration is OK now
$this->update(
[
'id' => $plugin->fields['id'],
'state' => self::NOTACTIVATED
]
);
return;
} else if ((int)$plugin->fields['state'] !== self::TOBECONFIGURED && !$is_config_ok) {
// Add TOBECONFIGURED state if configuration is required
trigger_error(
sprintf(
'Plugin "%s" must be configured.',
$plugin_key
),
E_USER_WARNING
);
$this->update(
[
'id' => $plugin->fields['id'],
'state' => self::TOBECONFIGURED
]
);
return;
}
}
if (self::ACTIVATED !== (int)$plugin->fields['state']) {
// Plugin is not activated, nothing to do
return;
}
// Check that active state of plugin can be kept
$usage_ok = true;
// Check compatibility
ob_start();
if (!$this->checkVersions($plugin_key)) {
$usage_ok = false;
}
ob_end_clean();
// Check prerequisites
if ($usage_ok) {
$function = 'plugin_' . $plugin_key . '_check_prerequisites';
if (function_exists($function)) {
ob_start();
if (!$function()) {
$usage_ok = false;
}
ob_end_clean();
}
}
if (!$usage_ok) {
// Deactivate if not usable
trigger_error(
sprintf(
'Plugin "%s" prerequisites are not matched. It has been deactivated.',
$plugin_key
),
E_USER_WARNING
);
$this->unactivate($plugin->fields['id']);
}
}
/**
* Get plugin information based on its old name.
*
* @param string $oldname
*
* @return null|array If a new directory is found, returns an array containing 'directory' and 'informations' keys.
*/
private function getNewInfoAndDirBasedOnOldName($oldname) {
$plugins_directories = new DirectoryIterator(GLPI_ROOT . '/plugins');
/** @var SplFileInfo $plugin_directory */
foreach ($plugins_directories as $plugin_directory) {
if (in_array($plugin_directory->getFilename(), ['.svn', '.', '..'])
|| !is_dir($plugin_directory->getRealPath())) {
continue;
}
$informations = $this->getInformationsFromDirectory($plugin_directory->getFilename());
if (array_key_exists('oldname', $informations) && $informations['oldname'] === $oldname) {
// Return informations if oldname specified in parsed directory matches passed value
return [
'directory' => $plugin_directory->getFilename(),
'informations' => $informations,
];
}
}
return null;
}
/**
* Get list of all plugins
*
* @param array $fields Fields to retrieve
* @param array $order Query ORDER clause
*
* @return array
*/
public function getList(array $fields = [], array $order = ['name', 'directory']) {
global $DB;
$query = [
'FROM' => $this->getTable()
];
if (count($fields) > 0) {
$query['FIELDS'] = $fields;
}
if (count($order) > 0) {
$query['ORDER'] = $order;
}
$iterator = $DB->request($query);
return iterator_to_array($iterator, false);
}
/**
* Uninstall a plugin
*
* @param integer $ID ID of the plugin (The `id` field, not directory)
**/
function uninstall($ID) {
$message = '';
$type = ERROR;
if ($this->getFromDB($ID)) {
CronTask::Unregister($this->fields['directory']);
self::load($this->fields['directory'], true); // Force load in case plugin is not active
FieldUnicity::deleteForItemtype($this->fields['directory']);
Link_Itemtype::deleteForItemtype($this->fields['directory']);
// Run the Plugin's Uninstall Function first
$function = 'plugin_' . $this->fields['directory'] . '_uninstall';
if (function_exists($function)) {
$function();
} else {
Session::addMessageAfterRedirect(
sprintf(__('Plugin %1$s has no uninstall function!'), $this->fields['name']),
true,
WARNING
);
}
$this->update([
'id' => $ID,
'state' => self::NOTINSTALLED,
]);
$this->unload($this->fields['directory']);
$type = INFO;
$message = sprintf(__('Plugin %1$s has been uninstalled!'), $this->fields['name']);
} else {
$message = sprintf(__('Plugin %1$s not found!'), $ID);
}
Session::addMessageAfterRedirect(
$message,
true,
$type
);
}
/**
* Install a plugin
*
* @param integer $ID ID of the plugin (The `id` field, not directory)
* @param array $params Additional params to pass to install hook.
*
* @return void
*
* @since 9.5.0 Added $param parameter
**/
function install($ID, array $params = []) {
global $DB;
$message = '';
$type = ERROR;
if ($this->getFromDB($ID)) {
// Clear locale cache to prevent errors while reloading plugin locales
$translation_cache = Config::getCache('cache_trans', 'core', true);
if ($translation_cache instanceof CacheInterface) {
$translation_cache->clear();
}
self::load($this->fields['directory'], true); // Load plugin hooks
$install_function = 'plugin_' . $this->fields['directory'] . '_install';
if (function_exists($install_function)) {
$DB->disableTableCaching(); //prevents issues on table/fieldExists upgrading from old versions
if ($install_function($params)) {
$type = INFO;
$check_function = 'plugin_' . $this->fields['directory'] . '_check_config';
$is_config_ok = !function_exists($check_function) || $check_function();
if ($is_config_ok) {
$this->update(['id' => $ID,
'state' => self::NOTACTIVATED]);
$message = sprintf(__('Plugin %1$s has been installed!'), $this->fields['name']);
$message .= '<br/><br/>' . str_replace(
'%activate_link',
Html::getSimpleForm(static::getFormURL(), ['action' => 'activate'],
mb_strtolower(_x('button', 'Enable')), ['id' => $ID], '', 'class="pointer"'),
__('Do you want to %activate_link it?')
);
} else {
$this->update(['id' => $ID,
'state' => self::TOBECONFIGURED]);
$message = sprintf(__('Plugin %1$s has been installed and must be configured!'), $this->fields['name']);
}
}
} else {
$type = WARNING;
$message = sprintf(__('Plugin %1$s has no install function!'), $this->fields['name']);
}
} else {
$message = sprintf(__('Plugin %1$s not found!'), $ID);
}
Session::addMessageAfterRedirect(
$message,
true,
$type
);
}
/**
* activate a plugin
*
* @param integer $ID ID of the plugin (The `id` field, not directory)
*
* @return boolean about success
**/
function activate($ID) {
global $PLUGIN_HOOKS;
if ($this->getFromDB($ID)) {
// Enable autoloader and load plugin hooks
self::load($this->fields['directory'], true);
// No activation if not CSRF compliant
if (!isset($PLUGIN_HOOKS['csrf_compliant'][$this->fields['directory']])
|| !$PLUGIN_HOOKS['csrf_compliant'][$this->fields['directory']]) {
Session::addMessageAfterRedirect(
sprintf(__('Plugin %1$s is not CSRF compliant!'), $this->fields['name']),
true,
ERROR
);
return false;
}
$function = 'plugin_' . $this->fields['directory'] . '_check_prerequisites';
if (function_exists($function)) {
ob_start();
$do_activate = $function();
$msg = '';
if (!$do_activate) {
$msg = '<span class="error">' . ob_get_contents() . '</span>';
}
ob_end_clean();
if (!$do_activate) {
$this->unload($this->fields['directory']);
Session::addMessageAfterRedirect(
sprintf(__('Plugin prerequisites are not matching, it cannot be activated.') . ' ' . $msg, $this->fields['name']),
true,
ERROR
);
return false;
}
}
$function = 'plugin_' . $this->fields['directory'] . '_check_config';
if (!function_exists($function) || $function()) {
$this->update(['id' => $ID,
'state' => self::ACTIVATED]);
// Initialize session for the plugin
if (isset($PLUGIN_HOOKS['init_session'][$this->fields['directory']])
&& is_callable($PLUGIN_HOOKS['init_session'][$this->fields['directory']])) {
call_user_func($PLUGIN_HOOKS['init_session'][$this->fields['directory']]);
}
// Initialize profile for the plugin
if (isset($PLUGIN_HOOKS['change_profile'][$this->fields['directory']])
&& is_callable($PLUGIN_HOOKS['change_profile'][$this->fields['directory']])) {
call_user_func($PLUGIN_HOOKS['change_profile'][$this->fields['directory']]);
}
// reset menu
if (isset($_SESSION['glpimenu'])) {
unset($_SESSION['glpimenu']);
}
Session::addMessageAfterRedirect(
sprintf(__('Plugin %1$s has been activated!'), $this->fields['name']),
true,
INFO
);
return true;
} else {
$this->unload($this->fields['directory']);
Session::addMessageAfterRedirect(
sprintf(__('Plugin configuration must be done, it cannot be activated.') . ' ' . $msg, $this->fields['name']),
true,
ERROR
);
return false;
}
}
Session::addMessageAfterRedirect(
sprintf(__('Plugin %1$s not found!'), $ID),
true,
ERROR
);
return false;
}
/**
* Unactivate a plugin
*
* @param integer $ID ID of the plugin (The `id` field, not directory)
*
* @return boolean
**/
function unactivate($ID) {
if ($this->getFromDB($ID)) {
$this->update([
'id' => $ID,
'state' => self::NOTACTIVATED
]);
$this->unload($this->fields['directory']);
// reset menu
if (isset($_SESSION['glpimenu'])) {
unset($_SESSION['glpimenu']);
}
Session::addMessageAfterRedirect(
sprintf(__('Plugin %1$s has been deactivated!'), $this->fields['name']),
true,
INFO
);
return true;
}
Session::addMessageAfterRedirect(
sprintf(__('Plugin %1$s not found!'), $ID),
true,
ERROR
);
return false;
}
/**
* Unactivate all activated plugins for update process.
* This will prevent any plugin class to be available through autoloader.
**/
function unactivateAll() {
global $DB;
$DB->update(
$this->getTable(), [
'state' => self::NOTACTIVATED
], [
'state' => self::ACTIVATED
]
);
self::$activated_plugins = [];
self::$loaded_plugins = [];
// reset menu
if (isset($_SESSION['glpimenu'])) {
unset($_SESSION['glpimenu']);
}
}
/**
* clean a plugin
*
* @param $ID ID of the plugin
**/
function clean($ID) {
if ($this->getFromDB($ID)) {
// Clean crontask after "hard" remove
CronTask::Unregister($this->fields['directory']);
$this->unload($this->fields['directory']);
$this->delete(['id' => $ID]);
}
}
/**
* Is a plugin activated ?
*
* @param string $directory Plugin directory
*
* @return boolean
*/
function isActivated($directory) {
// Make a lowercase comparison, as sometime this function is called based on
// extraction of plugin name from a classname, which does not use same naming rules than directories.
$activated_plugins = array_map('strtolower', self::$activated_plugins);
if (in_array(strtolower($directory), $activated_plugins)) {
// If plugin is marked as activated, no need to query DB on this case.
return true;
}
// If plugin is not marked as activated, check on DB as it may have not been loaded yet.
if ($this->getFromDBbyDir($directory)) {
return ($this->fields['state'] == self::ACTIVATED) && $this->isLoadable($directory);
}
return false;
}
/**
* Is a plugin updatable ?
*
* @param string $directory Plugin directory
*
* @return boolean
*/
function isUpdatable($directory) {
// Make a lowercase comparison, as sometime this function is called based on
// extraction of plugin name from a classname, which does not use same naming rules than directories.
$activated_plugins = array_map('strtolower', self::$activated_plugins);
if (in_array(strtolower($directory), $activated_plugins)) {
// If plugin is marked as activated, no need to query DB on this case.
return false;
}
// If plugin is not marked as activated, check on DB as it may have not been loaded yet.
if ($this->getFromDBbyDir($directory)) {
return ($this->fields['state'] == self::NOTUPDATED) && $this->isLoadable($directory);
}
return false;
}
/**
* Is a plugin loadable ?
*
* @param string $directory Plugin directory
*
* @return boolean
*/
function isLoadable($directory) {
return !empty($this->getInformationsFromDirectory($directory));
}
/**
* Is a plugin installed ?
*
* @param string $directory Plugin directory
*
* @return boolean
*/
function isInstalled($directory) {
if ($this->isPluginLoaded($directory)) {
// If plugin is loaded, it is because it is installed and active. No need to query DB on this case.
return true;
}
// If plugin is not loaded, check on DB as plugins may have not been loaded yet.
if ($this->getFromDBbyDir($directory)) {
return in_array($this->fields['state'], [self::ACTIVATED, self::TOBECONFIGURED, self::NOTACTIVATED])
&& $this->isLoadable($directory);
}
}
/**
* Migrate itemtype from integer (0.72) to string (0.80)
*
* @param array $types Array of (num=>name) of type manage by the plugin
* @param array $glpitables Array of GLPI table name used by the plugin
* @param array $plugtables Array of Plugin table name which have an itemtype
*
* @return void
**/
static function migrateItemType($types = [], $glpitables = [], $plugtables = []) {
global $DB;
$typetoname = [0 => "",// For tickets
1 => "Computer",
2 => "NetworkEquipment",
3 => "Printer",
4 => "Monitor",
5 => "Peripheral",
6 => "Software",
7 => "Contact",
8 => "Supplier",
9 => "Infocom",
10 => "Contract",
11 => "CartridgeItem",
12 => "DocumentType",
13 => "Document",
14 => "KnowbaseItem",
15 => "User",
16 => "Ticket",
17 => "ConsumableItem",
18 => "Consumable",
19 => "Cartridge",
20 => "SoftwareLicense",
21 => "Link",
22 => "State",
23 => "Phone",
24 => "Device",
25 => "Reminder",
26 => "Stat",
27 => "Group",
28 => "Entity",
29 => "ReservationItem",
30 => "AuthMail",
31 => "AuthLDAP",
32 => "OcsServer",
33 => "RegistryKey",
34 => "Profile",
35 => "MailCollector",
36 => "Rule",
37 => "Transfer",
38 => "SavedSearch",
39 => "SoftwareVersion",
40 => "Plugin",
41 => "Item_Disk",
42 => "NetworkPort",
43 => "TicketFollowup",
44 => "Budget"];
// Filter tables that does not exists or does not contains an itemtype field.
// This kind of case exist when current method is called from plugins that based their
// logic on an old GLPI datamodel that may have changed upon time.
// see https://github.com/pluginsGLPI/order/issues/111
$glpitables = array_filter(
$glpitables,
function ($table) use ($DB) {
return $DB->tableExists($table) && $DB->fieldExists($table, 'itemtype');
}
);
//Add plugins types
$typetoname = self::doHookFunction("migratetypes", $typetoname);
foreach ($types as $num => $name) {
$typetoname[$num] = $name;
foreach ($glpitables as $table) {
$DB->updateOrDie(
$table, [
'itemtype' => $name,
], [
'itemtype' => $num
],
"update itemtype of table $table for $name"
);
}
}
if (in_array('glpi_infocoms', $glpitables) && count($types)) {
$entities = getAllDataFromTable('glpi_entities');
$entities[0] = "Root";
foreach ($types as $num => $name) {
$itemtable = getTableForItemType($name);
if (!$DB->tableExists($itemtable)) {
// Just for security, shouldn't append
continue;
}
$do_recursive = false;
if ($DB->fieldExists($itemtable, 'is_recursive')) {
$do_recursive = true;
}
foreach ($entities as $entID => $val) {
if ($do_recursive) {
// Non recursive ones
$sub_query = new \QuerySubQuery([
'SELECT' => 'id',
'FROM' => $itemtable,
'WHERE' => [
'entities_id' => $entID,
'is_recursive' => 0
]
]);
$DB->updateOrDie(
'glpi_infocoms', [
'entities_id' => $entID,
'is_recursive' => 0
], [
'itemtype' => $name,
'items_id' => $sub_query
],
"update entities_id and is_recursive=0 in glpi_infocoms for $name"
);
// Recursive ones
$sub_query = new \QuerySubQuery([
'SELECT' => 'id',
'FROM' => $itemtable,
'WHERE' => [
'entities_id' => $entID,
'is_recursive' => 1
]
]);
$DB->updateOrDie(
'glpi_infocoms', [
'entities_id' => $entID,
'is_recursive' => 1
], [
'itemtype' => $name,
'items_id' => $sub_query
],
"update entities_id and is_recursive=1 in glpi_infocoms for $name"
);
} else {
$sub_query = new \QuerySubQuery([
'SELECT' => 'id',
'FROM' => $itemtable,
'WHERE' => [
'entities_id' => $entID,
]
]);
$DB->updateOrDie(
'glpi_infocoms', [
'entities_id' => $entID
], [
'itemtype' => $name,
'items_id' => $sub_query
],
"update entities_id in glpi_infocoms for $name"
);
}
} // each entity
} // each plugin type
}
foreach ($typetoname as $num => $name) {
foreach ($plugtables as $table) {
$DB->updateOrDie(
$table, [
'itemtype' => $name
], [
'itemtype' => $num
],
"update itemtype of table $table for $name"
);
}
}
}
/**
* @param integer $width
**/
function showSystemInformations($width) {
// No need to translate, this part always display in english (for copy/paste to forum)
echo "\n<tr class='tab_bg_2'><th>Plugins list</th></tr>";
echo "<tr class='tab_bg_1'><td><pre>\n&nbsp;\n";
$plug = new Plugin();
$pluglist = $plug->find([], "name, directory");
foreach ($pluglist as $plugin) {
$msg = substr(str_pad($plugin['directory'], 30), 0, 20).
" Name: ".Toolbox::substr(str_pad($plugin['name'], 40), 0, 30).
" Version: ".str_pad($plugin['version'], 10).
" State: ";
$state = $plug->isLoadable($plugin['directory']) ? $plugin['state'] : self::TOBECLEANED;
$msg .= self::getState($state);
echo wordwrap("\t".$msg."\n", $width, "\n\t\t");
}
echo "\n</pre></td></tr>";
}
/**
* Define a new class managed by a plugin
*
* @param string $itemtype Class name
* @param array $attrib Array of attributes, a hashtable with index in
* (classname, typename, reservation_types)
*
* @return bool
**/
static function registerClass($itemtype, $attrib = []) {
global $CFG_GLPI;
$plug = isPluginItemType($itemtype);
if (!$plug) {
return false;
}
$all_types = preg_grep('/.+_types/', array_keys($CFG_GLPI));
$all_types[] = 'networkport_instantiations';
$mapping = [
'doc_types' => 'document_types',
'helpdesk_types' => 'ticket_types',
'netport_types' => 'networkport_types'
];
foreach ($mapping as $orig => $fixed) {
if (isset($attrib[$orig])) {
\Toolbox::deprecated(
sprintf(
'%1$s type is deprecated, use %2$s instead.',
$orig,
$fixed
)
);
$attrib[$fixed] = $attrib[$orig];
unset($attrib[$orig]);
}
}
$blacklist = ['device_types'];
foreach ($all_types as $att) {
if (!in_array($att, $blacklist) && isset($attrib[$att]) && $attrib[$att]) {
$CFG_GLPI[$att][] = $itemtype;
unset($attrib[$att]);
}
}
if (isset($attrib['device_types']) && $attrib['device_types']
&& method_exists($itemtype, 'getItem_DeviceType')) {
if (class_exists($itemtype::getItem_DeviceType())) {
$CFG_GLPI['device_types'][] = $itemtype;
}
unset($attrib[$att]);
}
if (isset($attrib['addtabon'])) {
if (!is_array($attrib['addtabon'])) {
$attrib['addtabon'] = [$attrib['addtabon']];
}
foreach ($attrib['addtabon'] as $form) {
CommonGLPI::registerStandardTab($form, $itemtype);
}
}
//Manage entity forward from a source itemtype to this itemtype
if (isset($attrib['forwardentityfrom'])) {
CommonDBTM::addForwardEntity($attrib['forwardentityfrom'], $itemtype);
}
return true;
}
/**
* This function executes a hook.
*
* @param string $name Name of hook to fire
* @param mixed $param Parameters if needed : if object limit to the itemtype (default NULL)
*
* @return mixed $data
**/
static function doHook($name, $param = null) {
global $PLUGIN_HOOKS;
if ($param == null) {
$data = func_get_args();
} else {
$data = $param;
}
// Apply hook only for the item
if (($param != null) && is_object($param)) {
$itemtype = get_class($param);
if (isset($PLUGIN_HOOKS[$name]) && is_array($PLUGIN_HOOKS[$name])) {
foreach ($PLUGIN_HOOKS[$name] as $plugin_key => $tab) {
if (!Plugin::isPluginActive($plugin_key)) {
continue;
}
if (isset($tab[$itemtype])) {
self::includeHook($plugin_key);
if (is_callable($tab[$itemtype])) {
call_user_func($tab[$itemtype], $data);
}
}
}
}
} else { // Standard hook call
if (isset($PLUGIN_HOOKS[$name]) && is_array($PLUGIN_HOOKS[$name])) {
foreach ($PLUGIN_HOOKS[$name] as $plugin_key => $function) {
if (!Plugin::isPluginActive($plugin_key)) {
continue;
}
self::includeHook($plugin_key);
if (is_callable($function)) {
call_user_func($function, $data);
}
}
}
}
/* Variable-length argument lists have a slight problem when */
/* passing values by reference. Pity. This is a workaround. */
return $data;
}
/**
* This function executes a hook.
*
* @param string $name Name of hook to fire
* @param mixed $parm Parameters (default NULL)
*
* @return mixed $data
**/
static function doHookFunction($name, $parm = null) {
global $PLUGIN_HOOKS;
$ret = $parm;
if (isset($PLUGIN_HOOKS[$name]) && is_array($PLUGIN_HOOKS[$name])) {
foreach ($PLUGIN_HOOKS[$name] as $plugin_key => $function) {
if (!Plugin::isPluginActive($plugin_key)) {
continue;
}
self::includeHook($plugin_key);
if (is_callable($function)) {
$ret = call_user_func($function, $ret);
}
}
}
/* Variable-length argument lists have a slight problem when */
/* passing values by reference. Pity. This is a workaround. */
return $ret;
}
/**
* This function executes a hook for 1 plugin.
*
* @param string $plugin_key System name of the plugin
* @param string|callable $hook suffix used to build function to be called ("plugin_myplugin_{$hook}")
* or callable function
* @param mixed ...$args [optional] One or more arguments passed to hook function
*
* @return mixed $data
**/
static function doOneHook($plugin_key, $hook, ...$args) {
$plugin_key = strtolower($plugin_key);
if (!Plugin::isPluginActive($plugin_key)) {
return;
}
self::includeHook($plugin_key);
if (is_string($hook) && !is_callable($hook)) {
$hook = "plugin_" . $plugin_key . "_" . $hook;
}
if (is_callable($hook)) {
return call_user_func_array($hook, $args);
}
}
/**
* Get dropdowns for plugins
*
* @return array Array containing plugin dropdowns
**/
static function getDropdowns() {
$dps = [];
foreach (self::getPlugins() as $plug) {
$tab = self::doOneHook($plug, 'getDropdown');
if (is_array($tab)) {
$dps = array_merge($dps, [self::getInfo($plug, 'name') => $tab]);
}
}
return $dps;
}
/**
* Get information from a plugin
*
* @param string $plugin System name (Plugin directory)
* @param string $info Wanted info (name, version, ...), NULL for all
*
* @since 0.84
*
* @return string|array The specific information value requested or an array of all information if $info is null.
**/
static function getInfo($plugin, $info = null) {
$fct = 'plugin_version_'.strtolower($plugin);
if (function_exists($fct)) {
$res = $fct();
if (!isset($res['requirements']) && isset($res['minGlpiVersion'])) {
$res['requirements'] = ['glpi' => ['min' => $res['minGlpiVersion']]];
}
} else {
Toolbox::logError("$fct method must be defined!");
$res = [];
}
if (isset($info)) {
return (isset($res[$info]) ? $res[$info] : '');
}
return $res;
}
/**
* Returns plugin information from directory.
*
* @param string $directory
*
* @return array
*/
public function getInformationsFromDirectory($directory) {
$informations = [];
foreach (PLUGINS_DIRECTORIES as $base_dir) {
if (!is_dir($base_dir)) {
continue;
}
$setup_file = "$base_dir/$directory/setup.php";
if (file_exists($setup_file)) {
// Includes are made inside a function to prevent included files to override
// variables used in this function.
// For example, if the included files contains a $plugin variable, it will
// replace the $plugin variable used here.
$include_fct = function () use ($directory, $setup_file) {
self::loadLang($directory);
include_once($setup_file);
};
$include_fct();
$informations = Toolbox::addslashes_deep(self::getInfo($directory));
// plugin found, don't parse others directories
break;
}
}
return $informations;
}
/**
* Get database relations for plugins
*
* @return array Array containing plugin database relations
**/
static function getDatabaseRelations() {
$dps = [];
foreach (self::getPlugins() as $plugin_key) {
self::includeHook($plugin_key);
$function2 = "plugin_".$plugin_key."_getDatabaseRelations";
if (function_exists($function2)) {
$dps = array_merge_recursive($dps, $function2());
}
}
return $dps;
}
/**
* Get additional search options managed by plugins
*
* @param $itemtype
*
* @return array Array containing plugin search options for given type
**/
static function getAddSearchOptions($itemtype) {
$sopt = [];
foreach (self::getPlugins() as $plugin_key) {
self::includeHook($plugin_key);
$function = "plugin_".$plugin_key."_getAddSearchOptions";
if (function_exists($function)) {
$tmp = $function($itemtype);
if (is_array($tmp) && count($tmp)) {
$sopt += $tmp;
}
}
}
return $sopt;
}
/**
* Include the hook file for a plugin
*
* @param string $plugin_key
*/
static function includeHook(string $plugin_key = "") {
foreach (PLUGINS_DIRECTORIES as $base_dir) {
if (file_exists("$base_dir/$plugin_key/hook.php")) {
include_once("$base_dir/$plugin_key/hook.php");
break;
}
}
}
/**
* Get additional search options managed by plugins
*
* @since 9.2
*
* @param string $itemtype Item type
*
* @return array An *indexed* array of search options
*
* @see https://glpi-developer-documentation.rtfd.io/en/master/devapi/search.html
**/
static function getAddSearchOptionsNew($itemtype) {
$options = [];
foreach (self::getPlugins() as $plugin_key) {
self::includeHook($plugin_key);
$function = "plugin_".$plugin_key."_getAddSearchOptionsNew";
if (function_exists($function)) {
$tmp = $function($itemtype);
foreach ($tmp as $opt) {
if (!isset($opt['id'])) {
throw new \Exception($itemtype . ': invalid search option! ' . print_r($opt, true));
}
$optid = $opt['id'];
unset($opt['id']);
if (isset($options[$optid])) {
$message = "Duplicate key $optid ({$options[$optid]['name']}/{$opt['name']}) in ".
$itemtype . " searchOptions!";
Toolbox::logError($message);
}
foreach ($opt as $k => $v) {
$options[$optid][$k] = $v;
}
}
}
}
return $options;
}
/**
* Check if there is a plugin enabled that supports importing items
*
* @return boolean
*
* @since 0.84
**/
static function haveImport() {
global $PLUGIN_HOOKS;
return (isset($PLUGIN_HOOKS['import_item']) && count($PLUGIN_HOOKS['import_item']));
}
/**
* Get an internationalized message for incompatible plugins (either core or php version)
*
* @param string $type Either 'php' or 'core', defaults to 'core'
* @param string $min Minimal required version
* @param string $max Maximal required version
*
* @since 9.2
*
* @return string
*/
static public function messageIncompatible($type = 'core', $min = null, $max = null) {
$type = ($type === 'core' ? __('GLPI') : __('PHP'));
if ($min === null && $max !== null) {
return sprintf(
__('This plugin requires %1$s < %2$s.'),
$type,
$max
);
} else if ($min !== null && $max === null) {
return sprintf(
__('This plugin requires %1$s >= %2$s.'),
$type,
$min
);
} else {
return sprintf(
__('This plugin requires %1$s >= %2$s and < %3$s.'),
$type,
$min,
$max
);
}
}
/**
* Get an internationalized message for missing requirement (extension, other plugin, ...)
*
* @param string $type Type of what is missing, one of:
* - ext (PHP module)
* - plugin (other plugin)
* - compil (compilation option)
* - param (GLPI configuration parameter)
* @param string $name Missing name
*
* @since 9.2
*
* @return string
*/
static public function messageMissingRequirement($type, $name) {
switch ($type) {
case 'ext':
return sprintf(
__('This plugin requires PHP extension %1$s'),
$name
);
break;
case 'plugin':
return sprintf(
__('This plugin requires %1$s plugin'),
$name
);
break;
case 'compil':
return sprintf(
__('This plugin requires PHP compiled along with "%1$s"'),
$name
);
break;
case 'param':
return sprintf(
__('This plugin requires PHP parameter %1$s'),
$name
);
break;
case 'glpiparam':
return sprintf(
__('This plugin requires GLPI parameter %1$s'),
$name
);
break;
default:
throw new \RuntimeException("messageMissing type $type is unknown!");
}
}
/**
* Check declared versions (GLPI, PHP, ...)
*
* @since 9.2
*
* @param string $name System name (Plugin directory)
*
* @return boolean
*/
public function checkVersions($name) {
$infos = self::getInfo($name);
$ret = true;
if (isset($infos['requirements'])) {
if (isset($infos['requirements']['glpi'])) {
$glpi = $infos['requirements']['glpi'];
if (isset($glpi['min']) || isset($glpi['max'])) {
$ret = $ret && $this->checkGlpiVersion($infos['requirements']['glpi']);
}
if (isset($glpi['params'])) {
$ret = $ret && $this->checkGlpiParameters($glpi['params']);
}
if (isset($glpi['plugins'])) {
$ret = $ret && $this->checkGlpiPlugins($glpi['plugins']);
}
}
if (isset($infos['requirements']['php'])) {
$php = $infos['requirements']['php'];
if (isset($php['min']) || isset($php['max'])) {
$ret = $ret && $this->checkPhpVersion($php);
}
if (isset($php['exts'])) {
$ret = $ret && $this->checkPhpExtensions($php['exts']);
}
if (isset($php['params'])) {
$ret = $ret && $this->checkPhpParameters($php['params']);
}
}
}
return $ret;
}
/**
* Check for GLPI version
*
* @since 9.2
* @since 9.3 Removed the 'dev' key of $info parameter.
*
* @param array $infos Requirements infos:
* - min: minimal supported version,
* - max: maximal supported version
* One of min or max is required.
*
* @return boolean
*/
public function checkGlpiVersion($infos) {
if (!isset($infos['min']) && !isset($infos['max'])) {
throw new LogicException('Either "min" or "max" is required for GLPI requirements!');
}
$glpiVersion = $this->isGlpiPrever() ? $this->getGlpiPrever() : $this->getGlpiVersion();
$compat = true;
if (isset($infos['min']) && !version_compare($glpiVersion, $infos['min'], '>=')) {
$compat = false;
}
if (isset($infos['max']) && !version_compare($glpiVersion, $infos['max'], '<')) {
$compat = false;
}
if (!$compat) {
echo Plugin::messageIncompatible(
'core',
(isset($infos['min']) ? $infos['min'] : null),
(isset($infos['max']) ? $infos['max'] : null)
);
}
return $compat;
}
/**
* Check for PHP version
*
* @since 9.2
*
* @param array $infos Requirements infos:
* - min: minimal supported version,
* - max: maximal supported version.
* One of min or max is required.
*
* @return boolean
*/
public function checkPhpVersion($infos) {
$compat = true;
if (isset($infos['min']) && isset($infos['max'])) {
$compat = !(version_compare($this->getPhpVersion(), $infos['min'], 'lt') || version_compare($this->getPhpVersion(), $infos['max'], 'ge'));
} else if (isset($infos['min'])) {
$compat = !(version_compare($this->getPhpVersion(), $infos['min'], 'lt'));
} else if (isset($infos['max'])) {
$compat = !(version_compare($this->getPhpVersion(), $infos['max'], 'ge'));
} else {
throw new LogicException('Either "min" or "max" is required for PHP requirements!');
}
if (!$compat) {
echo Plugin::messageIncompatible(
'php',
(isset($infos['min']) ? $infos['min'] : null),
(isset($infos['max']) ? $infos['max'] : null)
);
}
return $compat;
}
/**
* Check fo required PHP extensions
*
* @since 9.2
*
* @param array $exts Extensions lists/config @see Config::checkExtensions()
*
* @return boolean
*/
public function checkPhpExtensions($exts) {
$report = Config::checkExtensions($exts);
if (count($report['missing'])) {
foreach (array_keys($report['missing']) as $ext) {
echo self::messageMissingRequirement('ext', $ext) . '<br/>';
}
return false;
}
return true;
}
/**
* Check expected GLPI parameters
*
* @since 9.2
*
* @param array $params Expected parameters to be setup
*
* @return boolean
*/
public function checkGlpiParameters($params) {
global $CFG_GLPI;
$compat = true;
foreach ($params as $param) {
if (!isset($CFG_GLPI[$param]) || trim($CFG_GLPI[$param]) == '' || !$CFG_GLPI[$param]) {
echo self::messageMissingRequirement('glpiparam', $param) . '<br/>';
$compat = false;
}
}
return $compat;
}
/**
* Check expected PHP parameters
*
* @since 9.2
*
* @param array $params Expected parameters to be setup
*
* @return boolean
*/
public function checkPhpParameters($params) {
$compat = true;
foreach ($params as $param) {
if (!ini_get($param) || trim(ini_get($param)) == '') {
echo self::messageMissingRequirement('param', $param) . '<br/>';
$compat = false;
}
}
return $compat;
}
/**
* Check expected GLPI plugins
*
* @since 9.2
*
* @param array $plugins Expected plugins
*
* @return boolean
*/
public function checkGlpiPlugins($plugins) {
$compat = true;
foreach ($plugins as $plugin) {
if (!$this->isInstalled($plugin) || !$this->isActivated($plugin)) {
echo self::messageMissingRequirement('plugin', $plugin) . '<br/>';
$compat = false;
}
}
return $compat;
}
/**
* Get GLPI version
* Used from unit tests to mock.
*
* @since 9.2
*
* @return string
*/
public function getGlpiVersion() {
return GLPI_VERSION;
}
/**
* Get GLPI pre version
* Used from unit tests to mock.
*
* @since 9.2
*
* @return string
*/
public function getGlpiPrever() {
return GLPI_PREVER;
}
/**
* Check if GLPI version is a pre version
*
* @since 9.3
*
* @return string
*/
public function isGlpiPrever() {
return defined('GLPI_PREVER');
}
/**
* Get PHP version
* Used from unit tests to mock.
*
* @since 9.2
*
* @return string
*/
public function getPhpVersion() {
return PHP_VERSION;
}
/**
* Return label for an integer plugin state
*
* @since 9.3
*
* @param integer $state see this class constants (ex self::ANEW, self::ACTIVATED)
* @return string the label
*/
static function getState($state = 0) {
switch ($state) {
case self::ANEW :
return _x('status', 'New');
case self::ACTIVATED :
return _x('plugin', 'Enabled');
case self::NOTINSTALLED :
return _x('plugin', 'Not installed');
case self::NOTUPDATED :
return __('To update');
case self::TOBECONFIGURED :
return _x('plugin', 'Installed / not configured');
case self::NOTACTIVATED :
return _x('plugin', 'Installed / not activated');
}
return __('Error / to clean');
}
/**
* Return key for an integer plugin state
* purpose is to have a corresponding css class name
*
* @since 9.5
*
* @param integer $state see this class constants (ex self::ANEW, self::ACTIVATED)
* @return string the key
*/
static function getStateKey(int $state = 0): string {
switch ($state) {
case self::ANEW :
return "new";
case self::ACTIVATED :
return "activated";
case self::NOTINSTALLED :
return "notinstalled";
case self::NOTUPDATED :
return "notupdated";
case self::TOBECONFIGURED :
return "tobeconfigured";
case self::NOTACTIVATED :
return "notactived";
}
return "";
}
/**
* Get plugins list
*
* @since 9.3.2
*
* @return array
*/
public static function getPlugins() {
return self::$activated_plugins;
}
/**
* Check if a plugin is loaded
*
* @since 9.3.2
*
* @param string $plugin_key System name (Plugin directory)
*
* @return boolean
*/
public static function isPluginLoaded($plugin_key) {
// Make a lowercase comparison, as sometime this function is called based on
// extraction of plugin name from a classname, which does not use same naming rules than directories.
$loadedPlugins = array_map('strtolower', self::$loaded_plugins);
return in_array(strtolower($plugin_key), $loadedPlugins);
}
/**
* Check if a plugin is active
*
* @since 9.5.0
*
* @param string $plugin_key System name (Plugin directory)
*
* @return boolean
*/
public static function isPluginActive($plugin_key) {
$plugin = new self();
return $plugin->isActivated($plugin_key);
}
/**
* Set plugin loaded
*
* @since 9.3.2
* @deprecated 9.5.0
*
* @param integer $id Plugin id
* @param string $name Plugin name
*
* @return void
*/
public static function setLoaded($id, $name) {
Toolbox::deprecated();
if (!in_array($name, self::$loaded_plugins)) {
self::$loaded_plugins[] = $name;
}
}
/**
* Set plugin unloaded
*
* @since 9.3.2
* @deprecated 9.5.0
*
* @param integer $id Plugin id
*
* @return void
*/
public static function setUnloaded($id) {
Toolbox::deprecated();
$plugin = new self();
$plugin->getFromDB($id);
$plugin->unload($plugin->fields['directory']);
}
/**
* Set plugin unloaded from its name
*
* @since 9.3.2
* @deprecated 9.5.0
*
* @param integer $name System name (Plugin directory)
*
* @return void
*/
public static function setUnloadedByName($name) {
Toolbox::deprecated();
$plugin = new self();
$plugin->unload($name);
}
function rawSearchOptions() {
$tab = [];
$tab[] = [
'id' => 'common',
'name' => __('Characteristics')
];
$tab[] = [
'id' => '1',
'table' => $this->getTable(),
'field' => 'name',
'name' => __('Name'),
'datatype' => 'specific',
'massiveaction' => false, // implicit key==1
'additionalfields' => ['state', 'directory'],
];
$tab[] = [
'id' => '2',
'table' => $this->getTable(),
'field' => 'directory',
'name' => __('Directory'),
'massiveaction' => false,
'nosearch' => true,
'noremove' => true
];
$tab[] = [
'id' => '3',
'table' => $this->getTable(),
'field' => 'version',
'name' => _n('Version', 'Versions', 1),
'datatype' => 'text',
'massiveaction' => false
];
$tab[] = [
'id' => '4',
'table' => $this->getTable(),
'field' => 'license',
'name' => SoftwareLicense::getTypeName(1),
'datatype' => 'text',
'massiveaction' => false
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'state',
'name' => __('Status'),
'searchtype' => 'equals',
'noremove' => true,
'additionalfields' => ['directory'],
];
$tab[] = [
'id' => '6',
'table' => $this->getTable(),
'field' => 'author',
'name' => __('Authors')
];
$tab[] = [
'id' => '7',
'table' => $this->getTable(),
'field' => 'homepage',
'name' => __('Website'),
'datatype' => 'specific'
];
$tab[] = [
'id' => '8',
'table' => $this->getTable(),
'field' => 'id',
'name' => __('Actions'),
'massiveaction' => false,
'nosearch' => true,
'datatype' => 'specific',
'noremove' => true,
'additionalfields' => ['directory']
];
return $tab;
}
static function getSpecificValueToDisplay($field, $values, array $options = []) {
global $PLUGIN_HOOKS, $CFG_GLPI;
if (!is_array($values)) {
$values = [$field => $values];
}
switch ($field) {
case 'id':
//action...
$ID = $values[$field];
$plugin = new self;
$plugin->getFromDB($ID);
$directory = $plugin->fields['directory'];
$state = (int)$plugin->fields['state'];
if ($plugin->isLoadable($directory)) {
self::load($directory, true);
} else {
$state = self::TOBECLEANED;
}
$output = '';
if (in_array($state, [self::ACTIVATED, self::TOBECONFIGURED], true)
&& isset($PLUGIN_HOOKS['config_page'][$directory])) {
// Configuration button for activated or configurable plugins
$plugin_dir = self::getWebDir($directory, true);
$config_url = "$plugin_dir/".$PLUGIN_HOOKS['config_page'][$directory];
$output .= '<a href="' . $config_url . '" title="' . __s('Configure') . '">'
. '<i class="fas fa-wrench fa-2x"></i>'
. '<span class="sr-only">' . __s('Configure') . '</span>'
. '</a>'
. '&nbsp;';
}
if ($state === self::ACTIVATED) {
// Deactivate button for active plugins
$output .= Html::getSimpleForm(
static::getFormURL(),
['action' => 'unactivate'],
_x('button', 'Disable'),
['id' => $ID],
'fa-fw fa-toggle-on fa-2x enabled'
) . '&nbsp;';
} else if ($state === self::NOTACTIVATED) {
// Activate button for configured and up to date plugins
ob_start();
$do_activate = $plugin->checkVersions($directory);
if (!$do_activate) {
$output .= "<span class='error'>" . ob_get_contents() . "</span>";
}
ob_end_clean();
$function = 'plugin_' . $directory . '_check_prerequisites';
if (!isset($PLUGIN_HOOKS['csrf_compliant'][$directory])
|| !$PLUGIN_HOOKS['csrf_compliant'][$directory]) {
$output .= "<span class='error'>" . __('Not CSRF compliant') . "</span>";
$do_activate = false;
} else if (function_exists($function) && $do_activate) {
ob_start();
$do_activate = $function();
if (!$do_activate) {
$output .= '<span class="error">' . ob_get_contents() . '</span>';
}
ob_end_clean();
}
if ($do_activate) {
$output .= Html::getSimpleForm(
static::getFormURL(),
['action' => 'activate'],
_x('button', 'Enable'),
['id' => $ID],
'fa-fw fa-toggle-off fa-2x disabled'
) . '&nbsp;';
}
}
if (in_array($state, [self::ANEW, self::NOTINSTALLED, self::NOTUPDATED], true)) {
// Install button for new, not installed or not up to date plugins
if (function_exists("plugin_".$directory."_install")) {
$function = 'plugin_' . $directory . '_check_prerequisites';
ob_start();
$do_install = $plugin->checkVersions($directory);
if (!$do_install) {
$output .= "<span class='error'>" . ob_get_contents() . "</span>";
}
ob_end_clean();
if ($do_install && function_exists($function)) {
ob_start();
$do_install = $function();
$msg = '';
if (!$do_install) {
$msg = '<span class="error">' . ob_get_contents() . '</span>';
}
ob_end_clean();
$output .= $msg;
}
if ($state == self::NOTUPDATED) {
$msg = _x('button', 'Upgrade');
} else {
$msg = _x('button', 'Install');
}
if ($do_install) {
$output .= Html::getSimpleForm(
static::getFormURL(),
['action' => 'install'],
$msg,
['id' => $ID],
'fa-fw fa-folder-plus fa-2x'
) . '&nbsp;';
}
} else {
$missing = '';
if (!function_exists("plugin_".$directory."_install")) {
$missing .= "plugin_".$directory."_install";
}
//TRANS: %s is the list of missing functions
$output .= sprintf(__('%1$s: %2$s'), __('Non-existent function'),
$missing);
}
}
if (in_array($state, [self::ACTIVATED, self::NOTUPDATED, self::TOBECONFIGURED, self::NOTACTIVATED], true)) {
// Uninstall button for installed plugins
if (function_exists("plugin_".$directory."_uninstall")) {
$output .= Html::getSimpleForm(
static::getFormURL(),
['action' => 'uninstall'],
_x('button', 'Uninstall'),
['id' => $ID],
'fa-fw fa-folder-minus fa-2x'
) . '&nbsp;';
} else {
//TRANS: %s is the list of missing functions
$output .= sprintf(__('%1$s: %2$s'), __('Non-existent function'),
"plugin_".$directory."_uninstall");
}
} else if ($state === self::TOBECLEANED) {
$output .= Html::getSimpleForm(
static::getFormURL(),
['action' => 'clean'],
_x('button', 'Clean'),
['id' => $ID],
'fa-fw fas fa-broom fa-2x'
);
}
return "<div style='text-align:right'>$output</div>";
break;
case 'state':
$plugin = new self();
$state = $plugin->isLoadable($values['directory']) ? $values[$field] : self::TOBECLEANED;
return self::getState($state);
break;
case 'homepage':
$value = $values[$field];
if (!empty($value)) {
return "<a href=\"".Toolbox::formatOutputWebLink($value)."\" target='_blank'>
<i class='fas fa-external-link-alt fa-2x'></i><span class='sr-only'>$value</span>
</a>";
}
return "&nbsp;";
break;
case 'name':
$value = $values[$field];
$state = $values['state'];
$directory = $values['directory'];
self::load($directory); // Load plugin to give it ability to define its config_page hook
if (in_array($state, [self::ACTIVATED, self::TOBECONFIGURED])
&& isset($PLUGIN_HOOKS['config_page'][$directory])
) {
$plugin_dir = self::getWebDir($directory, true);
$config_url = "$plugin_dir/".$PLUGIN_HOOKS['config_page'][$directory];
return "<a href='$config_url'><span class='b'>$value</span></a>";
} else {
return $value;
}
break;
}
return parent::getSpecificValueToDisplay($field, $values, $options);
}
static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) {
if (!is_array($values)) {
$values = [$field => $values];
}
$options['display'] = false;
switch ($field) {
case 'state':
$tab = [
self::ANEW => _x('status', 'New'),
self::ACTIVATED => _x('plugin', 'Enabled'),
self::NOTINSTALLED => _x('plugin', 'Not installed'),
self::NOTUPDATED => __('To update'),
self::TOBECONFIGURED => _x('plugin', 'Installed / not configured'),
self::NOTACTIVATED => _x('plugin', 'Installed / not activated'),
self::TOBECLEANED => __('Error / to clean')
];
$options['value'] = $values[$field];
return Dropdown::showFromArray($name, $tab, $options);
break;
}
}
function getForbiddenStandardMassiveAction() {
$forbidden = parent::getForbiddenStandardMassiveAction();
$forbidden[] = 'update';
$forbidden[] = 'clone';
$forbidden[] = 'purge';
return $forbidden;
}
/**
* Return the system path for a given plugin key
*
* @since 9.5
*
* @param string $plugin_key plugin system key
* @param bool $full true for absolute path
*
* @return false|string the path
*/
static function getPhpDir(string $plugin_key = "", $full = true) {
$directory = false;
foreach (PLUGINS_DIRECTORIES as $plugins_directory) {
if (is_dir("$plugins_directory/$plugin_key")) {
$directory = "$plugins_directory/$plugin_key";
break;
}
}
if (!$full) {
$directory = str_replace(GLPI_ROOT, "", $directory);
}
return str_replace('\\', '/', $directory);
}
/**
* Return the web path for a given plugin key
*
* @since 9.5
*
* @param string $plugin_key plugin system key
* @param bool $full if true, append root_doc from config
* @param bool $use_url_base if true, url_base instead root_doc
*
* @return false|string the web path
*/
static function getWebDir(string $plugin_key = "", $full = true, $use_url_base = false) {
global $CFG_GLPI;
$directory = self::getPhpDir($plugin_key, false);
$directory = ltrim($directory, '/\\');
if ($full) {
$root = $use_url_base ? $CFG_GLPI['url_base'] : $CFG_GLPI["root_doc"];
$directory = "$root/$directory";
}
return str_replace('\\', '/', $directory);
}
static function getIcon() {
return "fas fa-puzzle-piece";
}
}