. * --------------------------------------------------------------------- */ /** * 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 = "$mp_title"; $cl_icon = Plugin::getIcon(); $cl_title = Plugin::getTypeName(); $classic = "$cl_title"; 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 .= '

' . 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 = '' . ob_get_contents() . ''; } 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 "\nPlugins list"; echo "
\n \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
"; } /** * 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) . '
'; } 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) . '
'; $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) . '
'; $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) . '
'; $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 .= '' . '' . '' . __s('Configure') . '' . '' . ' '; } 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' ) . ' '; } 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 .= "" . ob_get_contents() . ""; } ob_end_clean(); $function = 'plugin_' . $directory . '_check_prerequisites'; if (!isset($PLUGIN_HOOKS['csrf_compliant'][$directory]) || !$PLUGIN_HOOKS['csrf_compliant'][$directory]) { $output .= "" . __('Not CSRF compliant') . ""; $do_activate = false; } else if (function_exists($function) && $do_activate) { ob_start(); $do_activate = $function(); if (!$do_activate) { $output .= '' . ob_get_contents() . ''; } 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' ) . ' '; } } 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 .= "" . ob_get_contents() . ""; } ob_end_clean(); if ($do_install && function_exists($function)) { ob_start(); $do_install = $function(); $msg = ''; if (!$do_install) { $msg = '' . ob_get_contents() . ''; } 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' ) . ' '; } } 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' ) . ' '; } 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 "
$output
"; 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 " $value "; } return " "; 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 "$value"; } 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"; } }