2013-10-04 22:40:44 +02:00
|
|
|
<?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';
|
|
|
|
|
2015-09-26 09:21:04 +02:00
|
|
|
/** 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';
|
|
|
|
|
2015-10-01 15:56:26 +02:00
|
|
|
/** 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';
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
/** @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;
|
2015-10-01 15:56:26 +02:00
|
|
|
/** @var array cache information about availability in the plugins directory */
|
|
|
|
protected $remotepluginsinfo = null;
|
2013-10-04 22:40:44 +02:00
|
|
|
/** @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;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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() {
|
2015-09-24 20:53:04 +02:00
|
|
|
if (is_null(static::$singletoninstance)) {
|
|
|
|
static::$singletoninstance = new static();
|
2013-10-04 22:40:44 +02:00
|
|
|
}
|
2015-09-24 20:53:04 +02:00
|
|
|
return static::$singletoninstance;
|
2013-10-04 22:40:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset all caches.
|
|
|
|
* @param bool $phpunitreset
|
|
|
|
*/
|
|
|
|
public static function reset_caches($phpunitreset = false) {
|
|
|
|
if ($phpunitreset) {
|
2015-09-24 20:53:04 +02:00
|
|
|
static::$singletoninstance = null;
|
2013-10-04 22:40:44 +02:00
|
|
|
} else {
|
2015-09-24 20:53:04 +02:00
|
|
|
if (static::$singletoninstance) {
|
|
|
|
static::$singletoninstance->pluginsinfo = null;
|
|
|
|
static::$singletoninstance->subpluginsinfo = null;
|
|
|
|
static::$singletoninstance->installedplugins = null;
|
|
|
|
static::$singletoninstance->enabledplugins = null;
|
|
|
|
static::$singletoninstance->presentplugins = null;
|
|
|
|
static::$singletoninstance->plugintypes = null;
|
2013-10-04 22:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$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 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();
|
|
|
|
|
2014-01-29 01:40:30 +01:00
|
|
|
// TODO: Delete this block once Moodle 2.6 or later becomes minimum required version to upgrade.
|
2013-10-04 22:40:44 +02:00
|
|
|
if ($CFG->version < 2013092001.02) {
|
|
|
|
// We did not upgrade the database yet.
|
|
|
|
$modules = $DB->get_records('modules', array(), 'name ASC', 'id, name, version');
|
|
|
|
foreach ($modules as $module) {
|
|
|
|
$this->installedplugins['mod'][$module->name] = $module->version;
|
|
|
|
}
|
|
|
|
$blocks = $DB->get_records('block', array(), 'name ASC', 'id, name, version');
|
|
|
|
foreach ($blocks as $block) {
|
|
|
|
$this->installedplugins['block'][$block->name] = $block->version;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$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) {
|
2015-09-24 20:53:04 +02:00
|
|
|
$plugininfoclass = static::resolve_plugininfo_class($plugintype);
|
2013-10-04 22:40:44 +02:00
|
|
|
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) {
|
2015-08-06 12:33:56 +02:00
|
|
|
$module = new stdClass();
|
2013-10-04 22:40:44 +02:00
|
|
|
$plugin = new stdClass();
|
|
|
|
$plugin->version = null;
|
2015-03-25 08:35:42 +00:00
|
|
|
include($fullplug.'/version.php');
|
2015-08-06 12:33:56 +02:00
|
|
|
|
|
|
|
// Check if the legacy $module syntax is still used.
|
2015-09-02 01:20:09 +02:00
|
|
|
if (!is_object($module) or (count((array)$module) > 0)) {
|
2015-08-06 12:33:56 +02:00
|
|
|
debugging('Unsupported $module syntax detected in version.php of the '.$type.'_'.$plug.' plugin.');
|
|
|
|
$skipcache = true;
|
|
|
|
}
|
|
|
|
|
2015-08-06 14:19:58 +02:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
$this->presentplugins[$type][$plug] = $plugin;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-06 12:33:56 +02:00
|
|
|
if (empty($skipcache)) {
|
|
|
|
$cache->set('present', $this->presentplugins);
|
|
|
|
}
|
2013-10-04 22:40:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
|
2013-10-24 19:13:18 +02:00
|
|
|
if (!isset($types[$type])) {
|
|
|
|
// Orphaned subplugins!
|
2015-09-24 20:53:04 +02:00
|
|
|
$plugintypeclass = static::resolve_plugininfo_class($type);
|
2015-09-24 22:34:31 +02:00
|
|
|
$this->pluginsinfo[$type] = $plugintypeclass::get_plugins($type, null, $plugintypeclass, $this);
|
2013-10-24 19:13:18 +02:00
|
|
|
return $this->pluginsinfo[$type];
|
|
|
|
}
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
/** @var \core\plugininfo\base $plugintypeclass */
|
2015-09-24 20:53:04 +02:00
|
|
|
$plugintypeclass = static::resolve_plugininfo_class($type);
|
2015-09-24 22:34:31 +02:00
|
|
|
$plugins = $plugintypeclass::get_plugins($type, $types[$type], $plugintypeclass, $this);
|
2013-10-04 22:40:44 +02:00
|
|
|
$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;
|
|
|
|
}
|
2013-10-24 19:13:18 +02:00
|
|
|
|
|
|
|
// Add orphaned subplugin types.
|
|
|
|
$this->load_installed_plugins();
|
|
|
|
foreach ($this->installedplugins as $plugintype => $unused) {
|
|
|
|
if (!isset($plugintypes[$plugintype])) {
|
|
|
|
$this->pluginsinfo[$plugintype] = null;
|
|
|
|
}
|
|
|
|
}
|
2013-10-04 22:40:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2013-10-24 19:13:18 +02:00
|
|
|
$plugintypes = core_component::get_plugin_types();
|
|
|
|
if (!isset($plugintypes[$type])) {
|
|
|
|
return '\core\plugininfo\orphaned';
|
|
|
|
}
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
$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.php file.
|
|
|
|
*
|
|
|
|
* @return array with keys like 'mod_quiz', and values the data from the
|
|
|
|
* corresponding db/subplugins.php 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);
|
2013-10-24 20:10:56 +02:00
|
|
|
$plugins = $this->get_plugins_of_type($type);
|
|
|
|
if (isset($plugins[$name])) {
|
|
|
|
return $plugins[$name];
|
2013-10-04 22:40:44 +02:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check to see if the current version of the plugin seems to be a checkout of an external repository.
|
|
|
|
*
|
|
|
|
* @see \core\update\deployer::plugin_external_source()
|
|
|
|
* @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';
|
|
|
|
}
|
|
|
|
|
2014-09-25 21:17:53 +02:00
|
|
|
if (is_file($pluginroot.'/.git')) {
|
|
|
|
return 'git-submodule';
|
|
|
|
}
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
if (is_dir($pluginroot.'/CVS')) {
|
|
|
|
return 'cvs';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_dir($pluginroot.'/.svn')) {
|
|
|
|
return 'svn';
|
|
|
|
}
|
|
|
|
|
2014-01-25 02:41:01 +03:00
|
|
|
if (is_dir($pluginroot.'/.hg')) {
|
|
|
|
return 'mercurial';
|
|
|
|
}
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
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
|
|
|
|
* @return bool true if all the dependencies are satisfied for all plugins.
|
|
|
|
*/
|
|
|
|
public function all_plugins_ok($moodleversion, &$failedplugins = array()) {
|
|
|
|
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $return;
|
|
|
|
}
|
|
|
|
|
2015-09-26 09:21:04 +02:00
|
|
|
/**
|
|
|
|
* 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
|
2015-10-01 15:56:26 +02:00
|
|
|
* ->(string)availability
|
2015-09-26 09:21:04 +02:00
|
|
|
*
|
|
|
|
* @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 ($moodleversion === null) {
|
|
|
|
$moodleversion = $CFG->version;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($moodlebranch === null) {
|
|
|
|
$moodlebranch = $CFG->branch;
|
|
|
|
}
|
|
|
|
|
|
|
|
$reqs = array();
|
|
|
|
$reqcore = $this->resolve_core_requirements($plugin, $moodleversion);
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
2015-10-01 15:56:26 +02:00
|
|
|
$reqs = (object)array(
|
|
|
|
'hasver' => null,
|
|
|
|
'reqver' => null,
|
|
|
|
'status' => null,
|
|
|
|
'availability' => null,
|
|
|
|
);
|
2015-09-26 09:21:04 +02:00
|
|
|
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
2015-10-01 15:56:26 +02:00
|
|
|
$reqs = (object)array(
|
|
|
|
'hasver' => null,
|
|
|
|
'reqver' => null,
|
|
|
|
'status' => null,
|
|
|
|
'availability' => null,
|
|
|
|
);
|
|
|
|
|
2015-09-26 09:21:04 +02:00
|
|
|
$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;
|
2015-10-01 15:56:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($reqs->status !== self::REQUIREMENT_STATUS_OK) {
|
|
|
|
if ($this->is_remote_plugin_available($otherpluginname, $requiredversion)) {
|
|
|
|
$reqs->availability = self::REQUIREMENT_AVAILABLE;
|
|
|
|
} else {
|
|
|
|
$reqs->availability = self::REQUIREMENT_UNAVAILABLE;
|
|
|
|
}
|
2015-09-26 09:21:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $reqs;
|
|
|
|
}
|
|
|
|
|
2015-10-01 15:56:26 +02:00
|
|
|
/**
|
|
|
|
* Is the given plugin version available in the plugins directory?
|
|
|
|
*
|
|
|
|
* @param string $component
|
|
|
|
* @param string|int $requiredversion ANY_VERSION or the version number
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function is_remote_plugin_available($component, $requiredversion) {
|
|
|
|
|
|
|
|
$info = $this->get_remote_plugin_info($component, $requiredversion);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns information about a plugin in the plugins directory.
|
|
|
|
*
|
|
|
|
* See {@link \core\update\api::find_plugin()} for more details.
|
|
|
|
*
|
|
|
|
* @param string $component
|
|
|
|
* @param string|int $requiredversion ANY_VERSION or the version number
|
|
|
|
* @return stdClass|bool false or data object
|
|
|
|
*/
|
|
|
|
public function get_remote_plugin_info($component, $requiredversion) {
|
|
|
|
|
|
|
|
if (!isset($this->remotepluginsinfo[$component][$requiredversion])) {
|
|
|
|
$client = \core\update\api::client();
|
|
|
|
$this->remotepluginsinfo[$component][$requiredversion] = $client->find_plugin($component, $requiredversion);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->remotepluginsinfo[$component][$requiredversion];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a list of all missing dependencies.
|
|
|
|
*
|
|
|
|
* This should provide the full list of plugins that should be installed to
|
|
|
|
* fulfill the requirements of all plugins, if possible.
|
|
|
|
*
|
|
|
|
* @return array of stdClass|bool indexed by the component name
|
|
|
|
*/
|
|
|
|
public function missing_dependencies() {
|
|
|
|
|
|
|
|
$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);
|
|
|
|
|
|
|
|
if (empty($dependencies[$reqname])) {
|
|
|
|
$dependencies[$reqname] = $remoteinfo;
|
|
|
|
} else {
|
|
|
|
// If two local plugins depend on the two different
|
|
|
|
// versions of the same remote plugin, pick the
|
|
|
|
// higher version.
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $dependencies;
|
|
|
|
}
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns uninstall URL if exists.
|
|
|
|
*
|
|
|
|
* @param string $component
|
|
|
|
* @param string $return either 'overview' or 'manage'
|
|
|
|
* @return moodle_url uninstall URL, null if uninstall not supported
|
|
|
|
*/
|
|
|
|
public function get_uninstall_url($component, $return = 'overview') {
|
|
|
|
if (!$this->can_uninstall_plugin($component)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$pluginfo = $this->get_plugin_info($component);
|
|
|
|
|
|
|
|
if (is_null($pluginfo)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (method_exists($pluginfo, 'get_uninstall_url')) {
|
|
|
|
debugging('plugininfo method get_uninstall_url() is deprecated, all plugins should be uninstalled via standard URL only.');
|
|
|
|
return $pluginfo->get_uninstall_url($return);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $pluginfo->get_default_uninstall_url($return);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uninstall the given plugin.
|
|
|
|
*
|
|
|
|
* Automatically cleans-up all remaining configuration data, log records, events,
|
|
|
|
* files from the file pool etc.
|
|
|
|
*
|
|
|
|
* In the future, the functionality of {@link uninstall_plugin()} function may be moved
|
|
|
|
* into this method and all the code should be refactored to use it. At the moment, we
|
|
|
|
* mimic this future behaviour by wrapping that function call.
|
|
|
|
*
|
|
|
|
* @param string $component
|
|
|
|
* @param progress_trace $progress traces the process
|
|
|
|
* @return bool true on success, false on errors/problems
|
|
|
|
*/
|
|
|
|
public function uninstall_plugin($component, progress_trace $progress) {
|
|
|
|
|
|
|
|
$pluginfo = $this->get_plugin_info($component);
|
|
|
|
|
|
|
|
if (is_null($pluginfo)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Give the pluginfo class a chance to execute some steps.
|
|
|
|
$result = $pluginfo->uninstall($progress);
|
|
|
|
if (!$result) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the legacy core function to uninstall the plugin.
|
|
|
|
ob_start();
|
|
|
|
uninstall_plugin($pluginfo->type, $pluginfo->name);
|
|
|
|
$progress->output(ob_get_clean());
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if there are some plugins with a known available update
|
|
|
|
*
|
|
|
|
* @return bool true if there is at least one available update
|
|
|
|
*/
|
|
|
|
public function some_plugins_updatable() {
|
|
|
|
foreach ($this->get_plugins() as $type => $plugins) {
|
|
|
|
foreach ($plugins as $plugin) {
|
|
|
|
if ($plugin->available_updates()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-25 00:54:53 +02:00
|
|
|
/**
|
|
|
|
* Returns list of available updates for the given component.
|
|
|
|
*
|
|
|
|
* This method should be considered as internal API and is supposed to be
|
|
|
|
* called by {@link \core\plugininfo\base::available_updates()} only
|
|
|
|
* to lazy load the data once they are first requested.
|
|
|
|
*
|
|
|
|
* @param string $component frankenstyle name of the plugin
|
|
|
|
* @return null|array array of \core\update\info objects or null
|
|
|
|
*/
|
|
|
|
public function load_available_updates_for_plugin($component) {
|
|
|
|
global $CFG;
|
|
|
|
|
|
|
|
$provider = \core\update\checker::instance();
|
|
|
|
|
|
|
|
if (!$provider->enabled() or during_initial_install()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($CFG->updateminmaturity)) {
|
|
|
|
$minmaturity = $CFG->updateminmaturity;
|
|
|
|
} else {
|
|
|
|
// This can happen during the very first upgrade to 2.3.
|
|
|
|
$minmaturity = MATURITY_STABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $provider->get_update_info($component, array('minmaturity' => $minmaturity));
|
|
|
|
}
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
/**
|
|
|
|
* Check to see if the given plugin folder can be removed by the web server process.
|
|
|
|
*
|
|
|
|
* @param string $component full frankenstyle component
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function is_plugin_folder_removable($component) {
|
|
|
|
|
|
|
|
$pluginfo = $this->get_plugin_info($component);
|
|
|
|
|
|
|
|
if (is_null($pluginfo)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// To be able to remove the plugin folder, its parent must be writable, too.
|
|
|
|
if (!is_writable(dirname($pluginfo->rootdir))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the folder and all its content is writable (thence removable).
|
|
|
|
return $this->is_directory_removable($pluginfo->rootdir);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
|
|
|
|
* but are not anymore and are deleted during upgrades.
|
|
|
|
*
|
|
|
|
* The main purpose of this list is to hide missing plugins during upgrade.
|
|
|
|
*
|
|
|
|
* @param string $type plugin type
|
|
|
|
* @param string $name plugin name
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function is_deleted_standard_plugin($type, $name) {
|
|
|
|
// Do not include plugins that were removed during upgrades to versions that are
|
|
|
|
// not supported as source versions for upgrade any more. For example, at MOODLE_23_STABLE
|
|
|
|
// branch, listed should be no plugins that were removed at 1.9.x - 2.1.x versions as
|
|
|
|
// Moodle 2.3 supports upgrades from 2.2.x only.
|
|
|
|
$plugins = array(
|
2014-10-01 14:28:42 +01:00
|
|
|
'qformat' => array('blackboard', 'learnwise'),
|
2013-10-04 22:40:44 +02:00
|
|
|
'enrol' => array('authorize'),
|
2014-05-01 14:00:15 +08:00
|
|
|
'tinymce' => array('dragmath'),
|
2015-03-28 18:46:16 +13:00
|
|
|
'tool' => array('bloglevelupgrade', 'qeupgradehelper', 'timezoneimport'),
|
2014-03-19 10:57:45 +08:00
|
|
|
'theme' => array('mymobile', 'afterburner', 'anomaly', 'arialist', 'binarius', 'boxxie', 'brick', 'formal_white',
|
|
|
|
'formfactor', 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero', 'overlay', 'serenity', 'sky_high',
|
|
|
|
'splash', 'standard', 'standardold'),
|
2013-10-04 22:40:44 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (!isset($plugins[$type])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return in_array($name, $plugins[$type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Defines a white list of all plugins shipped in the standard Moodle distribution
|
|
|
|
*
|
|
|
|
* @param string $type
|
|
|
|
* @return false|array array of standard plugins or false if the type is unknown
|
|
|
|
*/
|
|
|
|
public static function standard_plugins_list($type) {
|
|
|
|
|
|
|
|
$standard_plugins = array(
|
|
|
|
|
2014-01-28 13:43:59 +08:00
|
|
|
'atto' => array(
|
2014-03-25 18:03:54 +08:00
|
|
|
'accessibilitychecker', 'accessibilityhelper', 'align',
|
|
|
|
'backcolor', 'bold', 'charmap', 'clear', 'collapse', 'emoticon',
|
|
|
|
'equation', 'fontcolor', 'html', 'image', 'indent', 'italic',
|
|
|
|
'link', 'managefiles', 'media', 'noautolink', 'orderedlist',
|
|
|
|
'rtl', 'strike', 'subscript', 'superscript', 'table', 'title',
|
2014-03-25 13:21:49 +08:00
|
|
|
'underline', 'undo', 'unorderedlist'
|
2014-01-28 13:43:59 +08:00
|
|
|
),
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
'assignment' => array(
|
|
|
|
'offline', 'online', 'upload', 'uploadsingle'
|
|
|
|
),
|
|
|
|
|
|
|
|
'assignsubmission' => array(
|
|
|
|
'comments', 'file', 'onlinetext'
|
|
|
|
),
|
|
|
|
|
|
|
|
'assignfeedback' => array(
|
|
|
|
'comments', 'file', 'offline', 'editpdf'
|
|
|
|
),
|
|
|
|
|
|
|
|
'auth' => array(
|
|
|
|
'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
|
|
|
|
'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
|
|
|
|
'shibboleth', 'webservice'
|
|
|
|
),
|
|
|
|
|
MDL-44070 Conditional availability enhancements (2): subsystem, API
This commit defines the new /availability root folder, with
/availability/classes, /availability/tests, and
/availability/condition where the condition plugins will live.
Condition plugin prefix is availability_, e.g. availability_date.
Rationale for this organisation:
1. I was originally going to put this in /lib/availability but
it has been pointed out that putting even more junk in lib
is probably bad.
2. 'availability' and 'condition' are the two names used in code
to refer to this system ($CFG->enableavailability).
3. The prefix has to be short enough to allow database tables
(although in practice I assume that condition plugins will not
normally contain database tables).
The new API includes a Boolean tree structure that controls the
availability of an item.
AMOS BEGIN
CPY [availabilityconditions,core_condition],[restrictaccess,core_availability]
CPY [enableavailability,core_condition],[enableavailability,core_availability]
CPY [configenableavailability,core_condition],[enableavailability_desc,core_availability]
AMOS END
2014-03-26 12:02:30 +00:00
|
|
|
'availability' => array(
|
|
|
|
'completion', 'date', 'grade', 'group', 'grouping', 'profile'
|
|
|
|
),
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
'block' => array(
|
MDL-48969 Block: Implement a high scores block
AMOS BEGIN
CPY [bestgrade,block_quiz_results],[bestgrade,block_activity_results]
CPY [bestgrades,block_quiz_results],[bestgrades,block_activity_results]
CPY
[bestgroupgrade,block_quiz_results],[bestgroupgrade,block_activity_resul
ts]
CPY
[bestgroupgrades,block_quiz_results],[bestgroupgrades,block_activity_res
ults]
CPY
[config_format_absolute,block_quiz_results],[config_format_absolute,bloc
k_activity_results]
CPY
[config_format_fraction,block_quiz_results],[config_format_fraction,bloc
k_activity_results]
CPY
[config_format_percentage,block_quiz_results],[config_format_percentage,
block_activity_results]
CPY
[config_grade_format,block_quiz_results],[config_grade_format,block_acti
vity_results]
CPY
[config_name_format,block_quiz_results],[config_name_format,block_activi
ty_results]
CPY
[config_names_anon,block_quiz_results],[config_names_anon,block_activity
_results]
CPY
[config_names_full,block_quiz_results],[config_names_full,block_activity
_results]
CPY
[config_names_id,block_quiz_results],[config_names_id,block_activity_res
ults]
CPY
[config_show_best,block_quiz_results],[config_show_best,block_activity_r
esults]
CPY
[config_show_worst,block_quiz_results],[config_show_worst,block_activity
_results]
CPY
[configuredtoshownothing,block_quiz_results],[configuredtoshownothing,bl
ock_activity_results]
CPY
[config_use_groups,block_quiz_results],[config_use_groups,block_activity
_results]
CPY
[error_nogroupsexist,block_quiz_results],[error_nogroupsexist,block_acti
vity_results]
CPY [worstgrade,block_quiz_results],[worstgrade,block_activity_results]
CPY
[worstgrades,block_quiz_results],[worstgrades,block_activity_results]
CPY
[worstgroupgrade,block_quiz_results],[worstgroupgrade,block_activity_res
ults]
CPY
[worstgroupgrades,block_quiz_results],[worstgroupgrades,block_activity_r
esults]
AMOS END
2015-03-19 19:46:02 -04:00
|
|
|
'activity_modules', 'activity_results', 'admin_bookmarks', 'badges',
|
|
|
|
'blog_menu', 'blog_recent', 'blog_tags', 'calendar_month',
|
2013-10-04 22:40:44 +02:00
|
|
|
'calendar_upcoming', 'comments', 'community',
|
|
|
|
'completionstatus', 'course_list', 'course_overview',
|
|
|
|
'course_summary', 'feedback', 'glossary_random', 'html',
|
|
|
|
'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
|
|
|
|
'navigation', 'news_items', 'online_users', 'participants',
|
|
|
|
'private_files', 'quiz_results', 'recent_activity',
|
|
|
|
'rss_client', 'search_forums', 'section_links',
|
|
|
|
'selfcompletion', 'settings', 'site_main_menu',
|
|
|
|
'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
|
|
|
|
),
|
|
|
|
|
|
|
|
'booktool' => array(
|
|
|
|
'exportimscp', 'importhtml', 'print'
|
|
|
|
),
|
|
|
|
|
|
|
|
'cachelock' => array(
|
|
|
|
'file'
|
|
|
|
),
|
|
|
|
|
|
|
|
'cachestore' => array(
|
|
|
|
'file', 'memcache', 'memcached', 'mongodb', 'session', 'static'
|
|
|
|
),
|
|
|
|
|
|
|
|
'calendartype' => array(
|
|
|
|
'gregorian'
|
|
|
|
),
|
|
|
|
|
|
|
|
'coursereport' => array(
|
|
|
|
// Deprecated!
|
|
|
|
),
|
|
|
|
|
|
|
|
'datafield' => array(
|
|
|
|
'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
|
|
|
|
'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
|
|
|
|
),
|
|
|
|
|
|
|
|
'datapreset' => array(
|
|
|
|
'imagegallery'
|
|
|
|
),
|
|
|
|
|
|
|
|
'editor' => array(
|
2014-03-25 18:03:54 +08:00
|
|
|
'atto', 'textarea', 'tinymce'
|
2013-10-04 22:40:44 +02:00
|
|
|
),
|
|
|
|
|
|
|
|
'enrol' => array(
|
|
|
|
'category', 'cohort', 'database', 'flatfile',
|
|
|
|
'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
|
|
|
|
'paypal', 'self'
|
|
|
|
),
|
|
|
|
|
|
|
|
'filter' => array(
|
|
|
|
'activitynames', 'algebra', 'censor', 'emailprotect',
|
2014-03-25 16:42:07 +08:00
|
|
|
'emoticon', 'mathjaxloader', 'mediaplugin', 'multilang', 'tex', 'tidy',
|
2013-10-04 22:40:44 +02:00
|
|
|
'urltolink', 'data', 'glossary'
|
|
|
|
),
|
|
|
|
|
|
|
|
'format' => array(
|
|
|
|
'singleactivity', 'social', 'topics', 'weeks'
|
|
|
|
),
|
|
|
|
|
|
|
|
'gradeexport' => array(
|
|
|
|
'ods', 'txt', 'xls', 'xml'
|
|
|
|
),
|
|
|
|
|
|
|
|
'gradeimport' => array(
|
2014-09-19 10:02:03 +08:00
|
|
|
'csv', 'direct', 'xml'
|
2013-10-04 22:40:44 +02:00
|
|
|
),
|
|
|
|
|
|
|
|
'gradereport' => array(
|
2014-10-03 09:57:55 +08:00
|
|
|
'grader', 'history', 'outcomes', 'overview', 'user', 'singleview'
|
2013-10-04 22:40:44 +02:00
|
|
|
),
|
|
|
|
|
|
|
|
'gradingform' => array(
|
|
|
|
'rubric', 'guide'
|
|
|
|
),
|
|
|
|
|
|
|
|
'local' => array(
|
|
|
|
),
|
|
|
|
|
2014-01-13 09:08:58 +08:00
|
|
|
'logstore' => array(
|
|
|
|
'database', 'legacy', 'standard',
|
|
|
|
),
|
|
|
|
|
2014-09-03 23:35:39 +01:00
|
|
|
'ltiservice' => array(
|
2015-10-06 14:13:39 +02:00
|
|
|
'memberships', 'profile', 'toolproxy', 'toolsettings'
|
2014-09-03 23:35:39 +01:00
|
|
|
),
|
|
|
|
|
2013-10-04 22:40:44 +02:00
|
|
|
'message' => array(
|
2014-04-03 15:15:47 +02:00
|
|
|
'airnotifier', 'email', 'jabber', 'popup'
|
2013-10-04 22:40:44 +02:00
|
|
|
),
|
|
|
|
|
|
|
|
'mnetservice' => array(
|
|
|
|
'enrol'
|
|
|
|
),
|
|
|
|
|
|
|
|
'mod' => array(
|
|
|
|
'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
|
|
|
|
'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
|
|
|
|
'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
|
|
|
|
),
|
|
|
|
|
|
|
|
'plagiarism' => array(
|
|
|
|
),
|
|
|
|
|
|
|
|
'portfolio' => array(
|
|
|
|
'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
|
|
|
|
),
|
|
|
|
|
|
|
|
'profilefield' => array(
|
|
|
|
'checkbox', 'datetime', 'menu', 'text', 'textarea'
|
|
|
|
),
|
|
|
|
|
|
|
|
'qbehaviour' => array(
|
|
|
|
'adaptive', 'adaptivenopenalty', 'deferredcbm',
|
|
|
|
'deferredfeedback', 'immediatecbm', 'immediatefeedback',
|
|
|
|
'informationitem', 'interactive', 'interactivecountback',
|
|
|
|
'manualgraded', 'missing'
|
|
|
|
),
|
|
|
|
|
|
|
|
'qformat' => array(
|
|
|
|
'aiken', 'blackboard_six', 'examview', 'gift',
|
2014-10-01 14:28:42 +01:00
|
|
|
'missingword', 'multianswer', 'webct',
|
2013-10-04 22:40:44 +02:00
|
|
|
'xhtml', 'xml'
|
|
|
|
),
|
|
|
|
|
|
|
|
'qtype' => array(
|
|
|
|
'calculated', 'calculatedmulti', 'calculatedsimple',
|
2015-04-05 21:02:46 +01:00
|
|
|
'ddimageortext', 'ddmarker', 'ddwtos', 'description',
|
|
|
|
'essay', 'gapselect', 'match', 'missingtype', 'multianswer',
|
2013-10-04 22:40:44 +02:00
|
|
|
'multichoice', 'numerical', 'random', 'randomsamatch',
|
|
|
|
'shortanswer', 'truefalse'
|
|
|
|
),
|
|
|
|
|
|
|
|
'quiz' => array(
|
|
|
|
'grading', 'overview', 'responses', 'statistics'
|
|
|
|
),
|
|
|
|
|
|
|
|
'quizaccess' => array(
|
|
|
|
'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
|
|
|
|
'password', 'safebrowser', 'securewindow', 'timelimit'
|
|
|
|
),
|
|
|
|
|
|
|
|
'report' => array(
|
2014-04-03 09:22:29 +08:00
|
|
|
'backups', 'completion', 'configlog', 'courseoverview', 'eventlist',
|
2014-11-18 09:48:17 +13:00
|
|
|
'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats', 'performance',
|
|
|
|
'usersessions',
|
2013-10-04 22:40:44 +02:00
|
|
|
),
|
|
|
|
|
|
|
|
'repository' => array(
|
|
|
|
'alfresco', 'areafiles', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem',
|
|
|
|
'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
|
|
|
|
'picasa', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav',
|
|
|
|
'wikimedia', 'youtube'
|
|
|
|
),
|
|
|
|
|
|
|
|
'scormreport' => array(
|
|
|
|
'basic',
|
|
|
|
'interactions',
|
|
|
|
'graphs',
|
|
|
|
'objectives'
|
|
|
|
),
|
|
|
|
|
|
|
|
'tinymce' => array(
|
2014-05-01 14:00:15 +08:00
|
|
|
'ctrlhelp', 'managefiles', 'moodleemoticon', 'moodleimage',
|
2013-10-04 22:40:44 +02:00
|
|
|
'moodlemedia', 'moodlenolink', 'pdw', 'spellchecker', 'wrap'
|
|
|
|
),
|
|
|
|
|
|
|
|
'theme' => array(
|
2014-03-26 11:33:15 +13:00
|
|
|
'base', 'bootstrapbase', 'canvas', 'clean', 'more'
|
2013-10-04 22:40:44 +02:00
|
|
|
),
|
|
|
|
|
|
|
|
'tool' => array(
|
MDL-44070 Conditional availability enhancements (2): subsystem, API
This commit defines the new /availability root folder, with
/availability/classes, /availability/tests, and
/availability/condition where the condition plugins will live.
Condition plugin prefix is availability_, e.g. availability_date.
Rationale for this organisation:
1. I was originally going to put this in /lib/availability but
it has been pointed out that putting even more junk in lib
is probably bad.
2. 'availability' and 'condition' are the two names used in code
to refer to this system ($CFG->enableavailability).
3. The prefix has to be short enough to allow database tables
(although in practice I assume that condition plugins will not
normally contain database tables).
The new API includes a Boolean tree structure that controls the
availability of an item.
AMOS BEGIN
CPY [availabilityconditions,core_condition],[restrictaccess,core_availability]
CPY [enableavailability,core_condition],[enableavailability,core_availability]
CPY [configenableavailability,core_condition],[enableavailability_desc,core_availability]
AMOS END
2014-03-26 12:02:30 +00:00
|
|
|
'assignmentupgrade', 'availabilityconditions', 'behat', 'capability', 'customlang',
|
2014-12-23 12:04:02 +08:00
|
|
|
'dbtransfer', 'filetypes', 'generator', 'health', 'innodb', 'installaddon',
|
2014-06-19 15:16:45 +08:00
|
|
|
'langimport', 'log', 'messageinbound', 'multilangupgrade', 'monitor', 'phpunit', 'profiling',
|
2015-03-25 00:05:06 +08:00
|
|
|
'replace', 'spamcleaner', 'task', 'templatelibrary',
|
2013-10-04 22:40:44 +02:00
|
|
|
'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'xmldb'
|
|
|
|
),
|
|
|
|
|
|
|
|
'webservice' => array(
|
|
|
|
'amf', 'rest', 'soap', 'xmlrpc'
|
|
|
|
),
|
|
|
|
|
|
|
|
'workshopallocation' => array(
|
|
|
|
'manual', 'random', 'scheduled'
|
|
|
|
),
|
|
|
|
|
|
|
|
'workshopeval' => array(
|
|
|
|
'best'
|
|
|
|
),
|
|
|
|
|
|
|
|
'workshopform' => array(
|
|
|
|
'accumulative', 'comments', 'numerrors', 'rubric'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (isset($standard_plugins[$type])) {
|
|
|
|
return $standard_plugins[$type];
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reorders plugin types into a sequence to be displayed
|
|
|
|
*
|
|
|
|
* For technical reasons, plugin types returned by {@link core_component::get_plugin_types()} are
|
|
|
|
* in a certain order that does not need to fit the expected order for the display.
|
|
|
|
* Particularly, activity modules should be displayed first as they represent the
|
|
|
|
* real heart of Moodle. They should be followed by other plugin types that are
|
|
|
|
* used to build the courses (as that is what one expects from LMS). After that,
|
|
|
|
* other supportive plugin types follow.
|
|
|
|
*
|
|
|
|
* @param array $types associative array
|
|
|
|
* @return array same array with altered order of items
|
|
|
|
*/
|
|
|
|
protected function reorder_plugin_types(array $types) {
|
|
|
|
$fix = array('mod' => $types['mod']);
|
|
|
|
foreach (core_component::get_plugin_list('mod') as $plugin => $fulldir) {
|
|
|
|
if (!$subtypes = core_component::get_subplugins('mod_'.$plugin)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
foreach ($subtypes as $subtype => $ignored) {
|
|
|
|
$fix[$subtype] = $types[$subtype];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$fix['mod'] = $types['mod'];
|
|
|
|
$fix['block'] = $types['block'];
|
|
|
|
$fix['qtype'] = $types['qtype'];
|
|
|
|
$fix['qbehaviour'] = $types['qbehaviour'];
|
|
|
|
$fix['qformat'] = $types['qformat'];
|
|
|
|
$fix['filter'] = $types['filter'];
|
|
|
|
|
|
|
|
$fix['editor'] = $types['editor'];
|
|
|
|
foreach (core_component::get_plugin_list('editor') as $plugin => $fulldir) {
|
|
|
|
if (!$subtypes = core_component::get_subplugins('editor_'.$plugin)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
foreach ($subtypes as $subtype => $ignored) {
|
|
|
|
$fix[$subtype] = $types[$subtype];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$fix['enrol'] = $types['enrol'];
|
|
|
|
$fix['auth'] = $types['auth'];
|
|
|
|
$fix['tool'] = $types['tool'];
|
|
|
|
foreach (core_component::get_plugin_list('tool') as $plugin => $fulldir) {
|
|
|
|
if (!$subtypes = core_component::get_subplugins('tool_'.$plugin)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
foreach ($subtypes as $subtype => $ignored) {
|
|
|
|
$fix[$subtype] = $types[$subtype];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($types as $type => $path) {
|
|
|
|
if (!isset($fix[$type])) {
|
|
|
|
$fix[$type] = $path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $fix;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the given directory can be removed by the web server process.
|
|
|
|
*
|
|
|
|
* This recursively checks that the given directory and all its contents
|
|
|
|
* it writable.
|
|
|
|
*
|
|
|
|
* @param string $fullpath
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
protected function is_directory_removable($fullpath) {
|
|
|
|
|
|
|
|
if (!is_writable($fullpath)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_dir($fullpath)) {
|
|
|
|
$handle = opendir($fullpath);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = true;
|
|
|
|
|
|
|
|
while ($filename = readdir($handle)) {
|
|
|
|
|
|
|
|
if ($filename === '.' or $filename === '..') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$subfilepath = $fullpath.'/'.$filename;
|
|
|
|
|
|
|
|
if (is_dir($subfilepath)) {
|
|
|
|
$result = $result && $this->is_directory_removable($subfilepath);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
$result = $result && is_writable($subfilepath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir($handle);
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method that implements common uninstall prerequisites
|
|
|
|
*
|
|
|
|
* @param \core\plugininfo\base $pluginfo
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected function common_uninstall_check(\core\plugininfo\base $pluginfo) {
|
|
|
|
|
|
|
|
if (!$pluginfo->is_uninstall_allowed()) {
|
|
|
|
// The plugin's plugininfo class declares it should not be uninstalled.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-24 20:53:04 +02:00
|
|
|
if ($pluginfo->get_status() === static::PLUGIN_STATUS_NEW) {
|
2013-10-04 22:40:44 +02:00
|
|
|
// The plugin is not installed. It should be either installed or removed from the disk.
|
|
|
|
// Relying on this temporary state may be tricky.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (method_exists($pluginfo, 'get_uninstall_url') and is_null($pluginfo->get_uninstall_url())) {
|
|
|
|
// Backwards compatibility.
|
|
|
|
debugging('\core\plugininfo\base subclasses should use is_uninstall_allowed() instead of returning null in get_uninstall_url()',
|
|
|
|
DEBUG_DEVELOPER);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|