/lib/classes/plugin_manager.php
PHP | 2377 lines | 1553 code | 282 blank | 542 comment | 202 complexity | c62aa6e4ccd9a84246afb016811e947e MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * Defines classes used for plugins management
- *
- * This library provides a unified interface to various plugin types in
- * Moodle. It is mainly used by the plugins management admin page and the
- * plugins check page during the upgrade.
- *
- * @package core
- * @copyright 2011 David Mudrak <david@moodle.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- defined('MOODLE_INTERNAL') || die();
- /**
- * Singleton class providing general plugins management functionality.
- */
- class core_plugin_manager {
- /** the plugin is shipped with standard Moodle distribution */
- const PLUGIN_SOURCE_STANDARD = 'std';
- /** the plugin is added extension */
- const PLUGIN_SOURCE_EXTENSION = 'ext';
- /** the plugin uses neither database nor capabilities, no versions */
- const PLUGIN_STATUS_NODB = 'nodb';
- /** the plugin is up-to-date */
- const PLUGIN_STATUS_UPTODATE = 'uptodate';
- /** the plugin is about to be installed */
- const PLUGIN_STATUS_NEW = 'new';
- /** the plugin is about to be upgraded */
- const PLUGIN_STATUS_UPGRADE = 'upgrade';
- /** the standard plugin is about to be deleted */
- const PLUGIN_STATUS_DELETE = 'delete';
- /** the version at the disk is lower than the one already installed */
- const PLUGIN_STATUS_DOWNGRADE = 'downgrade';
- /** the plugin is installed but missing from disk */
- const PLUGIN_STATUS_MISSING = 'missing';
- /** the given requirement/dependency is fulfilled */
- const REQUIREMENT_STATUS_OK = 'ok';
- /** the plugin requires higher core/other plugin version than is currently installed */
- const REQUIREMENT_STATUS_OUTDATED = 'outdated';
- /** the required dependency is not installed */
- const REQUIREMENT_STATUS_MISSING = 'missing';
- /** the current Moodle version is too high for plugin. */
- const REQUIREMENT_STATUS_NEWER = 'newer';
- /** the required dependency is available in the plugins directory */
- const REQUIREMENT_AVAILABLE = 'available';
- /** the required dependency is available in the plugins directory */
- const REQUIREMENT_UNAVAILABLE = 'unavailable';
- /** the moodle version is explicitly supported */
- const VERSION_SUPPORTED = 'supported';
- /** the moodle version is not explicitly supported */
- const VERSION_NOT_SUPPORTED = 'notsupported';
- /** the plugin does not specify supports */
- const VERSION_NO_SUPPORTS = 'nosupports';
- /** @var core_plugin_manager holds the singleton instance */
- protected static $singletoninstance;
- /** @var array of raw plugins information */
- protected $pluginsinfo = null;
- /** @var array of raw subplugins information */
- protected $subpluginsinfo = null;
- /** @var array cache information about availability in the plugins directory if requesting "at least" version */
- protected $remotepluginsinfoatleast = null;
- /** @var array cache information about availability in the plugins directory if requesting exact version */
- protected $remotepluginsinfoexact = null;
- /** @var array list of installed plugins $name=>$version */
- protected $installedplugins = null;
- /** @var array list of all enabled plugins $name=>$name */
- protected $enabledplugins = null;
- /** @var array list of all enabled plugins $name=>$diskversion */
- protected $presentplugins = null;
- /** @var array reordered list of plugin types */
- protected $plugintypes = null;
- /** @var \core\update\code_manager code manager to use for plugins code operations */
- protected $codemanager = null;
- /** @var \core\update\api client instance to use for accessing download.moodle.org/api/ */
- protected $updateapiclient = null;
- /**
- * Direct initiation not allowed, use the factory method {@link self::instance()}
- */
- protected function __construct() {
- }
- /**
- * Sorry, this is singleton
- */
- protected function __clone() {
- }
- /**
- * Factory method for this class
- *
- * @return core_plugin_manager the singleton instance
- */
- public static function instance() {
- if (is_null(static::$singletoninstance)) {
- static::$singletoninstance = new static();
- }
- return static::$singletoninstance;
- }
- /**
- * Reset all caches.
- * @param bool $phpunitreset
- */
- public static function reset_caches($phpunitreset = false) {
- if ($phpunitreset) {
- static::$singletoninstance = null;
- } else {
- if (static::$singletoninstance) {
- static::$singletoninstance->pluginsinfo = null;
- static::$singletoninstance->subpluginsinfo = null;
- static::$singletoninstance->remotepluginsinfoatleast = null;
- static::$singletoninstance->remotepluginsinfoexact = null;
- static::$singletoninstance->installedplugins = null;
- static::$singletoninstance->enabledplugins = null;
- static::$singletoninstance->presentplugins = null;
- static::$singletoninstance->plugintypes = null;
- static::$singletoninstance->codemanager = null;
- static::$singletoninstance->updateapiclient = null;
- }
- }
- $cache = cache::make('core', 'plugin_manager');
- $cache->purge();
- }
- /**
- * Returns the result of {@link core_component::get_plugin_types()} ordered for humans
- *
- * @see self::reorder_plugin_types()
- * @return array (string)name => (string)location
- */
- public function get_plugin_types() {
- if (func_num_args() > 0) {
- if (!func_get_arg(0)) {
- throw new coding_exception('core_plugin_manager->get_plugin_types() does not support relative paths.');
- }
- }
- if ($this->plugintypes) {
- return $this->plugintypes;
- }
- $this->plugintypes = $this->reorder_plugin_types(core_component::get_plugin_types());
- return $this->plugintypes;
- }
- /**
- * Load list of installed plugins,
- * always call before using $this->installedplugins.
- *
- * This method is caching results for all plugins.
- */
- protected function load_installed_plugins() {
- global $DB, $CFG;
- if ($this->installedplugins) {
- return;
- }
- if (empty($CFG->version)) {
- // Nothing installed yet.
- $this->installedplugins = array();
- return;
- }
- $cache = cache::make('core', 'plugin_manager');
- $installed = $cache->get('installed');
- if (is_array($installed)) {
- $this->installedplugins = $installed;
- return;
- }
- $this->installedplugins = array();
- $versions = $DB->get_records('config_plugins', array('name'=>'version'));
- foreach ($versions as $version) {
- $parts = explode('_', $version->plugin, 2);
- if (!isset($parts[1])) {
- // Invalid component, there must be at least one "_".
- continue;
- }
- // Do not verify here if plugin type and name are valid.
- $this->installedplugins[$parts[0]][$parts[1]] = $version->value;
- }
- foreach ($this->installedplugins as $key => $value) {
- ksort($this->installedplugins[$key]);
- }
- $cache->set('installed', $this->installedplugins);
- }
- /**
- * Return list of installed plugins of given type.
- * @param string $type
- * @return array $name=>$version
- */
- public function get_installed_plugins($type) {
- $this->load_installed_plugins();
- if (isset($this->installedplugins[$type])) {
- return $this->installedplugins[$type];
- }
- return array();
- }
- /**
- * Load list of all enabled plugins,
- * call before using $this->enabledplugins.
- *
- * This method is caching results from individual plugin info classes.
- */
- protected function load_enabled_plugins() {
- global $CFG;
- if ($this->enabledplugins) {
- return;
- }
- if (empty($CFG->version)) {
- $this->enabledplugins = array();
- return;
- }
- $cache = cache::make('core', 'plugin_manager');
- $enabled = $cache->get('enabled');
- if (is_array($enabled)) {
- $this->enabledplugins = $enabled;
- return;
- }
- $this->enabledplugins = array();
- require_once($CFG->libdir.'/adminlib.php');
- $plugintypes = core_component::get_plugin_types();
- foreach ($plugintypes as $plugintype => $fulldir) {
- $plugininfoclass = static::resolve_plugininfo_class($plugintype);
- if (class_exists($plugininfoclass)) {
- $enabled = $plugininfoclass::get_enabled_plugins();
- if (!is_array($enabled)) {
- continue;
- }
- $this->enabledplugins[$plugintype] = $enabled;
- }
- }
- $cache->set('enabled', $this->enabledplugins);
- }
- /**
- * Get list of enabled plugins of given type,
- * the result may contain missing plugins.
- *
- * @param string $type
- * @return array|null list of enabled plugins of this type, null if unknown
- */
- public function get_enabled_plugins($type) {
- $this->load_enabled_plugins();
- if (isset($this->enabledplugins[$type])) {
- return $this->enabledplugins[$type];
- }
- return null;
- }
- /**
- * Load list of all present plugins - call before using $this->presentplugins.
- */
- protected function load_present_plugins() {
- if ($this->presentplugins) {
- return;
- }
- $cache = cache::make('core', 'plugin_manager');
- $present = $cache->get('present');
- if (is_array($present)) {
- $this->presentplugins = $present;
- return;
- }
- $this->presentplugins = array();
- $plugintypes = core_component::get_plugin_types();
- foreach ($plugintypes as $type => $typedir) {
- $plugs = core_component::get_plugin_list($type);
- foreach ($plugs as $plug => $fullplug) {
- $module = new stdClass();
- $plugin = new stdClass();
- $plugin->version = null;
- include($fullplug.'/version.php');
- // Check if the legacy $module syntax is still used.
- if (!is_object($module) or (count((array)$module) > 0)) {
- debugging('Unsupported $module syntax detected in version.php of the '.$type.'_'.$plug.' plugin.');
- $skipcache = true;
- }
- // Check if the component is properly declared.
- if (empty($plugin->component) or ($plugin->component !== $type.'_'.$plug)) {
- debugging('Plugin '.$type.'_'.$plug.' does not declare valid $plugin->component in its version.php.');
- $skipcache = true;
- }
- $this->presentplugins[$type][$plug] = $plugin;
- }
- }
- if (empty($skipcache)) {
- $cache->set('present', $this->presentplugins);
- }
- }
- /**
- * Get list of present plugins of given type.
- *
- * @param string $type
- * @return array|null list of presnet plugins $name=>$diskversion, null if unknown
- */
- public function get_present_plugins($type) {
- $this->load_present_plugins();
- if (isset($this->presentplugins[$type])) {
- return $this->presentplugins[$type];
- }
- return null;
- }
- /**
- * Returns a tree of known plugins and information about them
- *
- * @return array 2D array. The first keys are plugin type names (e.g. qtype);
- * the second keys are the plugin local name (e.g. multichoice); and
- * the values are the corresponding objects extending {@link \core\plugininfo\base}
- */
- public function get_plugins() {
- $this->init_pluginsinfo_property();
- // Make sure all types are initialised.
- foreach ($this->pluginsinfo as $plugintype => $list) {
- if ($list === null) {
- $this->get_plugins_of_type($plugintype);
- }
- }
- return $this->pluginsinfo;
- }
- /**
- * Returns list of known plugins of the given type.
- *
- * This method returns the subset of the tree returned by {@link self::get_plugins()}.
- * If the given type is not known, empty array is returned.
- *
- * @param string $type plugin type, e.g. 'mod' or 'workshopallocation'
- * @return \core\plugininfo\base[] (string)plugin name (e.g. 'workshop') => corresponding subclass of {@link \core\plugininfo\base}
- */
- public function get_plugins_of_type($type) {
- global $CFG;
- $this->init_pluginsinfo_property();
- if (!array_key_exists($type, $this->pluginsinfo)) {
- return array();
- }
- if (is_array($this->pluginsinfo[$type])) {
- return $this->pluginsinfo[$type];
- }
- $types = core_component::get_plugin_types();
- if (!isset($types[$type])) {
- // Orphaned subplugins!
- $plugintypeclass = static::resolve_plugininfo_class($type);
- $this->pluginsinfo[$type] = $plugintypeclass::get_plugins($type, null, $plugintypeclass, $this);
- return $this->pluginsinfo[$type];
- }
- /** @var \core\plugininfo\base $plugintypeclass */
- $plugintypeclass = static::resolve_plugininfo_class($type);
- $plugins = $plugintypeclass::get_plugins($type, $types[$type], $plugintypeclass, $this);
- $this->pluginsinfo[$type] = $plugins;
- return $this->pluginsinfo[$type];
- }
- /**
- * Init placeholder array for plugin infos.
- */
- protected function init_pluginsinfo_property() {
- if (is_array($this->pluginsinfo)) {
- return;
- }
- $this->pluginsinfo = array();
- $plugintypes = $this->get_plugin_types();
- foreach ($plugintypes as $plugintype => $plugintyperootdir) {
- $this->pluginsinfo[$plugintype] = null;
- }
- // Add orphaned subplugin types.
- $this->load_installed_plugins();
- foreach ($this->installedplugins as $plugintype => $unused) {
- if (!isset($plugintypes[$plugintype])) {
- $this->pluginsinfo[$plugintype] = null;
- }
- }
- }
- /**
- * Find the plugin info class for given type.
- *
- * @param string $type
- * @return string name of pluginfo class for give plugin type
- */
- public static function resolve_plugininfo_class($type) {
- $plugintypes = core_component::get_plugin_types();
- if (!isset($plugintypes[$type])) {
- return '\core\plugininfo\orphaned';
- }
- $parent = core_component::get_subtype_parent($type);
- if ($parent) {
- $class = '\\'.$parent.'\plugininfo\\' . $type;
- if (class_exists($class)) {
- $plugintypeclass = $class;
- } else {
- if ($dir = core_component::get_component_directory($parent)) {
- // BC only - use namespace instead!
- if (file_exists("$dir/adminlib.php")) {
- global $CFG;
- include_once("$dir/adminlib.php");
- }
- if (class_exists('plugininfo_' . $type)) {
- $plugintypeclass = 'plugininfo_' . $type;
- debugging('Class "'.$plugintypeclass.'" is deprecated, migrate to "'.$class.'"', DEBUG_DEVELOPER);
- } else {
- debugging('Subplugin type "'.$type.'" should define class "'.$class.'"', DEBUG_DEVELOPER);
- $plugintypeclass = '\core\plugininfo\general';
- }
- } else {
- $plugintypeclass = '\core\plugininfo\general';
- }
- }
- } else {
- $class = '\core\plugininfo\\' . $type;
- if (class_exists($class)) {
- $plugintypeclass = $class;
- } else {
- debugging('All standard types including "'.$type.'" should have plugininfo class!', DEBUG_DEVELOPER);
- $plugintypeclass = '\core\plugininfo\general';
- }
- }
- if (!in_array('core\plugininfo\base', class_parents($plugintypeclass))) {
- throw new coding_exception('Class ' . $plugintypeclass . ' must extend \core\plugininfo\base');
- }
- return $plugintypeclass;
- }
- /**
- * Returns list of all known subplugins of the given plugin.
- *
- * For plugins that do not provide subplugins (i.e. there is no support for it),
- * empty array is returned.
- *
- * @param string $component full component name, e.g. 'mod_workshop'
- * @return array (string) component name (e.g. 'workshopallocation_random') => subclass of {@link \core\plugininfo\base}
- */
- public function get_subplugins_of_plugin($component) {
- $pluginfo = $this->get_plugin_info($component);
- if (is_null($pluginfo)) {
- return array();
- }
- $subplugins = $this->get_subplugins();
- if (!isset($subplugins[$pluginfo->component])) {
- return array();
- }
- $list = array();
- foreach ($subplugins[$pluginfo->component] as $subdata) {
- foreach ($this->get_plugins_of_type($subdata->type) as $subpluginfo) {
- $list[$subpluginfo->component] = $subpluginfo;
- }
- }
- return $list;
- }
- /**
- * Returns list of plugins that define their subplugins and the information
- * about them from the db/subplugins.json file.
- *
- * @return array with keys like 'mod_quiz', and values the data from the
- * corresponding db/subplugins.json file.
- */
- public function get_subplugins() {
- if (is_array($this->subpluginsinfo)) {
- return $this->subpluginsinfo;
- }
- $plugintypes = core_component::get_plugin_types();
- $this->subpluginsinfo = array();
- foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
- foreach (core_component::get_plugin_list($type) as $plugin => $componentdir) {
- $component = $type.'_'.$plugin;
- $subplugins = core_component::get_subplugins($component);
- if (!$subplugins) {
- continue;
- }
- $this->subpluginsinfo[$component] = array();
- foreach ($subplugins as $subplugintype => $ignored) {
- $subplugin = new stdClass();
- $subplugin->type = $subplugintype;
- $subplugin->typerootdir = $plugintypes[$subplugintype];
- $this->subpluginsinfo[$component][$subplugintype] = $subplugin;
- }
- }
- }
- return $this->subpluginsinfo;
- }
- /**
- * Returns the name of the plugin that defines the given subplugin type
- *
- * If the given subplugin type is not actually a subplugin, returns false.
- *
- * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
- * @return false|string the name of the parent plugin, eg. mod_workshop
- */
- public function get_parent_of_subplugin($subplugintype) {
- $parent = core_component::get_subtype_parent($subplugintype);
- if (!$parent) {
- return false;
- }
- return $parent;
- }
- /**
- * Returns a localized name of a given plugin
- *
- * @param string $component name of the plugin, eg mod_workshop or auth_ldap
- * @return string
- */
- public function plugin_name($component) {
- $pluginfo = $this->get_plugin_info($component);
- if (is_null($pluginfo)) {
- throw new moodle_exception('err_unknown_plugin', 'core_plugin', '', array('plugin' => $component));
- }
- return $pluginfo->displayname;
- }
- /**
- * Returns a localized name of a plugin typed in singular form
- *
- * Most plugin types define their names in core_plugin lang file. In case of subplugins,
- * we try to ask the parent plugin for the name. In the worst case, we will return
- * the value of the passed $type parameter.
- *
- * @param string $type the type of the plugin, e.g. mod or workshopform
- * @return string
- */
- public function plugintype_name($type) {
- if (get_string_manager()->string_exists('type_' . $type, 'core_plugin')) {
- // For most plugin types, their names are defined in core_plugin lang file.
- return get_string('type_' . $type, 'core_plugin');
- } else if ($parent = $this->get_parent_of_subplugin($type)) {
- // If this is a subplugin, try to ask the parent plugin for the name.
- if (get_string_manager()->string_exists('subplugintype_' . $type, $parent)) {
- return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type, $parent);
- } else {
- return $this->plugin_name($parent) . ' / ' . $type;
- }
- } else {
- return $type;
- }
- }
- /**
- * Returns a localized name of a plugin type in plural form
- *
- * Most plugin types define their names in core_plugin lang file. In case of subplugins,
- * we try to ask the parent plugin for the name. In the worst case, we will return
- * the value of the passed $type parameter.
- *
- * @param string $type the type of the plugin, e.g. mod or workshopform
- * @return string
- */
- public function plugintype_name_plural($type) {
- if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
- // For most plugin types, their names are defined in core_plugin lang file.
- return get_string('type_' . $type . '_plural', 'core_plugin');
- } else if ($parent = $this->get_parent_of_subplugin($type)) {
- // If this is a subplugin, try to ask the parent plugin for the name.
- if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
- return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
- } else {
- return $this->plugin_name($parent) . ' / ' . $type;
- }
- } else {
- return $type;
- }
- }
- /**
- * Returns information about the known plugin, or null
- *
- * @param string $component frankenstyle component name.
- * @return \core\plugininfo\base|null the corresponding plugin information.
- */
- public function get_plugin_info($component) {
- list($type, $name) = core_component::normalize_component($component);
- $plugins = $this->get_plugins_of_type($type);
- if (isset($plugins[$name])) {
- return $plugins[$name];
- } else {
- return null;
- }
- }
- /**
- * Check to see if the current version of the plugin seems to be a checkout of an external repository.
- *
- * @param string $component frankenstyle component name
- * @return false|string
- */
- public function plugin_external_source($component) {
- $plugininfo = $this->get_plugin_info($component);
- if (is_null($plugininfo)) {
- return false;
- }
- $pluginroot = $plugininfo->rootdir;
- if (is_dir($pluginroot.'/.git')) {
- return 'git';
- }
- if (is_file($pluginroot.'/.git')) {
- return 'git-submodule';
- }
- if (is_dir($pluginroot.'/CVS')) {
- return 'cvs';
- }
- if (is_dir($pluginroot.'/.svn')) {
- return 'svn';
- }
- if (is_dir($pluginroot.'/.hg')) {
- return 'mercurial';
- }
- return false;
- }
- /**
- * Get a list of any other plugins that require this one.
- * @param string $component frankenstyle component name.
- * @return array of frankensyle component names that require this one.
- */
- public function other_plugins_that_require($component) {
- $others = array();
- foreach ($this->get_plugins() as $type => $plugins) {
- foreach ($plugins as $plugin) {
- $required = $plugin->get_other_required_plugins();
- if (isset($required[$component])) {
- $others[] = $plugin->component;
- }
- }
- }
- return $others;
- }
- /**
- * Check a dependencies list against the list of installed plugins.
- * @param array $dependencies compenent name to required version or ANY_VERSION.
- * @return bool true if all the dependencies are satisfied.
- */
- public function are_dependencies_satisfied($dependencies) {
- foreach ($dependencies as $component => $requiredversion) {
- $otherplugin = $this->get_plugin_info($component);
- if (is_null($otherplugin)) {
- return false;
- }
- if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) {
- return false;
- }
- }
- return true;
- }
- /**
- * Checks all dependencies for all installed plugins
- *
- * This is used by install and upgrade. The array passed by reference as the second
- * argument is populated with the list of plugins that have failed dependencies (note that
- * a single plugin can appear multiple times in the $failedplugins).
- *
- * @param int $moodleversion the version from version.php.
- * @param array $failedplugins to return the list of plugins with non-satisfied dependencies
- * @param int $branch the current moodle branch, null if not provided
- * @return bool true if all the dependencies are satisfied for all plugins.
- */
- public function all_plugins_ok($moodleversion, &$failedplugins = array(), $branch = null) {
- global $CFG;
- if (empty($branch)) {
- $branch = $CFG->branch;
- if (empty($branch)) {
- // During initial install there is no branch set.
- require($CFG->dirroot . '/version.php');
- $branch = (int)$branch;
- // Force CFG->branch to int value during install.
- $CFG->branch = $branch;
- }
- }
- $return = true;
- foreach ($this->get_plugins() as $type => $plugins) {
- foreach ($plugins as $plugin) {
- if (!$plugin->is_core_dependency_satisfied($moodleversion)) {
- $return = false;
- $failedplugins[] = $plugin->component;
- }
- if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
- $return = false;
- $failedplugins[] = $plugin->component;
- }
- if (!$plugin->is_core_compatible_satisfied($branch)) {
- $return = false;
- $failedplugins[] = $plugin->component;
- }
- }
- }
- return $return;
- }
- /**
- * Resolve requirements and dependencies of a plugin.
- *
- * Returns an array of objects describing the requirement/dependency,
- * indexed by the frankenstyle name of the component. The returned array
- * can be empty. The objects in the array have following properties:
- *
- * ->(numeric)hasver
- * ->(numeric)reqver
- * ->(string)status
- * ->(string)availability
- *
- * @param \core\plugininfo\base $plugin the plugin we are checking
- * @param null|string|int|double $moodleversion explicit moodle core version to check against, defaults to $CFG->version
- * @param null|string|int $moodlebranch explicit moodle core branch to check against, defaults to $CFG->branch
- * @return array of objects
- */
- public function resolve_requirements(\core\plugininfo\base $plugin, $moodleversion=null, $moodlebranch=null) {
- global $CFG;
- if ($plugin->versiondisk === null) {
- // Missing from disk, we have no version.php to read from.
- return array();
- }
- if ($moodleversion === null) {
- $moodleversion = $CFG->version;
- }
- if ($moodlebranch === null) {
- $moodlebranch = $CFG->branch;
- }
- $reqs = array();
- $reqcore = $this->resolve_core_requirements($plugin, $moodleversion, $moodlebranch);
- if (!empty($reqcore)) {
- $reqs['core'] = $reqcore;
- }
- foreach ($plugin->get_other_required_plugins() as $reqplug => $reqver) {
- $reqs[$reqplug] = $this->resolve_dependency_requirements($plugin, $reqplug, $reqver, $moodlebranch);
- }
- return $reqs;
- }
- /**
- * Helper method to resolve plugin's requirements on the moodle core.
- *
- * @param \core\plugininfo\base $plugin the plugin we are checking
- * @param string|int|double $moodleversion moodle core branch to check against
- * @return stdObject
- */
- protected function resolve_core_requirements(\core\plugininfo\base $plugin, $moodleversion, $moodlebranch) {
- $reqs = (object)array(
- 'hasver' => null,
- 'reqver' => null,
- 'status' => null,
- 'availability' => null,
- );
- $reqs->hasver = $moodleversion;
- if (empty($plugin->versionrequires)) {
- $reqs->reqver = ANY_VERSION;
- } else {
- $reqs->reqver = $plugin->versionrequires;
- }
- if ($plugin->is_core_dependency_satisfied($moodleversion)) {
- $reqs->status = self::REQUIREMENT_STATUS_OK;
- } else {
- $reqs->status = self::REQUIREMENT_STATUS_OUTDATED;
- }
- // Now check if there is an explicit incompatible, supersedes requires.
- if (isset($plugin->pluginincompatible) && $plugin->pluginincompatible != null) {
- if (!$plugin->is_core_compatible_satisfied($moodlebranch)) {
- $reqs->status = self::REQUIREMENT_STATUS_NEWER;
- }
- }
- return $reqs;
- }
- /**
- * Helper method to resolve plugin's dependecies on other plugins.
- *
- * @param \core\plugininfo\base $plugin the plugin we are checking
- * @param string $otherpluginname
- * @param string|int $requiredversion
- * @param string|int $moodlebranch explicit moodle core branch to check against, defaults to $CFG->branch
- * @return stdClass
- */
- protected function resolve_dependency_requirements(\core\plugininfo\base $plugin, $otherpluginname,
- $requiredversion, $moodlebranch) {
- $reqs = (object)array(
- 'hasver' => null,
- 'reqver' => null,
- 'status' => null,
- 'availability' => null,
- );
- $otherplugin = $this->get_plugin_info($otherpluginname);
- if ($otherplugin !== null) {
- // The required plugin is installed.
- $reqs->hasver = $otherplugin->versiondisk;
- $reqs->reqver = $requiredversion;
- // Check it has sufficient version.
- if ($requiredversion == ANY_VERSION or $otherplugin->versiondisk >= $requiredversion) {
- $reqs->status = self::REQUIREMENT_STATUS_OK;
- } else {
- $reqs->status = self::REQUIREMENT_STATUS_OUTDATED;
- }
- } else {
- // The required plugin is not installed.
- $reqs->hasver = null;
- $reqs->reqver = $requiredversion;
- $reqs->status = self::REQUIREMENT_STATUS_MISSING;
- }
- if ($reqs->status !== self::REQUIREMENT_STATUS_OK) {
- if ($this->is_remote_plugin_available($otherpluginname, $requiredversion, false)) {
- $reqs->availability = self::REQUIREMENT_AVAILABLE;
- } else {
- $reqs->availability = self::REQUIREMENT_UNAVAILABLE;
- }
- }
- return $reqs;
- }
- /**
- * Helper method to determine whether a moodle version is explicitly supported.
- *
- * @param \core\plugininfo\base $plugin the plugin we are checking
- * @param int $branch the moodle branch to check support for
- * @return string
- */
- public function check_explicitly_supported($plugin, $branch) : string {
- // Check for correctly formed supported.
- if (isset($plugin->pluginsupported)) {
- // Broken apart for readability.
- $error = false;
- if (!is_array($plugin->pluginsupported)) {
- $error = true;
- }
- if (!is_int($plugin->pluginsupported[0]) || !is_int($plugin->pluginsupported[1])) {
- $error = true;
- }
- if (count($plugin->pluginsupported) != 2) {
- $error = true;
- }
- if ($error) {
- throw new coding_exception(get_string('err_supported_syntax', 'core_plugin'));
- }
- }
- if (isset($plugin->pluginsupported) && $plugin->pluginsupported != null) {
- if ($plugin->pluginsupported[0] <= $branch && $branch <= $plugin->pluginsupported[1]) {
- return self::VERSION_SUPPORTED;
- } else {
- return self::VERSION_NOT_SUPPORTED;
- }
- } else {
- // If supports aren't specified, but incompatible is, return not supported if not incompatible.
- if (!isset($plugin->pluginsupported) && isset($plugin->pluginincompatible) && !empty($plugin->pluginincompatible)) {
- if (!$plugin->is_core_compatible_satisfied($branch)) {
- return self::VERSION_NOT_SUPPORTED;
- }
- }
- return self::VERSION_NO_SUPPORTS;
- }
- }
- /**
- * Is the given plugin version available in the plugins directory?
- *
- * See {@link self::get_remote_plugin_info()} for the full explanation of how the $version
- * parameter is interpretted.
- *
- * @param string $component plugin frankenstyle name
- * @param string|int $version ANY_VERSION or the version number
- * @param bool $exactmatch false if "given version or higher" is requested
- * @return boolean
- */
- public function is_remote_plugin_available($component, $version, $exactmatch) {
- $info = $this->get_remote_plugin_info($component, $version, $exactmatch);
- if (empty($info)) {
- // There is no available plugin of that name.
- return false;
- }
- if (empty($info->version)) {
- // Plugin is known, but no suitable version was found.
- return false;
- }
- return true;
- }
- /**
- * Can the given plugin version be installed via the admin UI?
- *
- * This check should be used whenever attempting to install a plugin from
- * the plugins directory (new install, available update, missing dependency).
- *
- * @param string $component
- * @param int $version version number
- * @param string $reason returned code of the reason why it is not
- * @return boolean
- */
- public function is_remote_plugin_installable($component, $version, &$reason=null) {
- global $CFG;
- // Make sure the feature is not disabled.
- if (!empty($CFG->disableupdateautodeploy)) {
- $reason = 'disabled';
- return false;
- }
- // Make sure the version is available.
- if (!$this->is_remote_plugin_available($component, $version, true)) {
- $reason = 'remoteunavailable';
- return false;
- }
- // Make sure the plugin type root directory is writable.
- list($plugintype, $pluginname) = core_component::normalize_component($component);
- if (!$this->is_plugintype_writable($plugintype)) {
- $reason = 'notwritableplugintype';
- return false;
- }
- $remoteinfo = $this->get_remote_plugin_info($component, $version, true);
- $localinfo = $this->get_plugin_info($component);
- if ($localinfo) {
- // If the plugin is already present, prevent downgrade.
- if ($localinfo->versiondb > $remoteinfo->version->version) {
- $reason = 'cannotdowngrade';
- return false;
- }
- // Make sure we have write access to all the existing code.
- if (is_dir($localinfo->rootdir)) {
- if (!$this->is_plugin_folder_removable($component)) {
- $reason = 'notwritableplugin';
- return false;
- }
- }
- }
- // Looks like it could work.
- return true;
- }
- /**
- * Given the list of remote plugin infos, return just those installable.
- *
- * This is typically used on lists returned by
- * {@link self::available_updates()} or {@link self::missing_dependencies()}
- * to perform bulk installation of remote plugins.
- *
- * @param array $remoteinfos list of {@link \core\update\remote_info}
- * @return array
- */
- public function filter_installable($remoteinfos) {
- global $CFG;
- if (!empty($CFG->disableupdateautodeploy)) {
- return array();
- }
- if (empty($remoteinfos)) {
- return array();
- }
- $installable = array();
- foreach ($remoteinfos as $index => $remoteinfo) {
- if ($this->is_remote_plugin_installable($remoteinfo->component, $remoteinfo->version->version)) {
- $installable[$index] = $remoteinfo;
- }
- }
- return $installable;
- }
- /**
- * Returns information about a plugin in the plugins directory.
- *
- * This is typically used when checking for available dependencies (in
- * which case the $version represents minimal version we need), or
- * when installing an available update or a new plugin from the plugins
- * directory (in which case the $version is exact version we are
- * interested in). The interpretation of the $version is controlled
- * by the $exactmatch argument.
- *
- * If a plugin with the given component name is found, data about the
- * plugin are returned as an object. The ->version property of the object
- * contains the information about the particular plugin version that
- * matches best the given critera. The ->version property is false if no
- * suitable version of the plugin was found (yet the plugin itself is
- * known).
- *
- * See {@link \core\update\api::validate_pluginfo_format()} for the
- * returned data structure.
- *
- * @param string $component plugin frankenstyle name
- * @param string|int $version ANY_VERSION or the version number
- * @param bool $exactmatch false if "given version or higher" is requested
- * @return \core\update\remote_info|bool
- */
- public function get_remote_plugin_info($component, $version, $exactmatch) {
- if ($exactmatch and $version == ANY_VERSION) {
- throw new coding_exception('Invalid request for exactly any version, it does not make sense.');
- }
- $client = $this->get_update_api_client();
- if ($exactmatch) {
- // Use client's get_plugin_info() method.
- if (!isset($this->remotepluginsinfoexact[$component][$version])) {
- $this->remotepluginsinfoexact[$component][$version] = $client->get_plugin_info($component, $version);
- }
- return $this->remotepluginsinfoexact[$component][$version];
- } else {
- // Use client's find_plugin() method.
- if (!isset($this->remotepluginsinfoatleast[$component][$version])) {
- $this->remotepluginsinfoatleast[$component][$version] = $client->find_plugin($component, $version);
- }
- return $this->remotepluginsinfoatleast[$component][$version];
- }
- }
- /**
- * Obtain the plugin ZIP file from the given URL
- *
- * The caller is supposed to know both downloads URL and the MD5 hash of
- * the ZIP contents in advance, typically by using the API requests against
- * the plugins directory.
- *
- * @param string $url
- * @param string $md5
- * @return string|bool full path to the file, false on error
- */
- public function get_remote_plugin_zip($url, $md5) {
- global $CFG;
- if (!empty($CFG->disableupdateautodeploy)) {
- return false;
- }
- return $this->get_code_manager()->get_remote_plugin_zip($url, $md5);
- }
- /**
- * Extracts the saved plugin ZIP file.
- *
- * Returns the list of files found in the ZIP. The format of that list is
- * array of (string)filerelpath => (bool|string) where the array value is
- * either true or a string describing the problematic file.
- *
- * @see zip_packer::extract_to_pathname()
- * @param string $zipfilepath full path to the saved ZIP file
- * @param string $targetdir full path to the directory to extract the ZIP file to
- * @param string $rootdir explicitly rename the root directory of the ZIP into this non-empty value
- * @return array list of extracted files as returned by {@link zip_packer::extract_to_pathname()}
- */
- public function unzip_plugin_file($zipfilepath, $targetdir, $rootdir = '') {
- return $this->get_code_manager()->unzip_plugin_file($zipfilepath, $targetdir, $rootdir);
- }
- /**
- * Detects the plugin's name from its ZIP file.
- *
- * Plugin ZIP packages are expected to contain a single directory and the
- * directory name would become the plugin name once extracted to the Moodle
- * dirroot.
- *
- * @param string $zipfilepath full path to the ZIP files
- * @return string|bool false on error
- */
- public function get_plugin_zip_root_dir($zipfilepath) {
- return $this->get_code_manager()->get_plugin_zip_root_dir($zipfilepath);
- }
- /**
- * Return a list of missing dependencies.
- *
- * This should provide the full list of plugins that should be installed to
- * fulfill the requirements of all plugins, if possible.
- *
- * @param bool $availableonly return only available missing dependencies
- * @return array of \core\update\remote_info|bool indexed by the component name
- */
- public function missing_dependencies($availableonly=false) {
- $dependencies = array();
- foreach ($this->get_plugins() as $plugintype => $pluginfos) {
- foreach ($pluginfos as $pluginname => $pluginfo) {
- foreach ($this->resolve_requirements($pluginfo) as $reqname => $reqinfo) {
- if ($reqname === 'core') {
- continue;
- }
- if ($reqinfo->status != self::REQUIREMENT_STATUS_OK) {
- if ($reqinfo->availability == self::REQUIREMENT_AVAILABLE) {
- $remoteinfo = $this->get_remote_plugin_info($reqname, $reqinfo->reqver, false);
- if (empty($dependencies[$reqname])) {
- $dependencies[$reqname] = $remoteinfo;
- } else {
- // If resolving requirements has led to two different versions of the same
- // remote plugin, pick the higher version. This can happen in cases like one
- // plugin requiring ANY_VERSION and another plugin requiring specific higher
- // version with lower maturity of a remote plugin.
- if ($remoteinfo->version->version > $dependencies[$reqname]->version->version) {
- $dependencies[$reqname] = $remoteinfo;
- }
- }
- } else {
- if (!isset($dependencies[$reqname])) {
- // Unable to find a plugin fulfilling the requirements.
- $dependencies[$reqname] = false;
- }
- }
- }
- }
- }
- }
- if ($availableonly) {
- foreach ($dependencies as $component => $info) {
- if (empty($info) or empty($info->version)) {
- unset($dependencies[$component]);
- }
- }
- }
- return $dependencies;
- }
- /**
- * Is it possible to uninstall the given plugin?
- *
- * False is returned if the plugininfo subclass declares the uninstall should
- * not be allowed via {@link \core\plugininfo\base::is_uninstall_allowed()} or if the
- * core vetoes it (e.g. becase the plugin or some of its subplugins is required
- * by some other installed plugin).
- *
- * @param string $component full frankenstyle name, e.g. mod_foobar
- * @return bool
- */
- public function can_uninstall_plugin($component) {
- $pluginfo = $this->get_plugin_info($component);
- if (is_null($pluginfo)) {
- return false;
- }
- if (!$this->common_uninstall_check($pluginfo)) {
- return false;
- }
- // Verify only if something else requires the subplugins, do not verify their common_uninstall_check()!
- $subplugins = $this->get_subplugins_of_plugin($pluginfo->component);
- foreach ($subplugins as $subpluginfo) {
- // Check if there are some other plugins requiring this subplugin
- // (but the parent and siblings).
- foreach ($this->other_plugins_that_require($subpluginfo->component) as $requiresme) {
- $ismyparent = ($pluginfo->component === $requiresme);
- $ismysibling = in_array($requiresme, array_keys($subplugins));
- if (!$ismyparent and !$ismysibling) {
- return false;
- }
- }
- }
- // Check if there are some other plugins requiring this plugin
- // (but its subplugins).
- foreach ($this->other_plugins_that_require($pluginfo->component) as $requiresme) {
- $ismysubplugin = in_array($requiresme, array_keys($subplugins));
- if (!$ismysubplugin) {
- return false;
- }
- }
- return true;
- }
- /**
- * Perform the installation of plugins.
- *
- * If used for installation of remote plugins from the Moodle Plugins
- * directory, the $plugins must be list of {@link \core\update\remote_info}
- * object that represent installable remote plugins. The caller can use
- * {@link self::filter_installable()} to prepare the list.
- *
- * If used for installation of plugins from locally available ZIP files,
- * the $plugins should be list of objects with properties ->component and
- * ->zipfilepath.
- *
- * The method uses {@link mtrace()} to produce direct output and can be
- * used in both web and cli interfaces.
- *
- * @param array $plugins list of plugins
- * @param bool $confirmed should the files be really deployed into the dirroot?
- * @param bool $silent perform without output
- * @return bool true on success
- */
- public function install_plugins(array $plugins, $confirmed, $silent) {
- global $CFG, $OUTPUT;
- if (!empty($CFG->disableupdateautodeploy)) {
- return false;
- }
- if (empty($plugins)) {
- return false;
- }
- $ok = get_string('ok', 'core');
- // Let admins know they can expect more verbose output.
- $silent or $this->mtrace(get_string('packagesdebug', 'core_plugin'), PHP_EOL, DEBUG_NORMAL);
- // Download all ZIP packages if we do not have them yet.
- $zips = array();
- foreach ($plugins as $plugin) {
- if ($plugin instanceof \core\update\remote_info) {
- $zips[$plugin->component] = $this->get_remote_plugin_zip($plugin->version->downloadurl,
- $plugin->version->downloadmd5);
- $silent or $this->mtrace(get_string('packagesdownloading', 'core_plugin', $plugin->component), ' ... ');
- $silent or $this->mtrace(PHP_EOL.' <- '.$plugin->version->downloadurl, '', DEBUG_DEVELOPER);
- $silent or $this->mtrace(PHP_EOL.' -> '.$zips[$plugin->component], ' ... ', DEBUG_DEVELOPER);
- if (!$zips[$plugin->component]) {
- $silent or $this->mtrace(get_string('error'));
- return false;
- }
- $silent or $this->mtrace($ok);
- } else {
- if (empty($plugin->zipfilepath)) {
- throw new coding_exception('Unexpected data structure provided');
- }
- $zips[$plugin->component] = $plugin->zipfilepath;
- $silent or $this->m…
Large files files are truncated, but you can click here to view the full file