| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 3 | //
 | 
        
           |  |  | 4 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 5 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 6 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 7 | // (at your option) any later version.
 | 
        
           |  |  | 8 | //
 | 
        
           |  |  | 9 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 12 | // GNU General Public License for more details.
 | 
        
           |  |  | 13 | //
 | 
        
           |  |  | 14 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 15 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | namespace core;
 | 
        
           |  |  | 18 |   | 
        
           |  |  | 19 | use cache;
 | 
        
           |  |  | 20 | use coding_exception;
 | 
        
           |  |  | 21 | use core_component;
 | 
        
           |  |  | 22 | use moodle_exception;
 | 
        
           |  |  | 23 | use moodle_url;
 | 
        
           |  |  | 24 | use progress_trace;
 | 
        
           |  |  | 25 | use stdClass;
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | /**
 | 
        
           |  |  | 28 |  * Defines classes used for plugins management
 | 
        
           |  |  | 29 |  *
 | 
        
           |  |  | 30 |  * This library provides a unified interface to various plugin types in
 | 
        
           |  |  | 31 |  * Moodle. It is mainly used by the plugins management admin page and the
 | 
        
           |  |  | 32 |  * plugins check page during the upgrade.
 | 
        
           |  |  | 33 |  *
 | 
        
           |  |  | 34 |  * @package    core
 | 
        
           |  |  | 35 |  * @copyright  2011 David Mudrak <david@moodle.com>
 | 
        
           |  |  | 36 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 37 |  */
 | 
        
           |  |  | 38 | class plugin_manager {
 | 
        
           |  |  | 39 |     /** the plugin is shipped with standard Moodle distribution */
 | 
        
           |  |  | 40 |     const PLUGIN_SOURCE_STANDARD    = 'std';
 | 
        
           |  |  | 41 |     /** the plugin is added extension */
 | 
        
           |  |  | 42 |     const PLUGIN_SOURCE_EXTENSION   = 'ext';
 | 
        
           |  |  | 43 |   | 
        
           |  |  | 44 |     /** the plugin uses neither database nor capabilities, no versions */
 | 
        
           |  |  | 45 |     const PLUGIN_STATUS_NODB        = 'nodb';
 | 
        
           |  |  | 46 |     /** the plugin is up-to-date */
 | 
        
           |  |  | 47 |     const PLUGIN_STATUS_UPTODATE    = 'uptodate';
 | 
        
           |  |  | 48 |     /** the plugin is about to be installed */
 | 
        
           |  |  | 49 |     const PLUGIN_STATUS_NEW         = 'new';
 | 
        
           |  |  | 50 |     /** the plugin is about to be upgraded */
 | 
        
           |  |  | 51 |     const PLUGIN_STATUS_UPGRADE     = 'upgrade';
 | 
        
           |  |  | 52 |     /** the standard plugin is about to be deleted */
 | 
        
           |  |  | 53 |     const PLUGIN_STATUS_DELETE     = 'delete';
 | 
        
           |  |  | 54 |     /** the version at the disk is lower than the one already installed */
 | 
        
           |  |  | 55 |     const PLUGIN_STATUS_DOWNGRADE   = 'downgrade';
 | 
        
           |  |  | 56 |     /** the plugin is installed but missing from disk */
 | 
        
           |  |  | 57 |     const PLUGIN_STATUS_MISSING     = 'missing';
 | 
        
           |  |  | 58 |   | 
        
           |  |  | 59 |     /** the given requirement/dependency is fulfilled */
 | 
        
           |  |  | 60 |     const REQUIREMENT_STATUS_OK = 'ok';
 | 
        
           |  |  | 61 |     /** the plugin requires higher core/other plugin version than is currently installed */
 | 
        
           |  |  | 62 |     const REQUIREMENT_STATUS_OUTDATED = 'outdated';
 | 
        
           |  |  | 63 |     /** the required dependency is not installed */
 | 
        
           |  |  | 64 |     const REQUIREMENT_STATUS_MISSING = 'missing';
 | 
        
           |  |  | 65 |     /** the current Moodle version is too high for plugin. */
 | 
        
           |  |  | 66 |     const REQUIREMENT_STATUS_NEWER = 'newer';
 | 
        
           |  |  | 67 |   | 
        
           |  |  | 68 |     /** the required dependency is available in the plugins directory */
 | 
        
           |  |  | 69 |     const REQUIREMENT_AVAILABLE = 'available';
 | 
        
           |  |  | 70 |     /** the required dependency is available in the plugins directory */
 | 
        
           |  |  | 71 |     const REQUIREMENT_UNAVAILABLE = 'unavailable';
 | 
        
           |  |  | 72 |   | 
        
           |  |  | 73 |     /** the moodle version is explicitly supported */
 | 
        
           |  |  | 74 |     const VERSION_SUPPORTED = 'supported';
 | 
        
           |  |  | 75 |     /** the moodle version is not explicitly supported */
 | 
        
           |  |  | 76 |     const VERSION_NOT_SUPPORTED = 'notsupported';
 | 
        
           |  |  | 77 |     /** the plugin does not specify supports */
 | 
        
           |  |  | 78 |     const VERSION_NO_SUPPORTS = 'nosupports';
 | 
        
           |  |  | 79 |   | 
        
           |  |  | 80 |     /** @var plugin_manager holds the singleton instance */
 | 
        
           |  |  | 81 |     protected static $singletoninstance;
 | 
        
           |  |  | 82 |     /** @var stdClass cache of standard plugins */
 | 
        
           |  |  | 83 |     protected static ?stdClass $standardplugincache = null;
 | 
        
           |  |  | 84 |     /** @var array of raw plugins information */
 | 
        
           |  |  | 85 |     protected $pluginsinfo = null;
 | 
        
           |  |  | 86 |     /** @var array of raw subplugins information */
 | 
        
           |  |  | 87 |     protected $subpluginsinfo = null;
 | 
        
           |  |  | 88 |     /** @var array cache information about availability in the plugins directory if requesting "at least" version */
 | 
        
           |  |  | 89 |     protected $remotepluginsinfoatleast = null;
 | 
        
           |  |  | 90 |     /** @var array cache information about availability in the plugins directory if requesting exact version */
 | 
        
           |  |  | 91 |     protected $remotepluginsinfoexact = null;
 | 
        
           |  |  | 92 |     /** @var array list of installed plugins $name=>$version */
 | 
        
           |  |  | 93 |     protected $installedplugins = null;
 | 
        
           |  |  | 94 |     /** @var array list of all enabled plugins $name=>$name */
 | 
        
           |  |  | 95 |     protected $enabledplugins = null;
 | 
        
           |  |  | 96 |     /** @var array list of all enabled plugins $name=>$diskversion */
 | 
        
           |  |  | 97 |     protected $presentplugins = null;
 | 
        
           |  |  | 98 |     /** @var array reordered list of plugin types */
 | 
        
           |  |  | 99 |     protected $plugintypes = null;
 | 
        
           |  |  | 100 |     /** @var \core\update\code_manager code manager to use for plugins code operations */
 | 
        
           |  |  | 101 |     protected $codemanager = null;
 | 
        
           |  |  | 102 |     /** @var \core\update\api client instance to use for accessing download.moodle.org/api/ */
 | 
        
           |  |  | 103 |     protected $updateapiclient = null;
 | 
        
           |  |  | 104 |   | 
        
           |  |  | 105 |     /**
 | 
        
           |  |  | 106 |      * Direct initiation not allowed, use the factory method {@link self::instance()}
 | 
        
           |  |  | 107 |      */
 | 
        
           |  |  | 108 |     protected function __construct() {
 | 
        
           |  |  | 109 |     }
 | 
        
           |  |  | 110 |   | 
        
           |  |  | 111 |     /**
 | 
        
           |  |  | 112 |      * Sorry, this is singleton
 | 
        
           |  |  | 113 |      */
 | 
        
           |  |  | 114 |     protected function __clone() {
 | 
        
           |  |  | 115 |     }
 | 
        
           |  |  | 116 |   | 
        
           |  |  | 117 |     /**
 | 
        
           |  |  | 118 |      * Factory method for this class
 | 
        
           |  |  | 119 |      *
 | 
        
           |  |  | 120 |      * @return static the singleton instance
 | 
        
           |  |  | 121 |      */
 | 
        
           |  |  | 122 |     public static function instance() {
 | 
        
           |  |  | 123 |         if (is_null(static::$singletoninstance)) {
 | 
        
           |  |  | 124 |             static::$singletoninstance = new static();
 | 
        
           |  |  | 125 |         }
 | 
        
           |  |  | 126 |         return static::$singletoninstance;
 | 
        
           |  |  | 127 |     }
 | 
        
           |  |  | 128 |   | 
        
           |  |  | 129 |     /**
 | 
        
           |  |  | 130 |      * Reset all caches.
 | 
        
           |  |  | 131 |      * @param bool $phpunitreset
 | 
        
           |  |  | 132 |      */
 | 
        
           |  |  | 133 |     public static function reset_caches($phpunitreset = false) {
 | 
        
           |  |  | 134 |         static::$standardplugincache = null;
 | 
        
           |  |  | 135 |         if ($phpunitreset) {
 | 
        
           |  |  | 136 |             static::$singletoninstance = null;
 | 
        
           |  |  | 137 |         } else {
 | 
        
           |  |  | 138 |             if (static::$singletoninstance) {
 | 
        
           |  |  | 139 |                 static::$singletoninstance->pluginsinfo = null;
 | 
        
           |  |  | 140 |                 static::$singletoninstance->subpluginsinfo = null;
 | 
        
           |  |  | 141 |                 static::$singletoninstance->remotepluginsinfoatleast = null;
 | 
        
           |  |  | 142 |                 static::$singletoninstance->remotepluginsinfoexact = null;
 | 
        
           |  |  | 143 |                 static::$singletoninstance->installedplugins = null;
 | 
        
           |  |  | 144 |                 static::$singletoninstance->enabledplugins = null;
 | 
        
           |  |  | 145 |                 static::$singletoninstance->presentplugins = null;
 | 
        
           |  |  | 146 |                 static::$singletoninstance->plugintypes = null;
 | 
        
           |  |  | 147 |                 static::$singletoninstance->codemanager = null;
 | 
        
           |  |  | 148 |                 static::$singletoninstance->updateapiclient = null;
 | 
        
           |  |  | 149 |             }
 | 
        
           |  |  | 150 |         }
 | 
        
           |  |  | 151 |         $cache = cache::make('core', 'plugin_manager');
 | 
        
           |  |  | 152 |         $cache->purge();
 | 
        
           |  |  | 153 |     }
 | 
        
           |  |  | 154 |   | 
        
           |  |  | 155 |     /**
 | 
        
           |  |  | 156 |      * Returns the result of {@link core_component::get_plugin_types()} ordered for humans
 | 
        
           |  |  | 157 |      *
 | 
        
           |  |  | 158 |      * @see self::reorder_plugin_types()
 | 
        
           |  |  | 159 |      * @return array (string)name => (string)location
 | 
        
           |  |  | 160 |      */
 | 
        
           |  |  | 161 |     public function get_plugin_types() {
 | 
        
           |  |  | 162 |         if (func_num_args() > 0) {
 | 
        
           |  |  | 163 |             if (!func_get_arg(0)) {
 | 
        
           |  |  | 164 |                 throw new coding_exception('core_plugin_manager->get_plugin_types() does not support relative paths.');
 | 
        
           |  |  | 165 |             }
 | 
        
           |  |  | 166 |         }
 | 
        
           |  |  | 167 |         if ($this->plugintypes) {
 | 
        
           |  |  | 168 |             return $this->plugintypes;
 | 
        
           |  |  | 169 |         }
 | 
        
           |  |  | 170 |   | 
        
           |  |  | 171 |         $this->plugintypes = $this->reorder_plugin_types(core_component::get_plugin_types());
 | 
        
           |  |  | 172 |         return $this->plugintypes;
 | 
        
           |  |  | 173 |     }
 | 
        
           |  |  | 174 |   | 
        
           |  |  | 175 |     /**
 | 
        
           |  |  | 176 |      * Load list of installed plugins,
 | 
        
           |  |  | 177 |      * always call before using $this->installedplugins.
 | 
        
           |  |  | 178 |      *
 | 
        
           |  |  | 179 |      * This method is caching results for all plugins.
 | 
        
           |  |  | 180 |      */
 | 
        
           |  |  | 181 |     protected function load_installed_plugins() {
 | 
        
           |  |  | 182 |         global $DB, $CFG;
 | 
        
           |  |  | 183 |   | 
        
           |  |  | 184 |         if ($this->installedplugins) {
 | 
        
           |  |  | 185 |             return;
 | 
        
           |  |  | 186 |         }
 | 
        
           |  |  | 187 |   | 
        
           |  |  | 188 |         if (empty($CFG->version)) {
 | 
        
           |  |  | 189 |             // Nothing installed yet.
 | 
        
           |  |  | 190 |             $this->installedplugins = [];
 | 
        
           |  |  | 191 |             return;
 | 
        
           |  |  | 192 |         }
 | 
        
           |  |  | 193 |   | 
        
           |  |  | 194 |         $cache = cache::make('core', 'plugin_manager');
 | 
        
           |  |  | 195 |         $installed = $cache->get('installed');
 | 
        
           |  |  | 196 |   | 
        
           |  |  | 197 |         if (is_array($installed)) {
 | 
        
           |  |  | 198 |             $this->installedplugins = $installed;
 | 
        
           |  |  | 199 |             return;
 | 
        
           |  |  | 200 |         }
 | 
        
           |  |  | 201 |   | 
        
           |  |  | 202 |         $this->installedplugins = [];
 | 
        
           |  |  | 203 |   | 
        
           |  |  | 204 |         $versions = $DB->get_records('config_plugins', ['name' => 'version']);
 | 
        
           |  |  | 205 |         foreach ($versions as $version) {
 | 
        
           |  |  | 206 |             $parts = explode('_', $version->plugin, 2);
 | 
        
           |  |  | 207 |             if (!isset($parts[1])) {
 | 
        
           |  |  | 208 |                 // Invalid component, there must be at least one "_".
 | 
        
           |  |  | 209 |                 continue;
 | 
        
           |  |  | 210 |             }
 | 
        
           |  |  | 211 |             // Do not verify here if plugin type and name are valid.
 | 
        
           |  |  | 212 |             $this->installedplugins[$parts[0]][$parts[1]] = $version->value;
 | 
        
           |  |  | 213 |         }
 | 
        
           |  |  | 214 |   | 
        
           |  |  | 215 |         foreach ($this->installedplugins as $key => $value) {
 | 
        
           |  |  | 216 |             ksort($this->installedplugins[$key]);
 | 
        
           |  |  | 217 |         }
 | 
        
           |  |  | 218 |   | 
        
           |  |  | 219 |         $cache->set('installed', $this->installedplugins);
 | 
        
           |  |  | 220 |     }
 | 
        
           |  |  | 221 |   | 
        
           |  |  | 222 |     /**
 | 
        
           |  |  | 223 |      * Return list of installed plugins of given type.
 | 
        
           |  |  | 224 |      * @param string $type
 | 
        
           |  |  | 225 |      * @return array $name=>$version
 | 
        
           |  |  | 226 |      */
 | 
        
           |  |  | 227 |     public function get_installed_plugins($type) {
 | 
        
           |  |  | 228 |         $this->load_installed_plugins();
 | 
        
           |  |  | 229 |         if (isset($this->installedplugins[$type])) {
 | 
        
           |  |  | 230 |             return $this->installedplugins[$type];
 | 
        
           |  |  | 231 |         }
 | 
        
           |  |  | 232 |         return [];
 | 
        
           |  |  | 233 |     }
 | 
        
           |  |  | 234 |   | 
        
           |  |  | 235 |     /**
 | 
        
           |  |  | 236 |      * Load list of all enabled plugins,
 | 
        
           |  |  | 237 |      * call before using $this->enabledplugins.
 | 
        
           |  |  | 238 |      *
 | 
        
           |  |  | 239 |      * This method is caching results from individual plugin info classes.
 | 
        
           |  |  | 240 |      */
 | 
        
           |  |  | 241 |     protected function load_enabled_plugins() {
 | 
        
           |  |  | 242 |         global $CFG;
 | 
        
           |  |  | 243 |   | 
        
           |  |  | 244 |         if ($this->enabledplugins) {
 | 
        
           |  |  | 245 |             return;
 | 
        
           |  |  | 246 |         }
 | 
        
           |  |  | 247 |   | 
        
           |  |  | 248 |         if (empty($CFG->version)) {
 | 
        
           |  |  | 249 |             $this->enabledplugins = [];
 | 
        
           |  |  | 250 |             return;
 | 
        
           |  |  | 251 |         }
 | 
        
           |  |  | 252 |   | 
        
           |  |  | 253 |         $cache = cache::make('core', 'plugin_manager');
 | 
        
           |  |  | 254 |         $enabled = $cache->get('enabled');
 | 
        
           |  |  | 255 |   | 
        
           |  |  | 256 |         if (is_array($enabled)) {
 | 
        
           |  |  | 257 |             $this->enabledplugins = $enabled;
 | 
        
           |  |  | 258 |             return;
 | 
        
           |  |  | 259 |         }
 | 
        
           |  |  | 260 |   | 
        
           |  |  | 261 |         $this->enabledplugins = [];
 | 
        
           |  |  | 262 |   | 
        
           |  |  | 263 |         require_once($CFG->libdir . '/adminlib.php');
 | 
        
           |  |  | 264 |   | 
        
           |  |  | 265 |         $plugintypes = core_component::get_plugin_types();
 | 
        
           |  |  | 266 |         foreach ($plugintypes as $plugintype => $fulldir) {
 | 
        
           |  |  | 267 |             $plugininfoclass = static::resolve_plugininfo_class($plugintype);
 | 
        
           |  |  | 268 |             if (class_exists($plugininfoclass)) {
 | 
        
           |  |  | 269 |                 $enabled = $plugininfoclass::get_enabled_plugins();
 | 
        
           |  |  | 270 |                 if (!is_array($enabled)) {
 | 
        
           |  |  | 271 |                     continue;
 | 
        
           |  |  | 272 |                 }
 | 
        
           |  |  | 273 |                 $this->enabledplugins[$plugintype] = $enabled;
 | 
        
           |  |  | 274 |             }
 | 
        
           |  |  | 275 |         }
 | 
        
           |  |  | 276 |   | 
        
           |  |  | 277 |         $cache->set('enabled', $this->enabledplugins);
 | 
        
           |  |  | 278 |     }
 | 
        
           |  |  | 279 |   | 
        
           |  |  | 280 |     /**
 | 
        
           |  |  | 281 |      * Get list of enabled plugins of given type,
 | 
        
           |  |  | 282 |      * the result may contain missing plugins.
 | 
        
           |  |  | 283 |      *
 | 
        
           |  |  | 284 |      * @param string $type
 | 
        
           |  |  | 285 |      * @return array|null  list of enabled plugins of this type, null if unknown
 | 
        
           |  |  | 286 |      */
 | 
        
           |  |  | 287 |     public function get_enabled_plugins($type) {
 | 
        
           |  |  | 288 |         $this->load_enabled_plugins();
 | 
        
           |  |  | 289 |         if (isset($this->enabledplugins[$type])) {
 | 
        
           |  |  | 290 |             return $this->enabledplugins[$type];
 | 
        
           |  |  | 291 |         }
 | 
        
           |  |  | 292 |         return null;
 | 
        
           |  |  | 293 |     }
 | 
        
           |  |  | 294 |   | 
        
           |  |  | 295 |     /**
 | 
        
           |  |  | 296 |      * Load list of all present plugins - call before using $this->presentplugins.
 | 
        
           |  |  | 297 |      */
 | 
        
           |  |  | 298 |     protected function load_present_plugins() {
 | 
        
           |  |  | 299 |         if ($this->presentplugins) {
 | 
        
           |  |  | 300 |             return;
 | 
        
           |  |  | 301 |         }
 | 
        
           |  |  | 302 |   | 
        
           |  |  | 303 |         $cache = cache::make('core', 'plugin_manager');
 | 
        
           |  |  | 304 |         $present = $cache->get('present');
 | 
        
           |  |  | 305 |   | 
        
           |  |  | 306 |         if (is_array($present)) {
 | 
        
           |  |  | 307 |             $this->presentplugins = $present;
 | 
        
           |  |  | 308 |             return;
 | 
        
           |  |  | 309 |         }
 | 
        
           |  |  | 310 |   | 
        
           |  |  | 311 |         $this->presentplugins = [];
 | 
        
           |  |  | 312 |   | 
        
           | 1441 | ariadna | 313 |         $allplugintypes = core_component::get_all_plugin_types();
 | 
        
           |  |  | 314 |         foreach ($allplugintypes as $type => $typedir) {
 | 
        
           |  |  | 315 |             $plugs = core_component::get_all_plugins_list($type);
 | 
        
           | 1 | efrain | 316 |             foreach ($plugs as $plug => $fullplug) {
 | 
        
           |  |  | 317 |                 $module = new stdClass();
 | 
        
           |  |  | 318 |                 $plugin = new stdClass();
 | 
        
           |  |  | 319 |                 $plugin->version = null;
 | 
        
           |  |  | 320 |                 include($fullplug . '/version.php');
 | 
        
           |  |  | 321 |   | 
        
           |  |  | 322 |                 // Check if the legacy $module syntax is still used.
 | 
        
           |  |  | 323 |                 if (!is_object($module) || (count((array)$module) > 0)) {
 | 
        
           |  |  | 324 |                     debugging('Unsupported $module syntax detected in version.php of the ' . $type . '_' . $plug . ' plugin.');
 | 
        
           |  |  | 325 |                     $skipcache = true;
 | 
        
           |  |  | 326 |                 }
 | 
        
           |  |  | 327 |   | 
        
           |  |  | 328 |                 // Check if the component is properly declared.
 | 
        
           |  |  | 329 |                 if (empty($plugin->component) || ($plugin->component !== $type . '_' . $plug)) {
 | 
        
           |  |  | 330 |                     debugging('Plugin ' . $type . '_' . $plug . ' does not declare valid $plugin->component in its version.php.');
 | 
        
           |  |  | 331 |                     $skipcache = true;
 | 
        
           |  |  | 332 |                 }
 | 
        
           |  |  | 333 |   | 
        
           |  |  | 334 |                 $this->presentplugins[$type][$plug] = $plugin;
 | 
        
           |  |  | 335 |             }
 | 
        
           |  |  | 336 |         }
 | 
        
           |  |  | 337 |   | 
        
           |  |  | 338 |         if (empty($skipcache)) {
 | 
        
           |  |  | 339 |             $cache->set('present', $this->presentplugins);
 | 
        
           |  |  | 340 |         }
 | 
        
           |  |  | 341 |     }
 | 
        
           |  |  | 342 |   | 
        
           |  |  | 343 |     /**
 | 
        
           |  |  | 344 |      * Load the standard plugin data from the plugins.json file.
 | 
        
           |  |  | 345 |      *
 | 
        
           |  |  | 346 |      * @return stdClass
 | 
        
           |  |  | 347 |      */
 | 
        
           |  |  | 348 |     protected static function load_standard_plugins(): stdClass {
 | 
        
           |  |  | 349 |         if (static::$standardplugincache === null) {
 | 
        
           |  |  | 350 |             $data = file_get_contents(dirname(__DIR__) . '/plugins.json');
 | 
        
           |  |  | 351 |             static::$standardplugincache = json_decode($data, false);
 | 
        
           |  |  | 352 |         }
 | 
        
           |  |  | 353 |   | 
        
           |  |  | 354 |         return static::$standardplugincache;
 | 
        
           |  |  | 355 |     }
 | 
        
           |  |  | 356 |   | 
        
           |  |  | 357 |     /**
 | 
        
           |  |  | 358 |      * Get list of present plugins of given type.
 | 
        
           |  |  | 359 |      *
 | 
        
           |  |  | 360 |      * @param string $type
 | 
        
           |  |  | 361 |      * @return array|null  list of presnet plugins $name=>$diskversion, null if unknown
 | 
        
           |  |  | 362 |      */
 | 
        
           |  |  | 363 |     public function get_present_plugins($type) {
 | 
        
           |  |  | 364 |         $this->load_present_plugins();
 | 
        
           |  |  | 365 |         if (isset($this->presentplugins[$type])) {
 | 
        
           |  |  | 366 |             return $this->presentplugins[$type];
 | 
        
           |  |  | 367 |         }
 | 
        
           |  |  | 368 |         return null;
 | 
        
           |  |  | 369 |     }
 | 
        
           |  |  | 370 |   | 
        
           |  |  | 371 |     /**
 | 
        
           |  |  | 372 |      * Returns a tree of known plugins and information about them
 | 
        
           |  |  | 373 |      *
 | 
        
           | 1441 | ariadna | 374 |      * @param bool $includeindeprecation whether to include plugins which are in deprecation (deprecated or deleted status).
 | 
        
           | 1 | efrain | 375 |      * @return array 2D array. The first keys are plugin type names (e.g. qtype);
 | 
        
           |  |  | 376 |      *      the second keys are the plugin local name (e.g. multichoice); and
 | 
        
           |  |  | 377 |      *      the values are the corresponding objects extending {@link \core\plugininfo\base}
 | 
        
           |  |  | 378 |      */
 | 
        
           | 1441 | ariadna | 379 |     public function get_plugins(bool $includeindeprecation = false) {
 | 
        
           | 1 | efrain | 380 |         $this->init_pluginsinfo_property();
 | 
        
           |  |  | 381 |   | 
        
           |  |  | 382 |         // Make sure all types are initialised.
 | 
        
           |  |  | 383 |         foreach ($this->pluginsinfo as $plugintype => $list) {
 | 
        
           |  |  | 384 |             if ($list === null) {
 | 
        
           | 1441 | ariadna | 385 |                 $this->get_plugins_of_type($plugintype, $includeindeprecation);
 | 
        
           | 1 | efrain | 386 |             }
 | 
        
           |  |  | 387 |         }
 | 
        
           |  |  | 388 |   | 
        
           | 1441 | ariadna | 389 |         if ($includeindeprecation) {
 | 
        
           |  |  | 390 |             return $this->pluginsinfo;
 | 
        
           |  |  | 391 |         }
 | 
        
           |  |  | 392 |         return array_filter($this->pluginsinfo, function($key) {
 | 
        
           |  |  | 393 |             return !core_component::is_plugintype_in_deprecation($key);
 | 
        
           |  |  | 394 |         }, ARRAY_FILTER_USE_KEY);
 | 
        
           | 1 | efrain | 395 |     }
 | 
        
           |  |  | 396 |   | 
        
           |  |  | 397 |     /**
 | 
        
           |  |  | 398 |      * Returns list of known plugins of the given type.
 | 
        
           |  |  | 399 |      *
 | 
        
           |  |  | 400 |      * This method returns the subset of the tree returned by {@link self::get_plugins()}.
 | 
        
           |  |  | 401 |      * If the given type is not known, empty array is returned.
 | 
        
           |  |  | 402 |      *
 | 
        
           |  |  | 403 |      * @param string $type plugin type, e.g. 'mod' or 'workshopallocation'
 | 
        
           | 1441 | ariadna | 404 |      * @param bool $includeindeprecation whether to include plugins which are in deprecation (deprecated or deleted status).
 | 
        
           | 1 | efrain | 405 |      * @return \core\plugininfo\base[] (string) plugin name => corresponding subclass of {@link \core\plugininfo\base}
 | 
        
           |  |  | 406 |      */
 | 
        
           | 1441 | ariadna | 407 |     public function get_plugins_of_type($type, bool $includeindeprecation = false) {
 | 
        
           | 1 | efrain | 408 |         global $CFG;
 | 
        
           |  |  | 409 |   | 
        
           |  |  | 410 |         $this->init_pluginsinfo_property();
 | 
        
           |  |  | 411 |   | 
        
           | 1441 | ariadna | 412 |         $exclude = !$includeindeprecation && core_component::is_plugintype_in_deprecation($type);
 | 
        
           |  |  | 413 |         if (!array_key_exists($type, $this->pluginsinfo) || $exclude) {
 | 
        
           | 1 | efrain | 414 |             return [];
 | 
        
           |  |  | 415 |         }
 | 
        
           |  |  | 416 |   | 
        
           |  |  | 417 |         if (is_array($this->pluginsinfo[$type])) {
 | 
        
           |  |  | 418 |             return $this->pluginsinfo[$type];
 | 
        
           |  |  | 419 |         }
 | 
        
           |  |  | 420 |   | 
        
           | 1441 | ariadna | 421 |         $allplugintypes = core_component::get_all_plugin_types();
 | 
        
           | 1 | efrain | 422 |   | 
        
           | 1441 | ariadna | 423 |         if (!isset($allplugintypes[$type])) {
 | 
        
           | 1 | efrain | 424 |             // Orphaned subplugins!
 | 
        
           |  |  | 425 |             $plugintypeclass = static::resolve_plugininfo_class($type);
 | 
        
           |  |  | 426 |             $this->pluginsinfo[$type] = $plugintypeclass::get_plugins($type, null, $plugintypeclass, $this);
 | 
        
           |  |  | 427 |             return $this->pluginsinfo[$type];
 | 
        
           |  |  | 428 |         }
 | 
        
           |  |  | 429 |   | 
        
           |  |  | 430 |         $plugintypeclass = static::resolve_plugininfo_class($type);
 | 
        
           | 1441 | ariadna | 431 |         if (isset($allplugintypes[$type])) {
 | 
        
           |  |  | 432 |             $plugins = $plugintypeclass::get_plugins($type, $allplugintypes[$type], $plugintypeclass, $this);
 | 
        
           |  |  | 433 |         }
 | 
        
           | 1 | efrain | 434 |         $this->pluginsinfo[$type] = $plugins;
 | 
        
           |  |  | 435 |   | 
        
           |  |  | 436 |         return $this->pluginsinfo[$type];
 | 
        
           |  |  | 437 |     }
 | 
        
           |  |  | 438 |   | 
        
           |  |  | 439 |     /**
 | 
        
           |  |  | 440 |      * Init placeholder array for plugin infos.
 | 
        
           |  |  | 441 |      */
 | 
        
           |  |  | 442 |     protected function init_pluginsinfo_property() {
 | 
        
           |  |  | 443 |         if (is_array($this->pluginsinfo)) {
 | 
        
           |  |  | 444 |             return;
 | 
        
           |  |  | 445 |         }
 | 
        
           |  |  | 446 |         $this->pluginsinfo = [];
 | 
        
           |  |  | 447 |   | 
        
           | 1441 | ariadna | 448 |         // The pluginsinfo instance var contains keys for all plugin types, including those currently in deprecation.
 | 
        
           |  |  | 449 |         // Other methods should filter their returns as needed, based on key checks, or by checking either
 | 
        
           |  |  | 450 |         // $plugininfo->is_deprecated() or $plugininfo->is_deleted().
 | 
        
           |  |  | 451 |         $plugintypes = array_merge(
 | 
        
           |  |  | 452 |             $this->get_plugin_types(),
 | 
        
           |  |  | 453 |             \core_component::get_deprecated_plugin_types(),
 | 
        
           |  |  | 454 |             \core_component::get_deleted_plugin_types()
 | 
        
           |  |  | 455 |         );
 | 
        
           | 1 | efrain | 456 |         foreach ($plugintypes as $plugintype => $plugintyperootdir) {
 | 
        
           |  |  | 457 |             $this->pluginsinfo[$plugintype] = null;
 | 
        
           |  |  | 458 |         }
 | 
        
           |  |  | 459 |   | 
        
           | 1441 | ariadna | 460 |         // Add orphaned plugins.
 | 
        
           | 1 | efrain | 461 |         $this->load_installed_plugins();
 | 
        
           |  |  | 462 |         foreach ($this->installedplugins as $plugintype => $unused) {
 | 
        
           |  |  | 463 |             if (!isset($plugintypes[$plugintype])) {
 | 
        
           |  |  | 464 |                 $this->pluginsinfo[$plugintype] = null;
 | 
        
           |  |  | 465 |             }
 | 
        
           |  |  | 466 |         }
 | 
        
           |  |  | 467 |     }
 | 
        
           |  |  | 468 |   | 
        
           |  |  | 469 |     /**
 | 
        
           |  |  | 470 |      * Find the plugin info class for given type.
 | 
        
           |  |  | 471 |      *
 | 
        
           |  |  | 472 |      * @param string $type
 | 
        
           |  |  | 473 |      * @return string name of pluginfo class for give plugin type
 | 
        
           |  |  | 474 |      */
 | 
        
           |  |  | 475 |     public static function resolve_plugininfo_class($type) {
 | 
        
           | 1441 | ariadna | 476 |         $allplugintypes = core_component::get_all_plugin_types();
 | 
        
           |  |  | 477 |   | 
        
           |  |  | 478 |         if (!isset($allplugintypes[$type])) {
 | 
        
           | 1 | efrain | 479 |             return '\core\plugininfo\orphaned';
 | 
        
           |  |  | 480 |         }
 | 
        
           |  |  | 481 |   | 
        
           |  |  | 482 |         $parent = core_component::get_subtype_parent($type);
 | 
        
           |  |  | 483 |   | 
        
           |  |  | 484 |         if ($parent) {
 | 
        
           |  |  | 485 |             $class = '\\' . $parent . '\plugininfo\\' . $type;
 | 
        
           |  |  | 486 |             if (class_exists($class)) {
 | 
        
           |  |  | 487 |                 $plugintypeclass = $class;
 | 
        
           |  |  | 488 |             } else {
 | 
        
           |  |  | 489 |                 if ($dir = core_component::get_component_directory($parent)) {
 | 
        
           |  |  | 490 |                     // BC only - use namespace instead!
 | 
        
           |  |  | 491 |                     if (file_exists("$dir/adminlib.php")) {
 | 
        
           |  |  | 492 |                         global $CFG;
 | 
        
           |  |  | 493 |                         include_once("$dir/adminlib.php");
 | 
        
           |  |  | 494 |                     }
 | 
        
           |  |  | 495 |                     if (class_exists('plugininfo_' . $type)) {
 | 
        
           |  |  | 496 |                         $plugintypeclass = 'plugininfo_' . $type;
 | 
        
           |  |  | 497 |                         debugging('Class "' . $plugintypeclass . '" is deprecated, migrate to "' . $class . '"', DEBUG_DEVELOPER);
 | 
        
           |  |  | 498 |                     } else {
 | 
        
           |  |  | 499 |                         debugging('Subplugin type "' . $type . '" should define class "' . $class . '"', DEBUG_DEVELOPER);
 | 
        
           |  |  | 500 |                         $plugintypeclass = '\core\plugininfo\general';
 | 
        
           |  |  | 501 |                     }
 | 
        
           |  |  | 502 |                 } else {
 | 
        
           |  |  | 503 |                     $plugintypeclass = '\core\plugininfo\general';
 | 
        
           |  |  | 504 |                 }
 | 
        
           |  |  | 505 |             }
 | 
        
           |  |  | 506 |         } else {
 | 
        
           |  |  | 507 |             $class = '\core\plugininfo\\' . $type;
 | 
        
           |  |  | 508 |             if (class_exists($class)) {
 | 
        
           |  |  | 509 |                 $plugintypeclass = $class;
 | 
        
           |  |  | 510 |             } else {
 | 
        
           |  |  | 511 |                 debugging('All standard types including "' . $type . '" should have plugininfo class!', DEBUG_DEVELOPER);
 | 
        
           |  |  | 512 |                 $plugintypeclass = '\core\plugininfo\general';
 | 
        
           |  |  | 513 |             }
 | 
        
           |  |  | 514 |         }
 | 
        
           |  |  | 515 |   | 
        
           |  |  | 516 |         if (!in_array('core\plugininfo\base', class_parents($plugintypeclass))) {
 | 
        
           |  |  | 517 |             throw new coding_exception('Class ' . $plugintypeclass . ' must extend \core\plugininfo\base');
 | 
        
           |  |  | 518 |         }
 | 
        
           |  |  | 519 |   | 
        
           |  |  | 520 |         return $plugintypeclass;
 | 
        
           |  |  | 521 |     }
 | 
        
           |  |  | 522 |   | 
        
           |  |  | 523 |     /**
 | 
        
           |  |  | 524 |      * Returns list of all known subplugins of the given plugin.
 | 
        
           |  |  | 525 |      *
 | 
        
           |  |  | 526 |      * For plugins that do not provide subplugins (i.e. there is no support for it),
 | 
        
           |  |  | 527 |      * empty array is returned.
 | 
        
           |  |  | 528 |      *
 | 
        
           |  |  | 529 |      * @param string $component full component name, e.g. 'mod_workshop'
 | 
        
           |  |  | 530 |      * @return array (string) component name (e.g. 'workshopallocation_random') => subclass of {@link \core\plugininfo\base}
 | 
        
           |  |  | 531 |      */
 | 
        
           |  |  | 532 |     public function get_subplugins_of_plugin($component) {
 | 
        
           |  |  | 533 |   | 
        
           |  |  | 534 |         $pluginfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 535 |   | 
        
           |  |  | 536 |         if (is_null($pluginfo)) {
 | 
        
           |  |  | 537 |             return [];
 | 
        
           |  |  | 538 |         }
 | 
        
           |  |  | 539 |   | 
        
           |  |  | 540 |         $subplugins = $this->get_subplugins();
 | 
        
           |  |  | 541 |   | 
        
           |  |  | 542 |         if (!isset($subplugins[$pluginfo->component])) {
 | 
        
           |  |  | 543 |             return [];
 | 
        
           |  |  | 544 |         }
 | 
        
           |  |  | 545 |   | 
        
           |  |  | 546 |         $list = [];
 | 
        
           |  |  | 547 |   | 
        
           |  |  | 548 |         foreach ($subplugins[$pluginfo->component] as $subdata) {
 | 
        
           |  |  | 549 |             foreach ($this->get_plugins_of_type($subdata->type) as $subpluginfo) {
 | 
        
           |  |  | 550 |                 $list[$subpluginfo->component] = $subpluginfo;
 | 
        
           |  |  | 551 |             }
 | 
        
           |  |  | 552 |         }
 | 
        
           |  |  | 553 |   | 
        
           |  |  | 554 |         return $list;
 | 
        
           |  |  | 555 |     }
 | 
        
           |  |  | 556 |   | 
        
           |  |  | 557 |     /**
 | 
        
           |  |  | 558 |      * Returns list of plugins that define their subplugins and the information
 | 
        
           |  |  | 559 |      * about them from the db/subplugins.json file.
 | 
        
           |  |  | 560 |      *
 | 
        
           |  |  | 561 |      * @return array with keys like 'mod_quiz', and values the data from the
 | 
        
           |  |  | 562 |      *      corresponding db/subplugins.json file.
 | 
        
           |  |  | 563 |      */
 | 
        
           |  |  | 564 |     public function get_subplugins() {
 | 
        
           |  |  | 565 |   | 
        
           |  |  | 566 |         if (is_array($this->subpluginsinfo)) {
 | 
        
           |  |  | 567 |             return $this->subpluginsinfo;
 | 
        
           |  |  | 568 |         }
 | 
        
           |  |  | 569 |   | 
        
           |  |  | 570 |         $plugintypes = core_component::get_plugin_types();
 | 
        
           |  |  | 571 |   | 
        
           |  |  | 572 |         $this->subpluginsinfo = [];
 | 
        
           |  |  | 573 |         foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
 | 
        
           |  |  | 574 |             foreach (core_component::get_plugin_list($type) as $plugin => $componentdir) {
 | 
        
           |  |  | 575 |                 $component = $type . '_' . $plugin;
 | 
        
           | 1441 | ariadna | 576 |                 $subplugins = core_component::get_subplugins($component) ?? [];
 | 
        
           | 1 | efrain | 577 |                 if (!$subplugins) {
 | 
        
           |  |  | 578 |                     continue;
 | 
        
           |  |  | 579 |                 }
 | 
        
           |  |  | 580 |                 $this->subpluginsinfo[$component] = [];
 | 
        
           |  |  | 581 |                 foreach ($subplugins as $subplugintype => $ignored) {
 | 
        
           |  |  | 582 |                     $subplugin = new stdClass();
 | 
        
           |  |  | 583 |                     $subplugin->type = $subplugintype;
 | 
        
           |  |  | 584 |                     $subplugin->typerootdir = $plugintypes[$subplugintype];
 | 
        
           |  |  | 585 |                     $this->subpluginsinfo[$component][$subplugintype] = $subplugin;
 | 
        
           |  |  | 586 |                 }
 | 
        
           |  |  | 587 |             }
 | 
        
           |  |  | 588 |         }
 | 
        
           |  |  | 589 |         return $this->subpluginsinfo;
 | 
        
           |  |  | 590 |     }
 | 
        
           |  |  | 591 |   | 
        
           |  |  | 592 |     /**
 | 
        
           |  |  | 593 |      * Returns the name of the plugin that defines the given subplugin type
 | 
        
           |  |  | 594 |      *
 | 
        
           |  |  | 595 |      * If the given subplugin type is not actually a subplugin, returns false.
 | 
        
           |  |  | 596 |      *
 | 
        
           |  |  | 597 |      * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
 | 
        
           | 1441 | ariadna | 598 |      * @param bool $includedeprecated whether to check deprecated subplugin types.
 | 
        
           | 1 | efrain | 599 |      * @return false|string the name of the parent plugin, eg. mod_workshop
 | 
        
           |  |  | 600 |      */
 | 
        
           | 1441 | ariadna | 601 |     public function get_parent_of_subplugin($subplugintype, bool $includedeprecated = false) {
 | 
        
           |  |  | 602 |         if (!$includedeprecated && core_component::is_plugintype_in_deprecation($subplugintype)) {
 | 
        
           |  |  | 603 |             return false;
 | 
        
           |  |  | 604 |         }
 | 
        
           | 1 | efrain | 605 |         $parent = core_component::get_subtype_parent($subplugintype);
 | 
        
           |  |  | 606 |         if (!$parent) {
 | 
        
           |  |  | 607 |             return false;
 | 
        
           |  |  | 608 |         }
 | 
        
           |  |  | 609 |         return $parent;
 | 
        
           |  |  | 610 |     }
 | 
        
           |  |  | 611 |   | 
        
           |  |  | 612 |     /**
 | 
        
           |  |  | 613 |      * Returns a localized name of a given plugin
 | 
        
           |  |  | 614 |      *
 | 
        
           |  |  | 615 |      * @param string $component name of the plugin, eg mod_workshop or auth_ldap
 | 
        
           |  |  | 616 |      * @return string
 | 
        
           |  |  | 617 |      */
 | 
        
           |  |  | 618 |     public function plugin_name($component) {
 | 
        
           |  |  | 619 |   | 
        
           |  |  | 620 |         $pluginfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 621 |   | 
        
           |  |  | 622 |         if (is_null($pluginfo)) {
 | 
        
           |  |  | 623 |             throw new moodle_exception('err_unknown_plugin', 'core_plugin', '', ['plugin' => $component]);
 | 
        
           |  |  | 624 |         }
 | 
        
           |  |  | 625 |   | 
        
           |  |  | 626 |         return $pluginfo->displayname;
 | 
        
           |  |  | 627 |     }
 | 
        
           |  |  | 628 |   | 
        
           |  |  | 629 |     /**
 | 
        
           |  |  | 630 |      * Returns a localized name of a plugin typed in singular form
 | 
        
           |  |  | 631 |      *
 | 
        
           |  |  | 632 |      * Most plugin types define their names in core_plugin lang file. In case of subplugins,
 | 
        
           |  |  | 633 |      * we try to ask the parent plugin for the name. In the worst case, we will return
 | 
        
           |  |  | 634 |      * the value of the passed $type parameter.
 | 
        
           |  |  | 635 |      *
 | 
        
           |  |  | 636 |      * @param string $type the type of the plugin, e.g. mod or workshopform
 | 
        
           |  |  | 637 |      * @return string
 | 
        
           |  |  | 638 |      */
 | 
        
           |  |  | 639 |     public function plugintype_name($type) {
 | 
        
           |  |  | 640 |         if (get_string_manager()->string_exists('type_' . $type, 'core_plugin')) {
 | 
        
           |  |  | 641 |             // For most plugin types, their names are defined in core_plugin lang file.
 | 
        
           |  |  | 642 |             return get_string('type_' . $type, 'core_plugin');
 | 
        
           |  |  | 643 |         } else if ($parent = $this->get_parent_of_subplugin($type)) {
 | 
        
           |  |  | 644 |             // If this is a subplugin, try to ask the parent plugin for the name.
 | 
        
           |  |  | 645 |             return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type, $parent);
 | 
        
           |  |  | 646 |         } else {
 | 
        
           |  |  | 647 |             return $type;
 | 
        
           |  |  | 648 |         }
 | 
        
           |  |  | 649 |     }
 | 
        
           |  |  | 650 |   | 
        
           |  |  | 651 |     /**
 | 
        
           |  |  | 652 |      * Returns a localized name of a plugin type in plural form
 | 
        
           |  |  | 653 |      *
 | 
        
           |  |  | 654 |      * Most plugin types define their names in core_plugin lang file. In case of subplugins,
 | 
        
           |  |  | 655 |      * we try to ask the parent plugin for the name. In the worst case, we will return
 | 
        
           |  |  | 656 |      * the value of the passed $type parameter.
 | 
        
           |  |  | 657 |      *
 | 
        
           |  |  | 658 |      * @param string $type the type of the plugin, e.g. mod or workshopform
 | 
        
           |  |  | 659 |      * @return string
 | 
        
           |  |  | 660 |      */
 | 
        
           |  |  | 661 |     public function plugintype_name_plural($type) {
 | 
        
           |  |  | 662 |         if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
 | 
        
           |  |  | 663 |             // For most plugin types, their names are defined in core_plugin lang file.
 | 
        
           |  |  | 664 |             return get_string('type_' . $type . '_plural', 'core_plugin');
 | 
        
           |  |  | 665 |         } else if ($parent = $this->get_parent_of_subplugin($type)) {
 | 
        
           |  |  | 666 |             // If this is a subplugin, try to ask the parent plugin for the name.
 | 
        
           |  |  | 667 |             return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
 | 
        
           |  |  | 668 |         } else {
 | 
        
           |  |  | 669 |             return $type;
 | 
        
           |  |  | 670 |         }
 | 
        
           |  |  | 671 |     }
 | 
        
           |  |  | 672 |   | 
        
           |  |  | 673 |     /**
 | 
        
           |  |  | 674 |      * Returns information about the known plugin, or null
 | 
        
           |  |  | 675 |      *
 | 
        
           |  |  | 676 |      * @param string $component frankenstyle component name.
 | 
        
           |  |  | 677 |      * @return \core\plugininfo\base|null the corresponding plugin information.
 | 
        
           |  |  | 678 |      */
 | 
        
           |  |  | 679 |     public function get_plugin_info($component) {
 | 
        
           |  |  | 680 |         [$type, $name] = core_component::normalize_component($component);
 | 
        
           | 1441 | ariadna | 681 |         $plugins = $this->get_plugins_of_type($type, true);
 | 
        
           | 1 | efrain | 682 |         if (isset($plugins[$name])) {
 | 
        
           |  |  | 683 |             return $plugins[$name];
 | 
        
           |  |  | 684 |         } else {
 | 
        
           |  |  | 685 |             return null;
 | 
        
           |  |  | 686 |         }
 | 
        
           |  |  | 687 |     }
 | 
        
           |  |  | 688 |   | 
        
           |  |  | 689 |     /**
 | 
        
           |  |  | 690 |      * Check to see if the current version of the plugin seems to be a checkout of an external repository.
 | 
        
           |  |  | 691 |      *
 | 
        
           |  |  | 692 |      * @param string $component frankenstyle component name
 | 
        
           |  |  | 693 |      * @return false|string
 | 
        
           |  |  | 694 |      */
 | 
        
           |  |  | 695 |     public function plugin_external_source($component) {
 | 
        
           |  |  | 696 |   | 
        
           |  |  | 697 |         $plugininfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 698 |   | 
        
           |  |  | 699 |         if (is_null($plugininfo)) {
 | 
        
           |  |  | 700 |             return false;
 | 
        
           |  |  | 701 |         }
 | 
        
           |  |  | 702 |   | 
        
           |  |  | 703 |         $pluginroot = $plugininfo->rootdir;
 | 
        
           |  |  | 704 |   | 
        
           |  |  | 705 |         if (is_dir($pluginroot . '/.git')) {
 | 
        
           |  |  | 706 |             return 'git';
 | 
        
           |  |  | 707 |         }
 | 
        
           |  |  | 708 |   | 
        
           |  |  | 709 |         if (is_file($pluginroot . '/.git')) {
 | 
        
           |  |  | 710 |             return 'git-submodule';
 | 
        
           |  |  | 711 |         }
 | 
        
           |  |  | 712 |   | 
        
           |  |  | 713 |         if (is_dir($pluginroot . '/CVS')) {
 | 
        
           |  |  | 714 |             return 'cvs';
 | 
        
           |  |  | 715 |         }
 | 
        
           |  |  | 716 |   | 
        
           |  |  | 717 |         if (is_dir($pluginroot . '/.svn')) {
 | 
        
           |  |  | 718 |             return 'svn';
 | 
        
           |  |  | 719 |         }
 | 
        
           |  |  | 720 |   | 
        
           |  |  | 721 |         if (is_dir($pluginroot . '/.hg')) {
 | 
        
           |  |  | 722 |             return 'mercurial';
 | 
        
           |  |  | 723 |         }
 | 
        
           |  |  | 724 |   | 
        
           |  |  | 725 |         return false;
 | 
        
           |  |  | 726 |     }
 | 
        
           |  |  | 727 |   | 
        
           |  |  | 728 |     /**
 | 
        
           |  |  | 729 |      * Get a list of any other plugins that require this one.
 | 
        
           |  |  | 730 |      * @param string $component frankenstyle component name.
 | 
        
           |  |  | 731 |      * @return array of frankensyle component names that require this one.
 | 
        
           |  |  | 732 |      */
 | 
        
           |  |  | 733 |     public function other_plugins_that_require($component) {
 | 
        
           |  |  | 734 |         $others = [];
 | 
        
           |  |  | 735 |         foreach ($this->get_plugins() as $type => $plugins) {
 | 
        
           |  |  | 736 |             foreach ($plugins as $plugin) {
 | 
        
           |  |  | 737 |                 $required = $plugin->get_other_required_plugins();
 | 
        
           |  |  | 738 |                 if (isset($required[$component])) {
 | 
        
           |  |  | 739 |                     $others[] = $plugin->component;
 | 
        
           |  |  | 740 |                 }
 | 
        
           |  |  | 741 |             }
 | 
        
           |  |  | 742 |         }
 | 
        
           |  |  | 743 |         return $others;
 | 
        
           |  |  | 744 |     }
 | 
        
           |  |  | 745 |   | 
        
           |  |  | 746 |     /**
 | 
        
           |  |  | 747 |      * Check a dependencies list against the list of installed plugins.
 | 
        
           |  |  | 748 |      * @param array $dependencies compenent name to required version or ANY_VERSION.
 | 
        
           |  |  | 749 |      * @return bool true if all the dependencies are satisfied.
 | 
        
           |  |  | 750 |      */
 | 
        
           |  |  | 751 |     public function are_dependencies_satisfied($dependencies) {
 | 
        
           |  |  | 752 |         foreach ($dependencies as $component => $requiredversion) {
 | 
        
           |  |  | 753 |             $otherplugin = $this->get_plugin_info($component);
 | 
        
           |  |  | 754 |             if (is_null($otherplugin)) {
 | 
        
           |  |  | 755 |                 return false;
 | 
        
           |  |  | 756 |             }
 | 
        
           |  |  | 757 |   | 
        
           |  |  | 758 |             if ($requiredversion != ANY_VERSION && $otherplugin->versiondisk < $requiredversion) {
 | 
        
           |  |  | 759 |                 return false;
 | 
        
           |  |  | 760 |             }
 | 
        
           |  |  | 761 |         }
 | 
        
           |  |  | 762 |   | 
        
           |  |  | 763 |         return true;
 | 
        
           |  |  | 764 |     }
 | 
        
           |  |  | 765 |   | 
        
           |  |  | 766 |     /**
 | 
        
           |  |  | 767 |      * Checks all dependencies for all installed plugins
 | 
        
           |  |  | 768 |      *
 | 
        
           |  |  | 769 |      * This is used by install and upgrade. The array passed by reference as the second
 | 
        
           |  |  | 770 |      * argument is populated with the list of plugins that have failed dependencies (note that
 | 
        
           |  |  | 771 |      * a single plugin can appear multiple times in the $failedplugins).
 | 
        
           |  |  | 772 |      *
 | 
        
           |  |  | 773 |      * @param int $moodleversion the version from version.php.
 | 
        
           |  |  | 774 |      * @param array $failedplugins to return the list of plugins with non-satisfied dependencies
 | 
        
           |  |  | 775 |      * @param int $branch the current moodle branch, null if not provided
 | 
        
           |  |  | 776 |      * @return bool true if all the dependencies are satisfied for all plugins.
 | 
        
           |  |  | 777 |      */
 | 
        
           |  |  | 778 |     public function all_plugins_ok($moodleversion, &$failedplugins = [], $branch = null) {
 | 
        
           |  |  | 779 |         global $CFG;
 | 
        
           |  |  | 780 |         if (empty($branch)) {
 | 
        
           |  |  | 781 |             $branch = $CFG->branch ?? '';
 | 
        
           |  |  | 782 |             if (empty($branch)) {
 | 
        
           |  |  | 783 |                 // During initial install there is no branch set.
 | 
        
           |  |  | 784 |                 require($CFG->dirroot . '/version.php');
 | 
        
           |  |  | 785 |                 $branch = (int)$branch;
 | 
        
           |  |  | 786 |                 // Force CFG->branch to int value during install.
 | 
        
           |  |  | 787 |                 $CFG->branch = $branch;
 | 
        
           |  |  | 788 |             }
 | 
        
           |  |  | 789 |         }
 | 
        
           |  |  | 790 |         $return = true;
 | 
        
           |  |  | 791 |         foreach ($this->get_plugins() as $type => $plugins) {
 | 
        
           |  |  | 792 |             foreach ($plugins as $plugin) {
 | 
        
           |  |  | 793 |                 if (!$plugin->is_core_dependency_satisfied($moodleversion)) {
 | 
        
           |  |  | 794 |                     $return = false;
 | 
        
           |  |  | 795 |                     $failedplugins[] = $plugin->component;
 | 
        
           |  |  | 796 |                 }
 | 
        
           |  |  | 797 |   | 
        
           |  |  | 798 |                 if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
 | 
        
           |  |  | 799 |                     $return = false;
 | 
        
           |  |  | 800 |                     $failedplugins[] = $plugin->component;
 | 
        
           |  |  | 801 |                 }
 | 
        
           |  |  | 802 |   | 
        
           |  |  | 803 |                 if (!$plugin->is_core_compatible_satisfied($branch)) {
 | 
        
           |  |  | 804 |                     $return = false;
 | 
        
           |  |  | 805 |                     $failedplugins[] = $plugin->component;
 | 
        
           |  |  | 806 |                 }
 | 
        
           |  |  | 807 |             }
 | 
        
           |  |  | 808 |         }
 | 
        
           |  |  | 809 |   | 
        
           |  |  | 810 |         return $return;
 | 
        
           |  |  | 811 |     }
 | 
        
           |  |  | 812 |   | 
        
           |  |  | 813 |     /**
 | 
        
           |  |  | 814 |      * Resolve requirements and dependencies of a plugin.
 | 
        
           |  |  | 815 |      *
 | 
        
           |  |  | 816 |      * Returns an array of objects describing the requirement/dependency,
 | 
        
           |  |  | 817 |      * indexed by the frankenstyle name of the component. The returned array
 | 
        
           |  |  | 818 |      * can be empty. The objects in the array have following properties:
 | 
        
           |  |  | 819 |      *
 | 
        
           |  |  | 820 |      *  ->(numeric)hasver
 | 
        
           |  |  | 821 |      *  ->(numeric)reqver
 | 
        
           |  |  | 822 |      *  ->(string)status
 | 
        
           |  |  | 823 |      *  ->(string)availability
 | 
        
           |  |  | 824 |      *
 | 
        
           |  |  | 825 |      * @param \core\plugininfo\base $plugin the plugin we are checking
 | 
        
           |  |  | 826 |      * @param null|string|int|double $moodleversion explicit moodle core version to check against, defaults to $CFG->version
 | 
        
           |  |  | 827 |      * @param null|string|int $moodlebranch explicit moodle core branch to check against, defaults to $CFG->branch
 | 
        
           |  |  | 828 |      * @return array of objects
 | 
        
           |  |  | 829 |      */
 | 
        
           |  |  | 830 |     public function resolve_requirements(\core\plugininfo\base $plugin, $moodleversion = null, $moodlebranch = null) {
 | 
        
           |  |  | 831 |         global $CFG;
 | 
        
           |  |  | 832 |   | 
        
           |  |  | 833 |         if ($plugin->versiondisk === null) {
 | 
        
           |  |  | 834 |             // Missing from disk, we have no version.php to read from.
 | 
        
           |  |  | 835 |             return [];
 | 
        
           |  |  | 836 |         }
 | 
        
           |  |  | 837 |   | 
        
           |  |  | 838 |         if ($moodleversion === null) {
 | 
        
           |  |  | 839 |             $moodleversion = $CFG->version;
 | 
        
           |  |  | 840 |         }
 | 
        
           |  |  | 841 |   | 
        
           |  |  | 842 |         if ($moodlebranch === null) {
 | 
        
           |  |  | 843 |             $moodlebranch = $CFG->branch;
 | 
        
           |  |  | 844 |         }
 | 
        
           |  |  | 845 |   | 
        
           |  |  | 846 |         $reqs = [];
 | 
        
           |  |  | 847 |         $reqcore = $this->resolve_core_requirements($plugin, $moodleversion, $moodlebranch);
 | 
        
           |  |  | 848 |   | 
        
           |  |  | 849 |         if (!empty($reqcore)) {
 | 
        
           |  |  | 850 |             $reqs['core'] = $reqcore;
 | 
        
           |  |  | 851 |         }
 | 
        
           |  |  | 852 |   | 
        
           |  |  | 853 |         foreach ($plugin->get_other_required_plugins() as $reqplug => $reqver) {
 | 
        
           |  |  | 854 |             $reqs[$reqplug] = $this->resolve_dependency_requirements($plugin, $reqplug, $reqver, $moodlebranch);
 | 
        
           |  |  | 855 |         }
 | 
        
           |  |  | 856 |   | 
        
           |  |  | 857 |         return $reqs;
 | 
        
           |  |  | 858 |     }
 | 
        
           |  |  | 859 |   | 
        
           |  |  | 860 |     /**
 | 
        
           |  |  | 861 |      * Helper method to resolve plugin's requirements on the moodle core.
 | 
        
           |  |  | 862 |      *
 | 
        
           |  |  | 863 |      * @param \core\plugininfo\base $plugin the plugin we are checking
 | 
        
           |  |  | 864 |      * @param string|int|double $moodleversion moodle core branch to check against
 | 
        
           |  |  | 865 |      * @return stdClass
 | 
        
           |  |  | 866 |      */
 | 
        
           |  |  | 867 |     protected function resolve_core_requirements(\core\plugininfo\base $plugin, $moodleversion, $moodlebranch) {
 | 
        
           |  |  | 868 |         $reqs = (object)[
 | 
        
           |  |  | 869 |             'hasver' => null,
 | 
        
           |  |  | 870 |             'reqver' => null,
 | 
        
           |  |  | 871 |             'status' => null,
 | 
        
           |  |  | 872 |             'availability' => null,
 | 
        
           |  |  | 873 |         ];
 | 
        
           |  |  | 874 |         $reqs->hasver = $moodleversion;
 | 
        
           |  |  | 875 |   | 
        
           |  |  | 876 |         if (empty($plugin->versionrequires)) {
 | 
        
           |  |  | 877 |             $reqs->reqver = ANY_VERSION;
 | 
        
           |  |  | 878 |         } else {
 | 
        
           |  |  | 879 |             $reqs->reqver = $plugin->versionrequires;
 | 
        
           |  |  | 880 |         }
 | 
        
           |  |  | 881 |   | 
        
           |  |  | 882 |         if ($plugin->is_core_dependency_satisfied($moodleversion)) {
 | 
        
           |  |  | 883 |             $reqs->status = self::REQUIREMENT_STATUS_OK;
 | 
        
           |  |  | 884 |         } else {
 | 
        
           |  |  | 885 |             $reqs->status = self::REQUIREMENT_STATUS_OUTDATED;
 | 
        
           |  |  | 886 |         }
 | 
        
           |  |  | 887 |   | 
        
           |  |  | 888 |         // Now check if there is an explicit incompatible, supersedes requires.
 | 
        
           |  |  | 889 |         if (isset($plugin->pluginincompatible) && $plugin->pluginincompatible != null) {
 | 
        
           |  |  | 890 |             if (!$plugin->is_core_compatible_satisfied($moodlebranch)) {
 | 
        
           |  |  | 891 |                 $reqs->status = self::REQUIREMENT_STATUS_NEWER;
 | 
        
           |  |  | 892 |             }
 | 
        
           |  |  | 893 |         }
 | 
        
           |  |  | 894 |   | 
        
           |  |  | 895 |         return $reqs;
 | 
        
           |  |  | 896 |     }
 | 
        
           |  |  | 897 |   | 
        
           |  |  | 898 |     /**
 | 
        
           |  |  | 899 |      * Helper method to resolve plugin's dependecies on other plugins.
 | 
        
           |  |  | 900 |      *
 | 
        
           |  |  | 901 |      * @param \core\plugininfo\base $plugin the plugin we are checking
 | 
        
           |  |  | 902 |      * @param string $otherpluginname
 | 
        
           |  |  | 903 |      * @param string|int $requiredversion
 | 
        
           |  |  | 904 |      * @param string|int $moodlebranch explicit moodle core branch to check against, defaults to $CFG->branch
 | 
        
           |  |  | 905 |      * @return stdClass
 | 
        
           |  |  | 906 |      */
 | 
        
           |  |  | 907 |     protected function resolve_dependency_requirements(
 | 
        
           |  |  | 908 |         \core\plugininfo\base $plugin,
 | 
        
           |  |  | 909 |         $otherpluginname,
 | 
        
           |  |  | 910 |         $requiredversion,
 | 
        
           |  |  | 911 |         $moodlebranch
 | 
        
           |  |  | 912 |     ) {
 | 
        
           |  |  | 913 |   | 
        
           |  |  | 914 |         $reqs = (object)[
 | 
        
           |  |  | 915 |             'hasver' => null,
 | 
        
           |  |  | 916 |             'reqver' => null,
 | 
        
           |  |  | 917 |             'status' => null,
 | 
        
           |  |  | 918 |             'availability' => null,
 | 
        
           |  |  | 919 |         ];
 | 
        
           |  |  | 920 |   | 
        
           |  |  | 921 |         $otherplugin = $this->get_plugin_info($otherpluginname);
 | 
        
           |  |  | 922 |   | 
        
           |  |  | 923 |         if ($otherplugin !== null) {
 | 
        
           |  |  | 924 |             // The required plugin is installed.
 | 
        
           |  |  | 925 |             $reqs->hasver = $otherplugin->versiondisk;
 | 
        
           |  |  | 926 |             $reqs->reqver = $requiredversion;
 | 
        
           |  |  | 927 |             // Check it has sufficient version.
 | 
        
           |  |  | 928 |             if ($requiredversion == ANY_VERSION || $otherplugin->versiondisk >= $requiredversion) {
 | 
        
           |  |  | 929 |                 $reqs->status = self::REQUIREMENT_STATUS_OK;
 | 
        
           |  |  | 930 |             } else {
 | 
        
           |  |  | 931 |                 $reqs->status = self::REQUIREMENT_STATUS_OUTDATED;
 | 
        
           |  |  | 932 |             }
 | 
        
           |  |  | 933 |         } else {
 | 
        
           |  |  | 934 |             // The required plugin is not installed.
 | 
        
           |  |  | 935 |             $reqs->hasver = null;
 | 
        
           |  |  | 936 |             $reqs->reqver = $requiredversion;
 | 
        
           |  |  | 937 |             $reqs->status = self::REQUIREMENT_STATUS_MISSING;
 | 
        
           |  |  | 938 |         }
 | 
        
           |  |  | 939 |   | 
        
           |  |  | 940 |         if ($reqs->status !== self::REQUIREMENT_STATUS_OK) {
 | 
        
           |  |  | 941 |             if ($this->is_remote_plugin_available($otherpluginname, $requiredversion, false)) {
 | 
        
           |  |  | 942 |                 $reqs->availability = self::REQUIREMENT_AVAILABLE;
 | 
        
           |  |  | 943 |             } else {
 | 
        
           |  |  | 944 |                 $reqs->availability = self::REQUIREMENT_UNAVAILABLE;
 | 
        
           |  |  | 945 |             }
 | 
        
           |  |  | 946 |         }
 | 
        
           |  |  | 947 |   | 
        
           |  |  | 948 |         return $reqs;
 | 
        
           |  |  | 949 |     }
 | 
        
           |  |  | 950 |   | 
        
           |  |  | 951 |     /**
 | 
        
           |  |  | 952 |      * Helper method to determine whether a moodle version is explicitly supported.
 | 
        
           |  |  | 953 |      *
 | 
        
           |  |  | 954 |      * @param \core\plugininfo\base $plugin the plugin we are checking
 | 
        
           |  |  | 955 |      * @param int $branch the moodle branch to check support for
 | 
        
           |  |  | 956 |      * @return string
 | 
        
           |  |  | 957 |      */
 | 
        
           |  |  | 958 |     public function check_explicitly_supported($plugin, $branch): string {
 | 
        
           |  |  | 959 |         // Check for correctly formed supported.
 | 
        
           |  |  | 960 |         if (isset($plugin->pluginsupported)) {
 | 
        
           |  |  | 961 |             // Broken apart for readability.
 | 
        
           |  |  | 962 |             $error = false;
 | 
        
           |  |  | 963 |             if (!is_array($plugin->pluginsupported)) {
 | 
        
           |  |  | 964 |                 $error = true;
 | 
        
           |  |  | 965 |             }
 | 
        
           |  |  | 966 |             if (!is_int($plugin->pluginsupported[0]) || !is_int($plugin->pluginsupported[1])) {
 | 
        
           |  |  | 967 |                 $error = true;
 | 
        
           |  |  | 968 |             }
 | 
        
           |  |  | 969 |             if (count($plugin->pluginsupported) != 2) {
 | 
        
           |  |  | 970 |                 $error = true;
 | 
        
           |  |  | 971 |             }
 | 
        
           |  |  | 972 |             if ($error) {
 | 
        
           |  |  | 973 |                 throw new coding_exception(get_string('err_supported_syntax', 'core_plugin'));
 | 
        
           |  |  | 974 |             }
 | 
        
           |  |  | 975 |         }
 | 
        
           |  |  | 976 |   | 
        
           |  |  | 977 |         if (isset($plugin->pluginsupported) && $plugin->pluginsupported != null) {
 | 
        
           |  |  | 978 |             if ($plugin->pluginsupported[0] <= $branch && $branch <= $plugin->pluginsupported[1]) {
 | 
        
           |  |  | 979 |                 return self::VERSION_SUPPORTED;
 | 
        
           |  |  | 980 |             } else {
 | 
        
           |  |  | 981 |                 return self::VERSION_NOT_SUPPORTED;
 | 
        
           |  |  | 982 |             }
 | 
        
           |  |  | 983 |         } else {
 | 
        
           |  |  | 984 |             // If supports aren't specified, but incompatible is, return not supported if not incompatible.
 | 
        
           |  |  | 985 |             if (!isset($plugin->pluginsupported) && isset($plugin->pluginincompatible) && !empty($plugin->pluginincompatible)) {
 | 
        
           |  |  | 986 |                 if (!$plugin->is_core_compatible_satisfied($branch)) {
 | 
        
           |  |  | 987 |                     return self::VERSION_NOT_SUPPORTED;
 | 
        
           |  |  | 988 |                 }
 | 
        
           |  |  | 989 |             }
 | 
        
           |  |  | 990 |             return self::VERSION_NO_SUPPORTS;
 | 
        
           |  |  | 991 |         }
 | 
        
           |  |  | 992 |     }
 | 
        
           |  |  | 993 |   | 
        
           |  |  | 994 |     /**
 | 
        
           |  |  | 995 |      * Is the given plugin version available in the plugins directory?
 | 
        
           |  |  | 996 |      *
 | 
        
           |  |  | 997 |      * See {@link self::get_remote_plugin_info()} for the full explanation of how the $version
 | 
        
           |  |  | 998 |      * parameter is interpretted.
 | 
        
           |  |  | 999 |      *
 | 
        
           |  |  | 1000 |      * @param string $component plugin frankenstyle name
 | 
        
           |  |  | 1001 |      * @param string|int $version ANY_VERSION or the version number
 | 
        
           |  |  | 1002 |      * @param bool $exactmatch false if "given version or higher" is requested
 | 
        
           |  |  | 1003 |      * @return boolean
 | 
        
           |  |  | 1004 |      */
 | 
        
           |  |  | 1005 |     public function is_remote_plugin_available($component, $version, $exactmatch) {
 | 
        
           |  |  | 1006 |   | 
        
           |  |  | 1007 |         $info = $this->get_remote_plugin_info($component, $version, $exactmatch);
 | 
        
           |  |  | 1008 |   | 
        
           |  |  | 1009 |         if (empty($info)) {
 | 
        
           |  |  | 1010 |             // There is no available plugin of that name.
 | 
        
           |  |  | 1011 |             return false;
 | 
        
           |  |  | 1012 |         }
 | 
        
           |  |  | 1013 |   | 
        
           |  |  | 1014 |         if (empty($info->version)) {
 | 
        
           |  |  | 1015 |             // Plugin is known, but no suitable version was found.
 | 
        
           |  |  | 1016 |             return false;
 | 
        
           |  |  | 1017 |         }
 | 
        
           |  |  | 1018 |   | 
        
           |  |  | 1019 |         return true;
 | 
        
           |  |  | 1020 |     }
 | 
        
           |  |  | 1021 |   | 
        
           |  |  | 1022 |     /**
 | 
        
           |  |  | 1023 |      * Can the given plugin version be installed via the admin UI?
 | 
        
           |  |  | 1024 |      *
 | 
        
           |  |  | 1025 |      * This check should be used whenever attempting to install a plugin from
 | 
        
           |  |  | 1026 |      * the plugins directory (new install, available update, missing dependency).
 | 
        
           |  |  | 1027 |      *
 | 
        
           |  |  | 1028 |      * @param string $component
 | 
        
           |  |  | 1029 |      * @param int $version version number
 | 
        
           |  |  | 1030 |      * @param string $reason returned code of the reason why it is not
 | 
        
           |  |  | 1031 |      * @param bool $checkremote check this version availability on moodle server
 | 
        
           |  |  | 1032 |      * @return boolean
 | 
        
           |  |  | 1033 |      */
 | 
        
           |  |  | 1034 |     public function is_remote_plugin_installable($component, $version, &$reason = null, $checkremote = true) {
 | 
        
           |  |  | 1035 |         global $CFG;
 | 
        
           |  |  | 1036 |   | 
        
           |  |  | 1037 |         // Make sure the feature is not disabled.
 | 
        
           |  |  | 1038 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1039 |             $reason = 'disabled';
 | 
        
           |  |  | 1040 |             return false;
 | 
        
           |  |  | 1041 |         }
 | 
        
           |  |  | 1042 |   | 
        
           |  |  | 1043 |         // Make sure the version is available.
 | 
        
           |  |  | 1044 |         if ($checkremote && !$this->is_remote_plugin_available($component, $version, true)) {
 | 
        
           |  |  | 1045 |             $reason = 'remoteunavailable';
 | 
        
           |  |  | 1046 |             return false;
 | 
        
           |  |  | 1047 |         }
 | 
        
           |  |  | 1048 |   | 
        
           |  |  | 1049 |         // Make sure the plugin type root directory is writable.
 | 
        
           |  |  | 1050 |         [$plugintype, $pluginname] = core_component::normalize_component($component);
 | 
        
           |  |  | 1051 |         if (!$this->is_plugintype_writable($plugintype)) {
 | 
        
           |  |  | 1052 |             $reason = 'notwritableplugintype';
 | 
        
           |  |  | 1053 |             return false;
 | 
        
           |  |  | 1054 |         }
 | 
        
           |  |  | 1055 |   | 
        
           |  |  | 1056 |         if (!$checkremote) {
 | 
        
           |  |  | 1057 |             $remoteversion = $version;
 | 
        
           |  |  | 1058 |         } else {
 | 
        
           |  |  | 1059 |             $remoteinfo = $this->get_remote_plugin_info($component, $version, true);
 | 
        
           |  |  | 1060 |             $remoteversion = $remoteinfo->version->version;
 | 
        
           |  |  | 1061 |         }
 | 
        
           |  |  | 1062 |         $localinfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 1063 |   | 
        
           |  |  | 1064 |         if ($localinfo) {
 | 
        
           |  |  | 1065 |             // If the plugin is already present, prevent downgrade.
 | 
        
           |  |  | 1066 |             if ($localinfo->versiondb > $remoteversion) {
 | 
        
           |  |  | 1067 |                 $reason = 'cannotdowngrade';
 | 
        
           |  |  | 1068 |                 return false;
 | 
        
           |  |  | 1069 |             }
 | 
        
           |  |  | 1070 |   | 
        
           |  |  | 1071 |             // Make sure we have write access to all the existing code.
 | 
        
           |  |  | 1072 |             if (is_dir($localinfo->rootdir)) {
 | 
        
           |  |  | 1073 |                 if (!$this->is_plugin_folder_removable($component)) {
 | 
        
           |  |  | 1074 |                     $reason = 'notwritableplugin';
 | 
        
           |  |  | 1075 |                     return false;
 | 
        
           |  |  | 1076 |                 }
 | 
        
           |  |  | 1077 |             }
 | 
        
           |  |  | 1078 |         }
 | 
        
           |  |  | 1079 |   | 
        
           |  |  | 1080 |         // Looks like it could work.
 | 
        
           |  |  | 1081 |         return true;
 | 
        
           |  |  | 1082 |     }
 | 
        
           |  |  | 1083 |   | 
        
           |  |  | 1084 |     /**
 | 
        
           |  |  | 1085 |      * Given the list of remote plugin infos, return just those installable.
 | 
        
           |  |  | 1086 |      *
 | 
        
           |  |  | 1087 |      * This is typically used on lists returned by
 | 
        
           |  |  | 1088 |      * {@link self::available_updates()} or {@link self::missing_dependencies()}
 | 
        
           |  |  | 1089 |      * to perform bulk installation of remote plugins.
 | 
        
           |  |  | 1090 |      *
 | 
        
           |  |  | 1091 |      * @param array $remoteinfos list of {@link \core\update\remote_info}
 | 
        
           |  |  | 1092 |      * @return array
 | 
        
           |  |  | 1093 |      */
 | 
        
           |  |  | 1094 |     public function filter_installable($remoteinfos) {
 | 
        
           |  |  | 1095 |         global $CFG;
 | 
        
           |  |  | 1096 |   | 
        
           |  |  | 1097 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1098 |             return [];
 | 
        
           |  |  | 1099 |         }
 | 
        
           |  |  | 1100 |         if (empty($remoteinfos)) {
 | 
        
           |  |  | 1101 |             return [];
 | 
        
           |  |  | 1102 |         }
 | 
        
           |  |  | 1103 |         $installable = [];
 | 
        
           |  |  | 1104 |         foreach ($remoteinfos as $index => $remoteinfo) {
 | 
        
           |  |  | 1105 |             if ($this->is_remote_plugin_installable($remoteinfo->component, $remoteinfo->version->version)) {
 | 
        
           |  |  | 1106 |                 $installable[$index] = $remoteinfo;
 | 
        
           |  |  | 1107 |             }
 | 
        
           |  |  | 1108 |         }
 | 
        
           |  |  | 1109 |         return $installable;
 | 
        
           |  |  | 1110 |     }
 | 
        
           |  |  | 1111 |   | 
        
           |  |  | 1112 |     /**
 | 
        
           |  |  | 1113 |      * Returns information about a plugin in the plugins directory.
 | 
        
           |  |  | 1114 |      *
 | 
        
           |  |  | 1115 |      * This is typically used when checking for available dependencies (in
 | 
        
           |  |  | 1116 |      * which case the $version represents minimal version we need), or
 | 
        
           |  |  | 1117 |      * when installing an available update or a new plugin from the plugins
 | 
        
           |  |  | 1118 |      * directory (in which case the $version is exact version we are
 | 
        
           |  |  | 1119 |      * interested in). The interpretation of the $version is controlled
 | 
        
           |  |  | 1120 |      * by the $exactmatch argument.
 | 
        
           |  |  | 1121 |      *
 | 
        
           |  |  | 1122 |      * If a plugin with the given component name is found, data about the
 | 
        
           |  |  | 1123 |      * plugin are returned as an object. The ->version property of the object
 | 
        
           |  |  | 1124 |      * contains the information about the particular plugin version that
 | 
        
           |  |  | 1125 |      * matches best the given critera. The ->version property is false if no
 | 
        
           |  |  | 1126 |      * suitable version of the plugin was found (yet the plugin itself is
 | 
        
           |  |  | 1127 |      * known).
 | 
        
           |  |  | 1128 |      *
 | 
        
           |  |  | 1129 |      * See {@link \core\update\api::validate_pluginfo_format()} for the
 | 
        
           |  |  | 1130 |      * returned data structure.
 | 
        
           |  |  | 1131 |      *
 | 
        
           |  |  | 1132 |      * @param string $component plugin frankenstyle name
 | 
        
           |  |  | 1133 |      * @param string|int $version ANY_VERSION or the version number
 | 
        
           |  |  | 1134 |      * @param bool $exactmatch false if "given version or higher" is requested
 | 
        
           |  |  | 1135 |      * @return \core\update\remote_info|bool
 | 
        
           |  |  | 1136 |      */
 | 
        
           |  |  | 1137 |     public function get_remote_plugin_info($component, $version, $exactmatch) {
 | 
        
           |  |  | 1138 |         if ($exactmatch && $version == ANY_VERSION) {
 | 
        
           |  |  | 1139 |             throw new coding_exception('Invalid request for exactly any version, it does not make sense.');
 | 
        
           |  |  | 1140 |         }
 | 
        
           |  |  | 1141 |   | 
        
           |  |  | 1142 |         $client = $this->get_update_api_client();
 | 
        
           |  |  | 1143 |   | 
        
           |  |  | 1144 |         if ($exactmatch) {
 | 
        
           |  |  | 1145 |             // Use client's get_plugin_info() method.
 | 
        
           |  |  | 1146 |             if (!isset($this->remotepluginsinfoexact[$component][$version])) {
 | 
        
           |  |  | 1147 |                 $this->remotepluginsinfoexact[$component][$version] = $client->get_plugin_info($component, $version);
 | 
        
           |  |  | 1148 |             }
 | 
        
           |  |  | 1149 |             return $this->remotepluginsinfoexact[$component][$version];
 | 
        
           |  |  | 1150 |         } else {
 | 
        
           |  |  | 1151 |             // Use client's find_plugin() method.
 | 
        
           |  |  | 1152 |             if (!isset($this->remotepluginsinfoatleast[$component][$version])) {
 | 
        
           |  |  | 1153 |                 $this->remotepluginsinfoatleast[$component][$version] = $client->find_plugin($component, $version);
 | 
        
           |  |  | 1154 |             }
 | 
        
           |  |  | 1155 |             return $this->remotepluginsinfoatleast[$component][$version];
 | 
        
           |  |  | 1156 |         }
 | 
        
           |  |  | 1157 |     }
 | 
        
           |  |  | 1158 |   | 
        
           |  |  | 1159 |     /**
 | 
        
           |  |  | 1160 |      * Obtain the plugin ZIP file from the given URL
 | 
        
           |  |  | 1161 |      *
 | 
        
           |  |  | 1162 |      * The caller is supposed to know both downloads URL and the MD5 hash of
 | 
        
           |  |  | 1163 |      * the ZIP contents in advance, typically by using the API requests against
 | 
        
           |  |  | 1164 |      * the plugins directory.
 | 
        
           |  |  | 1165 |      *
 | 
        
           |  |  | 1166 |      * @param string $url
 | 
        
           |  |  | 1167 |      * @param string $md5
 | 
        
           |  |  | 1168 |      * @return string|bool full path to the file, false on error
 | 
        
           |  |  | 1169 |      */
 | 
        
           |  |  | 1170 |     public function get_remote_plugin_zip($url, $md5) {
 | 
        
           |  |  | 1171 |         global $CFG;
 | 
        
           |  |  | 1172 |   | 
        
           |  |  | 1173 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1174 |             return false;
 | 
        
           |  |  | 1175 |         }
 | 
        
           |  |  | 1176 |         return $this->get_code_manager()->get_remote_plugin_zip($url, $md5);
 | 
        
           |  |  | 1177 |     }
 | 
        
           |  |  | 1178 |   | 
        
           |  |  | 1179 |     /**
 | 
        
           |  |  | 1180 |      * Extracts the saved plugin ZIP file.
 | 
        
           |  |  | 1181 |      *
 | 
        
           |  |  | 1182 |      * Returns the list of files found in the ZIP. The format of that list is
 | 
        
           |  |  | 1183 |      * array of (string)filerelpath => (bool|string) where the array value is
 | 
        
           |  |  | 1184 |      * either true or a string describing the problematic file.
 | 
        
           |  |  | 1185 |      *
 | 
        
           |  |  | 1186 |      * @see zip_packer::extract_to_pathname()
 | 
        
           |  |  | 1187 |      * @param string $zipfilepath full path to the saved ZIP file
 | 
        
           |  |  | 1188 |      * @param string $targetdir full path to the directory to extract the ZIP file to
 | 
        
           |  |  | 1189 |      * @param string $rootdir explicitly rename the root directory of the ZIP into this non-empty value
 | 
        
           |  |  | 1190 |      * @return array list of extracted files as returned by {@link zip_packer::extract_to_pathname()}
 | 
        
           |  |  | 1191 |      */
 | 
        
           |  |  | 1192 |     public function unzip_plugin_file($zipfilepath, $targetdir, $rootdir = '') {
 | 
        
           |  |  | 1193 |         return $this->get_code_manager()->unzip_plugin_file($zipfilepath, $targetdir, $rootdir);
 | 
        
           |  |  | 1194 |     }
 | 
        
           |  |  | 1195 |   | 
        
           |  |  | 1196 |     /**
 | 
        
           |  |  | 1197 |      * Detects the plugin's name from its ZIP file.
 | 
        
           |  |  | 1198 |      *
 | 
        
           |  |  | 1199 |      * Plugin ZIP packages are expected to contain a single directory and the
 | 
        
           |  |  | 1200 |      * directory name would become the plugin name once extracted to the Moodle
 | 
        
           |  |  | 1201 |      * dirroot.
 | 
        
           |  |  | 1202 |      *
 | 
        
           |  |  | 1203 |      * @param string $zipfilepath full path to the ZIP files
 | 
        
           |  |  | 1204 |      * @return string|bool false on error
 | 
        
           |  |  | 1205 |      */
 | 
        
           |  |  | 1206 |     public function get_plugin_zip_root_dir($zipfilepath) {
 | 
        
           |  |  | 1207 |         return $this->get_code_manager()->get_plugin_zip_root_dir($zipfilepath);
 | 
        
           |  |  | 1208 |     }
 | 
        
           |  |  | 1209 |   | 
        
           |  |  | 1210 |     /**
 | 
        
           |  |  | 1211 |      * Return a list of missing dependencies.
 | 
        
           |  |  | 1212 |      *
 | 
        
           |  |  | 1213 |      * This should provide the full list of plugins that should be installed to
 | 
        
           |  |  | 1214 |      * fulfill the requirements of all plugins, if possible.
 | 
        
           |  |  | 1215 |      *
 | 
        
           |  |  | 1216 |      * @param bool $availableonly return only available missing dependencies
 | 
        
           |  |  | 1217 |      * @return array of \core\update\remote_info|bool indexed by the component name
 | 
        
           |  |  | 1218 |      */
 | 
        
           |  |  | 1219 |     public function missing_dependencies($availableonly = false) {
 | 
        
           |  |  | 1220 |   | 
        
           |  |  | 1221 |         $dependencies = [];
 | 
        
           |  |  | 1222 |   | 
        
           |  |  | 1223 |         foreach ($this->get_plugins() as $plugintype => $pluginfos) {
 | 
        
           |  |  | 1224 |             foreach ($pluginfos as $pluginname => $pluginfo) {
 | 
        
           |  |  | 1225 |                 foreach ($this->resolve_requirements($pluginfo) as $reqname => $reqinfo) {
 | 
        
           |  |  | 1226 |                     if ($reqname === 'core') {
 | 
        
           |  |  | 1227 |                         continue;
 | 
        
           |  |  | 1228 |                     }
 | 
        
           |  |  | 1229 |                     if ($reqinfo->status != self::REQUIREMENT_STATUS_OK) {
 | 
        
           |  |  | 1230 |                         if ($reqinfo->availability == self::REQUIREMENT_AVAILABLE) {
 | 
        
           |  |  | 1231 |                             $remoteinfo = $this->get_remote_plugin_info($reqname, $reqinfo->reqver, false);
 | 
        
           |  |  | 1232 |   | 
        
           |  |  | 1233 |                             if (empty($dependencies[$reqname])) {
 | 
        
           |  |  | 1234 |                                 $dependencies[$reqname] = $remoteinfo;
 | 
        
           |  |  | 1235 |                             } else {
 | 
        
           |  |  | 1236 |                                 // If resolving requirements has led to two different versions of the same
 | 
        
           |  |  | 1237 |                                 // remote plugin, pick the higher version. This can happen in cases like one
 | 
        
           |  |  | 1238 |                                 // plugin requiring ANY_VERSION and another plugin requiring specific higher
 | 
        
           |  |  | 1239 |                                 // version with lower maturity of a remote plugin.
 | 
        
           |  |  | 1240 |                                 if ($remoteinfo->version->version > $dependencies[$reqname]->version->version) {
 | 
        
           |  |  | 1241 |                                     $dependencies[$reqname] = $remoteinfo;
 | 
        
           |  |  | 1242 |                                 }
 | 
        
           |  |  | 1243 |                             }
 | 
        
           |  |  | 1244 |                         } else {
 | 
        
           |  |  | 1245 |                             if (!isset($dependencies[$reqname])) {
 | 
        
           |  |  | 1246 |                                 // Unable to find a plugin fulfilling the requirements.
 | 
        
           |  |  | 1247 |                                 $dependencies[$reqname] = false;
 | 
        
           |  |  | 1248 |                             }
 | 
        
           |  |  | 1249 |                         }
 | 
        
           |  |  | 1250 |                     }
 | 
        
           |  |  | 1251 |                 }
 | 
        
           |  |  | 1252 |             }
 | 
        
           |  |  | 1253 |         }
 | 
        
           |  |  | 1254 |   | 
        
           |  |  | 1255 |         if ($availableonly) {
 | 
        
           |  |  | 1256 |             foreach ($dependencies as $component => $info) {
 | 
        
           |  |  | 1257 |                 if (empty($info) || empty($info->version)) {
 | 
        
           |  |  | 1258 |                     unset($dependencies[$component]);
 | 
        
           |  |  | 1259 |                 }
 | 
        
           |  |  | 1260 |             }
 | 
        
           |  |  | 1261 |         }
 | 
        
           |  |  | 1262 |   | 
        
           |  |  | 1263 |         return $dependencies;
 | 
        
           |  |  | 1264 |     }
 | 
        
           |  |  | 1265 |   | 
        
           |  |  | 1266 |     /**
 | 
        
           |  |  | 1267 |      * Is it possible to uninstall the given plugin?
 | 
        
           |  |  | 1268 |      *
 | 
        
           |  |  | 1269 |      * False is returned if the plugininfo subclass declares the uninstall should
 | 
        
           |  |  | 1270 |      * not be allowed via {@link \core\plugininfo\base::is_uninstall_allowed()} or if the
 | 
        
           |  |  | 1271 |      * core vetoes it (e.g. becase the plugin or some of its subplugins is required
 | 
        
           |  |  | 1272 |      * by some other installed plugin).
 | 
        
           |  |  | 1273 |      *
 | 
        
           |  |  | 1274 |      * @param string $component full frankenstyle name, e.g. mod_foobar
 | 
        
           |  |  | 1275 |      * @return bool
 | 
        
           |  |  | 1276 |      */
 | 
        
           |  |  | 1277 |     public function can_uninstall_plugin($component) {
 | 
        
           |  |  | 1278 |   | 
        
           |  |  | 1279 |         $pluginfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 1280 |   | 
        
           |  |  | 1281 |         if (is_null($pluginfo)) {
 | 
        
           |  |  | 1282 |             return false;
 | 
        
           |  |  | 1283 |         }
 | 
        
           |  |  | 1284 |   | 
        
           |  |  | 1285 |         if (!$this->common_uninstall_check($pluginfo)) {
 | 
        
           |  |  | 1286 |             return false;
 | 
        
           |  |  | 1287 |         }
 | 
        
           |  |  | 1288 |   | 
        
           |  |  | 1289 |         // Verify only if something else requires the subplugins, do not verify their common_uninstall_check()!
 | 
        
           |  |  | 1290 |         $subplugins = $this->get_subplugins_of_plugin($pluginfo->component);
 | 
        
           |  |  | 1291 |         foreach ($subplugins as $subpluginfo) {
 | 
        
           |  |  | 1292 |             // Check if there are some other plugins requiring this subplugin
 | 
        
           |  |  | 1293 |             // (but the parent and siblings).
 | 
        
           |  |  | 1294 |             foreach ($this->other_plugins_that_require($subpluginfo->component) as $requiresme) {
 | 
        
           |  |  | 1295 |                 $ismyparent = ($pluginfo->component === $requiresme);
 | 
        
           |  |  | 1296 |                 $ismysibling = in_array($requiresme, array_keys($subplugins));
 | 
        
           |  |  | 1297 |                 if (!$ismyparent && !$ismysibling) {
 | 
        
           |  |  | 1298 |                     return false;
 | 
        
           |  |  | 1299 |                 }
 | 
        
           |  |  | 1300 |             }
 | 
        
           |  |  | 1301 |         }
 | 
        
           |  |  | 1302 |   | 
        
           |  |  | 1303 |         // Check if there are some other plugins requiring this plugin
 | 
        
           |  |  | 1304 |         // (but its subplugins).
 | 
        
           |  |  | 1305 |         foreach ($this->other_plugins_that_require($pluginfo->component) as $requiresme) {
 | 
        
           |  |  | 1306 |             $ismysubplugin = in_array($requiresme, array_keys($subplugins));
 | 
        
           |  |  | 1307 |             if (!$ismysubplugin) {
 | 
        
           |  |  | 1308 |                 return false;
 | 
        
           |  |  | 1309 |             }
 | 
        
           |  |  | 1310 |         }
 | 
        
           |  |  | 1311 |   | 
        
           |  |  | 1312 |         return true;
 | 
        
           |  |  | 1313 |     }
 | 
        
           |  |  | 1314 |   | 
        
           |  |  | 1315 |     /**
 | 
        
           |  |  | 1316 |      * Perform the installation of plugins.
 | 
        
           |  |  | 1317 |      *
 | 
        
           |  |  | 1318 |      * If used for installation of remote plugins from the Moodle Plugins
 | 
        
           |  |  | 1319 |      * directory, the $plugins must be list of {@link \core\update\remote_info}
 | 
        
           |  |  | 1320 |      * object that represent installable remote plugins. The caller can use
 | 
        
           |  |  | 1321 |      * {@link self::filter_installable()} to prepare the list.
 | 
        
           |  |  | 1322 |      *
 | 
        
           |  |  | 1323 |      * If used for installation of plugins from locally available ZIP files,
 | 
        
           |  |  | 1324 |      * the $plugins should be list of objects with properties ->component and
 | 
        
           |  |  | 1325 |      * ->zipfilepath.
 | 
        
           |  |  | 1326 |      *
 | 
        
           |  |  | 1327 |      * The method uses {@link mtrace()} to produce direct output and can be
 | 
        
           |  |  | 1328 |      * used in both web and cli interfaces.
 | 
        
           |  |  | 1329 |      *
 | 
        
           |  |  | 1330 |      * @param array $plugins list of plugins
 | 
        
           |  |  | 1331 |      * @param bool $confirmed should the files be really deployed into the dirroot?
 | 
        
           |  |  | 1332 |      * @param bool $silent perform without output
 | 
        
           |  |  | 1333 |      * @return bool true on success
 | 
        
           |  |  | 1334 |      */
 | 
        
           |  |  | 1335 |     public function install_plugins(array $plugins, $confirmed, $silent) {
 | 
        
           |  |  | 1336 |         global $CFG, $OUTPUT;
 | 
        
           |  |  | 1337 |   | 
        
           |  |  | 1338 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1339 |             return false;
 | 
        
           |  |  | 1340 |         }
 | 
        
           |  |  | 1341 |   | 
        
           |  |  | 1342 |         if (empty($plugins)) {
 | 
        
           |  |  | 1343 |             return false;
 | 
        
           |  |  | 1344 |         }
 | 
        
           |  |  | 1345 |   | 
        
           |  |  | 1346 |         $ok = get_string('statusok', 'core');
 | 
        
           |  |  | 1347 |   | 
        
           |  |  | 1348 |         // Let admins know they can expect more verbose output.
 | 
        
           |  |  | 1349 |         $silent || $this->mtrace(get_string('packagesdebug', 'core_plugin'), PHP_EOL, DEBUG_NORMAL);
 | 
        
           |  |  | 1350 |   | 
        
           |  |  | 1351 |         // Download all ZIP packages if we do not have them yet.
 | 
        
           |  |  | 1352 |         $zips = [];
 | 
        
           |  |  | 1353 |         foreach ($plugins as $plugin) {
 | 
        
           |  |  | 1354 |             if ($plugin instanceof \core\update\remote_info) {
 | 
        
           |  |  | 1355 |                 $zips[$plugin->component] = $this->get_remote_plugin_zip(
 | 
        
           |  |  | 1356 |                     $plugin->version->downloadurl,
 | 
        
           |  |  | 1357 |                     $plugin->version->downloadmd5
 | 
        
           |  |  | 1358 |                 );
 | 
        
           |  |  | 1359 |                 $silent || $this->mtrace(get_string('packagesdownloading', 'core_plugin', $plugin->component), ' ... ');
 | 
        
           |  |  | 1360 |                 $silent || $this->mtrace(PHP_EOL . ' <- ' . $plugin->version->downloadurl, '', DEBUG_DEVELOPER);
 | 
        
           |  |  | 1361 |                 $silent || $this->mtrace(PHP_EOL . ' -> ' . $zips[$plugin->component], ' ... ', DEBUG_DEVELOPER);
 | 
        
           |  |  | 1362 |                 if (!$zips[$plugin->component]) {
 | 
        
           |  |  | 1363 |                     $silent || $this->mtrace(get_string('error'));
 | 
        
           |  |  | 1364 |                     return false;
 | 
        
           |  |  | 1365 |                 }
 | 
        
           |  |  | 1366 |                 $silent || $this->mtrace($ok);
 | 
        
           |  |  | 1367 |             } else {
 | 
        
           |  |  | 1368 |                 if (empty($plugin->zipfilepath)) {
 | 
        
           |  |  | 1369 |                     throw new coding_exception('Unexpected data structure provided');
 | 
        
           |  |  | 1370 |                 }
 | 
        
           |  |  | 1371 |                 $zips[$plugin->component] = $plugin->zipfilepath;
 | 
        
           |  |  | 1372 |                 $silent || $this->mtrace('ZIP ' . $plugin->zipfilepath, PHP_EOL, DEBUG_DEVELOPER);
 | 
        
           |  |  | 1373 |             }
 | 
        
           |  |  | 1374 |         }
 | 
        
           |  |  | 1375 |   | 
        
           |  |  | 1376 |         // Validate all downloaded packages.
 | 
        
           |  |  | 1377 |         foreach ($plugins as $plugin) {
 | 
        
           |  |  | 1378 |             $zipfile = $zips[$plugin->component];
 | 
        
           |  |  | 1379 |             $silent || $this->mtrace(get_string('packagesvalidating', 'core_plugin', $plugin->component), ' ... ');
 | 
        
           |  |  | 1380 |             [$plugintype, $pluginname] = core_component::normalize_component($plugin->component);
 | 
        
           |  |  | 1381 |             $tmp = make_request_directory();
 | 
        
           |  |  | 1382 |             $zipcontents = $this->unzip_plugin_file($zipfile, $tmp, $pluginname);
 | 
        
           |  |  | 1383 |             if (empty($zipcontents)) {
 | 
        
           |  |  | 1384 |                 $silent || $this->mtrace(get_string('error'));
 | 
        
           |  |  | 1385 |                 $silent || $this->mtrace('Unable to unzip ' . $zipfile, PHP_EOL, DEBUG_DEVELOPER);
 | 
        
           |  |  | 1386 |                 return false;
 | 
        
           |  |  | 1387 |             }
 | 
        
           |  |  | 1388 |   | 
        
           |  |  | 1389 |             $validator = \core\update\validator::instance($tmp, $zipcontents);
 | 
        
           |  |  | 1390 |             $validator->assert_plugin_type($plugintype);
 | 
        
           |  |  | 1391 |             $validator->assert_moodle_version($CFG->version);
 | 
        
           |  |  | 1392 |             // TODO Check for missing dependencies during validation.
 | 
        
           |  |  | 1393 |             $result = $validator->execute();
 | 
        
           |  |  | 1394 |             if (!$silent) {
 | 
        
           |  |  | 1395 |                 $result ? $this->mtrace($ok) : $this->mtrace(get_string('error'));
 | 
        
           |  |  | 1396 |                 foreach ($validator->get_messages() as $message) {
 | 
        
           |  |  | 1397 |                     if ($message->level === $validator::INFO) {
 | 
        
           |  |  | 1398 |                         // Display [OK] validation messages only if debugging mode is DEBUG_NORMAL.
 | 
        
           |  |  | 1399 |                         $level = DEBUG_NORMAL;
 | 
        
           |  |  | 1400 |                     } else if ($message->level === $validator::DEBUG) {
 | 
        
           |  |  | 1401 |                         // Display [Debug] validation messages only if debugging mode is DEBUG_ALL.
 | 
        
           |  |  | 1402 |                         $level = DEBUG_ALL;
 | 
        
           |  |  | 1403 |                     } else {
 | 
        
           |  |  | 1404 |                         // Display [Warning] and [Error] always.
 | 
        
           |  |  | 1405 |                         $level = null;
 | 
        
           |  |  | 1406 |                     }
 | 
        
           |  |  | 1407 |                     if ($message->level === $validator::WARNING && !CLI_SCRIPT) {
 | 
        
           |  |  | 1408 |                         $this->mtrace('  <strong>[' . $validator->message_level_name($message->level) . ']</strong>', ' ', $level);
 | 
        
           |  |  | 1409 |                     } else {
 | 
        
           |  |  | 1410 |                         $this->mtrace('  [' . $validator->message_level_name($message->level) . ']', ' ', $level);
 | 
        
           |  |  | 1411 |                     }
 | 
        
           |  |  | 1412 |                     $this->mtrace($validator->message_code_name($message->msgcode), ' ', $level);
 | 
        
           |  |  | 1413 |                     $info = $validator->message_code_info($message->msgcode, $message->addinfo);
 | 
        
           |  |  | 1414 |                     if ($info) {
 | 
        
           |  |  | 1415 |                         $this->mtrace('[' . s($info) . ']', ' ', $level);
 | 
        
           |  |  | 1416 |                     } else if (is_string($message->addinfo)) {
 | 
        
           |  |  | 1417 |                         $this->mtrace('[' . s($message->addinfo, true) . ']', ' ', $level);
 | 
        
           |  |  | 1418 |                     } else {
 | 
        
           |  |  | 1419 |                         $this->mtrace('[' . s(json_encode($message->addinfo, true)) . ']', ' ', $level);
 | 
        
           |  |  | 1420 |                     }
 | 
        
           |  |  | 1421 |                     if ($icon = $validator->message_help_icon($message->msgcode)) {
 | 
        
           |  |  | 1422 |                         if (CLI_SCRIPT) {
 | 
        
           |  |  | 1423 |                             $this->mtrace(PHP_EOL . '  ^^^ ' . get_string('help') . ': ' .
 | 
        
           |  |  | 1424 |                                 get_string($icon->identifier . '_help', $icon->component), '', $level);
 | 
        
           |  |  | 1425 |                         } else {
 | 
        
           |  |  | 1426 |                             $this->mtrace($OUTPUT->render($icon), ' ', $level);
 | 
        
           |  |  | 1427 |                         }
 | 
        
           |  |  | 1428 |                     }
 | 
        
           |  |  | 1429 |                     $this->mtrace(PHP_EOL, '', $level);
 | 
        
           |  |  | 1430 |                 }
 | 
        
           |  |  | 1431 |             }
 | 
        
           |  |  | 1432 |             if (!$result) {
 | 
        
           |  |  | 1433 |                 $silent || $this->mtrace(get_string('packagesvalidatingfailed', 'core_plugin'));
 | 
        
           |  |  | 1434 |                 return false;
 | 
        
           |  |  | 1435 |             }
 | 
        
           |  |  | 1436 |         }
 | 
        
           |  |  | 1437 |         $silent || $this->mtrace(PHP_EOL . get_string('packagesvalidatingok', 'core_plugin'));
 | 
        
           |  |  | 1438 |   | 
        
           |  |  | 1439 |         if (!$confirmed) {
 | 
        
           |  |  | 1440 |             return true;
 | 
        
           |  |  | 1441 |         }
 | 
        
           |  |  | 1442 |   | 
        
           |  |  | 1443 |         // Extract all ZIP packs do the dirroot.
 | 
        
           |  |  | 1444 |         foreach ($plugins as $plugin) {
 | 
        
           |  |  | 1445 |             $silent || $this->mtrace(get_string('packagesextracting', 'core_plugin', $plugin->component), ' ... ');
 | 
        
           |  |  | 1446 |             $zipfile = $zips[$plugin->component];
 | 
        
           |  |  | 1447 |             [$plugintype, $pluginname] = core_component::normalize_component($plugin->component);
 | 
        
           |  |  | 1448 |             $target = $this->get_plugintype_root($plugintype);
 | 
        
           |  |  | 1449 |             if (file_exists($target . '/' . $pluginname)) {
 | 
        
           |  |  | 1450 |                 $this->remove_plugin_folder($this->get_plugin_info($plugin->component));
 | 
        
           |  |  | 1451 |             }
 | 
        
           |  |  | 1452 |             if (!$this->unzip_plugin_file($zipfile, $target, $pluginname)) {
 | 
        
           |  |  | 1453 |                 $silent || $this->mtrace(get_string('error'));
 | 
        
           |  |  | 1454 |                 $silent || $this->mtrace('Unable to unzip ' . $zipfile, PHP_EOL, DEBUG_DEVELOPER);
 | 
        
           |  |  | 1455 |                 if (function_exists('opcache_reset')) {
 | 
        
           |  |  | 1456 |                     opcache_reset();
 | 
        
           |  |  | 1457 |                 }
 | 
        
           |  |  | 1458 |                 return false;
 | 
        
           |  |  | 1459 |             }
 | 
        
           |  |  | 1460 |             $silent || $this->mtrace($ok);
 | 
        
           |  |  | 1461 |         }
 | 
        
           |  |  | 1462 |         if (function_exists('opcache_reset')) {
 | 
        
           |  |  | 1463 |             opcache_reset();
 | 
        
           |  |  | 1464 |         }
 | 
        
           |  |  | 1465 |   | 
        
           |  |  | 1466 |         return true;
 | 
        
           |  |  | 1467 |     }
 | 
        
           |  |  | 1468 |   | 
        
           |  |  | 1469 |     /**
 | 
        
           |  |  | 1470 |      * Outputs the given message via {@link mtrace()}.
 | 
        
           |  |  | 1471 |      *
 | 
        
           |  |  | 1472 |      * If $debug is provided, then the message is displayed only at the given
 | 
        
           |  |  | 1473 |      * debugging level (e.g. DEBUG_DEVELOPER to display the message only if the
 | 
        
           |  |  | 1474 |      * site has developer debugging level selected).
 | 
        
           |  |  | 1475 |      *
 | 
        
           |  |  | 1476 |      * @param string $msg message
 | 
        
           |  |  | 1477 |      * @param string $eol end of line
 | 
        
           |  |  | 1478 |      * @param null|int $debug null to display always, int only on given debug level
 | 
        
           |  |  | 1479 |      */
 | 
        
           |  |  | 1480 |     protected function mtrace($msg, $eol = PHP_EOL, $debug = null) {
 | 
        
           |  |  | 1481 |         global $CFG;
 | 
        
           |  |  | 1482 |   | 
        
           |  |  | 1483 |         if ($debug !== null && !debugging(null, $debug)) {
 | 
        
           |  |  | 1484 |             return;
 | 
        
           |  |  | 1485 |         }
 | 
        
           |  |  | 1486 |   | 
        
           |  |  | 1487 |         mtrace($msg, $eol);
 | 
        
           |  |  | 1488 |     }
 | 
        
           |  |  | 1489 |   | 
        
           |  |  | 1490 |     /**
 | 
        
           |  |  | 1491 |      * Returns uninstall URL if exists.
 | 
        
           |  |  | 1492 |      *
 | 
        
           |  |  | 1493 |      * @param string $component
 | 
        
           |  |  | 1494 |      * @param string $return either 'overview' or 'manage'
 | 
        
           |  |  | 1495 |      * @return null|moodle_url uninstall URL, null if uninstall not supported
 | 
        
           |  |  | 1496 |      */
 | 
        
           |  |  | 1497 |     public function get_uninstall_url($component, $return = 'overview') {
 | 
        
           |  |  | 1498 |         if (!$this->can_uninstall_plugin($component)) {
 | 
        
           |  |  | 1499 |             return null;
 | 
        
           |  |  | 1500 |         }
 | 
        
           |  |  | 1501 |   | 
        
           |  |  | 1502 |         $pluginfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 1503 |   | 
        
           |  |  | 1504 |         if (is_null($pluginfo)) {
 | 
        
           |  |  | 1505 |             return null;
 | 
        
           |  |  | 1506 |         }
 | 
        
           |  |  | 1507 |   | 
        
           |  |  | 1508 |         if (method_exists($pluginfo, 'get_uninstall_url')) {
 | 
        
           |  |  | 1509 |             debugging(
 | 
        
           |  |  | 1510 |                 'plugininfo method get_uninstall_url() is deprecated, all plugins should be uninstalled via standard URL only.',
 | 
        
           |  |  | 1511 |                 DEBUG_DEVELOPER
 | 
        
           |  |  | 1512 |             );
 | 
        
           |  |  | 1513 |             return $pluginfo->get_uninstall_url($return);
 | 
        
           |  |  | 1514 |         }
 | 
        
           |  |  | 1515 |   | 
        
           |  |  | 1516 |         return $pluginfo->get_default_uninstall_url($return);
 | 
        
           |  |  | 1517 |     }
 | 
        
           |  |  | 1518 |   | 
        
           |  |  | 1519 |     /**
 | 
        
           |  |  | 1520 |      * Uninstall the given plugin.
 | 
        
           |  |  | 1521 |      *
 | 
        
           |  |  | 1522 |      * Automatically cleans-up all remaining configuration data, log records, events,
 | 
        
           |  |  | 1523 |      * files from the file pool etc.
 | 
        
           |  |  | 1524 |      *
 | 
        
           |  |  | 1525 |      * In the future, the functionality of {@link uninstall_plugin()} function may be moved
 | 
        
           |  |  | 1526 |      * into this method and all the code should be refactored to use it. At the moment, we
 | 
        
           |  |  | 1527 |      * mimic this future behaviour by wrapping that function call.
 | 
        
           |  |  | 1528 |      *
 | 
        
           |  |  | 1529 |      * @param string $component
 | 
        
           |  |  | 1530 |      * @param progress_trace $progress traces the process
 | 
        
           |  |  | 1531 |      * @return bool true on success, false on errors/problems
 | 
        
           |  |  | 1532 |      */
 | 
        
           |  |  | 1533 |     public function uninstall_plugin($component, progress_trace $progress) {
 | 
        
           |  |  | 1534 |   | 
        
           |  |  | 1535 |         $pluginfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 1536 |   | 
        
           |  |  | 1537 |         if (is_null($pluginfo)) {
 | 
        
           |  |  | 1538 |             return false;
 | 
        
           |  |  | 1539 |         }
 | 
        
           |  |  | 1540 |   | 
        
           |  |  | 1541 |         // Give the pluginfo class a chance to execute some steps.
 | 
        
           |  |  | 1542 |         $result = $pluginfo->uninstall($progress);
 | 
        
           |  |  | 1543 |         if (!$result) {
 | 
        
           |  |  | 1544 |             return false;
 | 
        
           |  |  | 1545 |         }
 | 
        
           |  |  | 1546 |   | 
        
           |  |  | 1547 |         // Call the legacy core function to uninstall the plugin.
 | 
        
           |  |  | 1548 |         ob_start();
 | 
        
           |  |  | 1549 |         uninstall_plugin($pluginfo->type, $pluginfo->name);
 | 
        
           |  |  | 1550 |         $progress->output(ob_get_clean());
 | 
        
           |  |  | 1551 |   | 
        
           |  |  | 1552 |         return true;
 | 
        
           |  |  | 1553 |     }
 | 
        
           |  |  | 1554 |   | 
        
           |  |  | 1555 |     /**
 | 
        
           |  |  | 1556 |      * Checks if there are some plugins with a known available update
 | 
        
           |  |  | 1557 |      *
 | 
        
           |  |  | 1558 |      * @return bool true if there is at least one available update
 | 
        
           |  |  | 1559 |      */
 | 
        
           |  |  | 1560 |     public function some_plugins_updatable() {
 | 
        
           |  |  | 1561 |         foreach ($this->get_plugins() as $type => $plugins) {
 | 
        
           |  |  | 1562 |             foreach ($plugins as $plugin) {
 | 
        
           |  |  | 1563 |                 if ($plugin->available_updates()) {
 | 
        
           |  |  | 1564 |                     return true;
 | 
        
           |  |  | 1565 |                 }
 | 
        
           |  |  | 1566 |             }
 | 
        
           |  |  | 1567 |         }
 | 
        
           |  |  | 1568 |   | 
        
           |  |  | 1569 |         return false;
 | 
        
           |  |  | 1570 |     }
 | 
        
           |  |  | 1571 |   | 
        
           |  |  | 1572 |     /**
 | 
        
           |  |  | 1573 |      * Returns list of available updates for the given component.
 | 
        
           |  |  | 1574 |      *
 | 
        
           |  |  | 1575 |      * This method should be considered as internal API and is supposed to be
 | 
        
           |  |  | 1576 |      * called by {@link \core\plugininfo\base::available_updates()} only
 | 
        
           |  |  | 1577 |      * to lazy load the data once they are first requested.
 | 
        
           |  |  | 1578 |      *
 | 
        
           |  |  | 1579 |      * @param string $component frankenstyle name of the plugin
 | 
        
           |  |  | 1580 |      * @return null|array array of \core\update\info objects or null
 | 
        
           |  |  | 1581 |      */
 | 
        
           |  |  | 1582 |     public function load_available_updates_for_plugin($component) {
 | 
        
           |  |  | 1583 |         global $CFG;
 | 
        
           |  |  | 1584 |   | 
        
           |  |  | 1585 |         $provider = \core\update\checker::instance();
 | 
        
           |  |  | 1586 |   | 
        
           |  |  | 1587 |         if (!$provider->enabled() || $component === '' || during_initial_install()) {
 | 
        
           |  |  | 1588 |             return null;
 | 
        
           |  |  | 1589 |         }
 | 
        
           |  |  | 1590 |   | 
        
           |  |  | 1591 |         if (isset($CFG->updateminmaturity)) {
 | 
        
           |  |  | 1592 |             $minmaturity = $CFG->updateminmaturity;
 | 
        
           |  |  | 1593 |         } else {
 | 
        
           |  |  | 1594 |             // This can happen during the very first upgrade to 2.3.
 | 
        
           |  |  | 1595 |             $minmaturity = MATURITY_STABLE;
 | 
        
           |  |  | 1596 |         }
 | 
        
           |  |  | 1597 |   | 
        
           |  |  | 1598 |         return $provider->get_update_info($component, ['minmaturity' => $minmaturity]);
 | 
        
           |  |  | 1599 |     }
 | 
        
           |  |  | 1600 |   | 
        
           |  |  | 1601 |     /**
 | 
        
           |  |  | 1602 |      * Returns a list of all available updates to be installed.
 | 
        
           |  |  | 1603 |      *
 | 
        
           |  |  | 1604 |      * This is used when "update all plugins" action is performed at the
 | 
        
           |  |  | 1605 |      * administration UI screen.
 | 
        
           |  |  | 1606 |      *
 | 
        
           |  |  | 1607 |      * Returns array of remote info objects indexed by the plugin
 | 
        
           |  |  | 1608 |      * component. If there are multiple updates available (typically a mix of
 | 
        
           |  |  | 1609 |      * stable and non-stable ones), we pick the most mature most recent one.
 | 
        
           |  |  | 1610 |      *
 | 
        
           |  |  | 1611 |      * Plugins without explicit maturity are considered more mature than
 | 
        
           |  |  | 1612 |      * release candidates but less mature than explicit stable (this should be
 | 
        
           |  |  | 1613 |      * pretty rare case).
 | 
        
           |  |  | 1614 |      *
 | 
        
           |  |  | 1615 |      * @return array (string)component => (\core\update\remote_info)remoteinfo
 | 
        
           |  |  | 1616 |      */
 | 
        
           |  |  | 1617 |     public function available_updates() {
 | 
        
           |  |  | 1618 |   | 
        
           |  |  | 1619 |         $updates = [];
 | 
        
           |  |  | 1620 |   | 
        
           |  |  | 1621 |         foreach ($this->get_plugins() as $type => $plugins) {
 | 
        
           |  |  | 1622 |             foreach ($plugins as $plugin) {
 | 
        
           |  |  | 1623 |                 $availableupdates = $plugin->available_updates();
 | 
        
           |  |  | 1624 |                 if (empty($availableupdates)) {
 | 
        
           |  |  | 1625 |                     continue;
 | 
        
           |  |  | 1626 |                 }
 | 
        
           |  |  | 1627 |                 foreach ($availableupdates as $update) {
 | 
        
           |  |  | 1628 |                     if (empty($updates[$plugin->component])) {
 | 
        
           |  |  | 1629 |                         $updates[$plugin->component] = $update;
 | 
        
           |  |  | 1630 |                         continue;
 | 
        
           |  |  | 1631 |                     }
 | 
        
           |  |  | 1632 |                     $maturitycurrent = $updates[$plugin->component]->maturity;
 | 
        
           |  |  | 1633 |                     if (empty($maturitycurrent)) {
 | 
        
           |  |  | 1634 |                         $maturitycurrent = MATURITY_STABLE - 25;
 | 
        
           |  |  | 1635 |                     }
 | 
        
           |  |  | 1636 |                     $maturityremote = $update->maturity;
 | 
        
           |  |  | 1637 |                     if (empty($maturityremote)) {
 | 
        
           |  |  | 1638 |                         $maturityremote = MATURITY_STABLE - 25;
 | 
        
           |  |  | 1639 |                     }
 | 
        
           |  |  | 1640 |                     if ($maturityremote < $maturitycurrent) {
 | 
        
           |  |  | 1641 |                         continue;
 | 
        
           |  |  | 1642 |                     }
 | 
        
           |  |  | 1643 |                     if ($maturityremote > $maturitycurrent) {
 | 
        
           |  |  | 1644 |                         $updates[$plugin->component] = $update;
 | 
        
           |  |  | 1645 |                         continue;
 | 
        
           |  |  | 1646 |                     }
 | 
        
           |  |  | 1647 |                     if ($update->version > $updates[$plugin->component]->version) {
 | 
        
           |  |  | 1648 |                         $updates[$plugin->component] = $update;
 | 
        
           |  |  | 1649 |                         continue;
 | 
        
           |  |  | 1650 |                     }
 | 
        
           |  |  | 1651 |                 }
 | 
        
           |  |  | 1652 |             }
 | 
        
           |  |  | 1653 |         }
 | 
        
           |  |  | 1654 |   | 
        
           |  |  | 1655 |         foreach ($updates as $component => $update) {
 | 
        
           |  |  | 1656 |             $remoteinfo = $this->get_remote_plugin_info($component, $update->version, true);
 | 
        
           |  |  | 1657 |             if (empty($remoteinfo) || empty($remoteinfo->version)) {
 | 
        
           |  |  | 1658 |                 unset($updates[$component]);
 | 
        
           |  |  | 1659 |             } else {
 | 
        
           |  |  | 1660 |                 $updates[$component] = $remoteinfo;
 | 
        
           |  |  | 1661 |             }
 | 
        
           |  |  | 1662 |         }
 | 
        
           |  |  | 1663 |   | 
        
           |  |  | 1664 |         return $updates;
 | 
        
           |  |  | 1665 |     }
 | 
        
           |  |  | 1666 |   | 
        
           |  |  | 1667 |     /**
 | 
        
           |  |  | 1668 |      * Check to see if the given plugin folder can be removed by the web server process.
 | 
        
           |  |  | 1669 |      *
 | 
        
           |  |  | 1670 |      * @param string $component full frankenstyle component
 | 
        
           |  |  | 1671 |      * @return bool
 | 
        
           |  |  | 1672 |      */
 | 
        
           |  |  | 1673 |     public function is_plugin_folder_removable($component) {
 | 
        
           |  |  | 1674 |   | 
        
           |  |  | 1675 |         $pluginfo = $this->get_plugin_info($component);
 | 
        
           |  |  | 1676 |   | 
        
           |  |  | 1677 |         if (is_null($pluginfo)) {
 | 
        
           |  |  | 1678 |             return false;
 | 
        
           |  |  | 1679 |         }
 | 
        
           |  |  | 1680 |   | 
        
           |  |  | 1681 |         // To be able to remove the plugin folder, its parent must be writable, too.
 | 
        
           |  |  | 1682 |         if (!isset($pluginfo->rootdir) || !is_writable(dirname($pluginfo->rootdir))) {
 | 
        
           |  |  | 1683 |             return false;
 | 
        
           |  |  | 1684 |         }
 | 
        
           |  |  | 1685 |   | 
        
           |  |  | 1686 |         // Check that the folder and all its content is writable (thence removable).
 | 
        
           |  |  | 1687 |         return $this->is_directory_removable($pluginfo->rootdir);
 | 
        
           |  |  | 1688 |     }
 | 
        
           |  |  | 1689 |   | 
        
           |  |  | 1690 |     /**
 | 
        
           |  |  | 1691 |      * Is it possible to create a new plugin directory for the given plugin type?
 | 
        
           |  |  | 1692 |      *
 | 
        
           |  |  | 1693 |      * @throws coding_exception for invalid plugin types or non-existing plugin type locations
 | 
        
           |  |  | 1694 |      * @param string $plugintype
 | 
        
           |  |  | 1695 |      * @return boolean
 | 
        
           |  |  | 1696 |      */
 | 
        
           |  |  | 1697 |     public function is_plugintype_writable($plugintype) {
 | 
        
           |  |  | 1698 |   | 
        
           |  |  | 1699 |         $plugintypepath = $this->get_plugintype_root($plugintype);
 | 
        
           |  |  | 1700 |   | 
        
           |  |  | 1701 |         if (is_null($plugintypepath)) {
 | 
        
           |  |  | 1702 |             throw new coding_exception('Unknown plugin type: ' . $plugintype);
 | 
        
           |  |  | 1703 |         }
 | 
        
           |  |  | 1704 |   | 
        
           |  |  | 1705 |         if ($plugintypepath === false) {
 | 
        
           |  |  | 1706 |             throw new coding_exception('Plugin type location does not exist: ' . $plugintype);
 | 
        
           |  |  | 1707 |         }
 | 
        
           |  |  | 1708 |   | 
        
           |  |  | 1709 |         return is_writable($plugintypepath);
 | 
        
           |  |  | 1710 |     }
 | 
        
           |  |  | 1711 |   | 
        
           |  |  | 1712 |     /**
 | 
        
           |  |  | 1713 |      * Returns the full path of the root of the given plugin type
 | 
        
           |  |  | 1714 |      *
 | 
        
           |  |  | 1715 |      * Null is returned if the plugin type is not known. False is returned if
 | 
        
           |  |  | 1716 |      * the plugin type root is expected but not found. Otherwise, string is
 | 
        
           |  |  | 1717 |      * returned.
 | 
        
           |  |  | 1718 |      *
 | 
        
           |  |  | 1719 |      * @param string $plugintype
 | 
        
           |  |  | 1720 |      * @return string|bool|null
 | 
        
           |  |  | 1721 |      */
 | 
        
           |  |  | 1722 |     public function get_plugintype_root($plugintype) {
 | 
        
           |  |  | 1723 |   | 
        
           |  |  | 1724 |         $plugintypepath = null;
 | 
        
           | 1441 | ariadna | 1725 |         $allplugintypes = core_component::get_all_plugin_types();
 | 
        
           |  |  | 1726 |         foreach ($allplugintypes as $type => $fullpath) {
 | 
        
           | 1 | efrain | 1727 |             if ($type === $plugintype) {
 | 
        
           |  |  | 1728 |                 $plugintypepath = $fullpath;
 | 
        
           |  |  | 1729 |                 break;
 | 
        
           |  |  | 1730 |             }
 | 
        
           |  |  | 1731 |         }
 | 
        
           |  |  | 1732 |         if (is_null($plugintypepath)) {
 | 
        
           |  |  | 1733 |             return null;
 | 
        
           |  |  | 1734 |         }
 | 
        
           |  |  | 1735 |         if (!is_dir($plugintypepath)) {
 | 
        
           |  |  | 1736 |             return false;
 | 
        
           |  |  | 1737 |         }
 | 
        
           |  |  | 1738 |   | 
        
           |  |  | 1739 |         return $plugintypepath;
 | 
        
           |  |  | 1740 |     }
 | 
        
           |  |  | 1741 |   | 
        
           |  |  | 1742 |     /**
 | 
        
           |  |  | 1743 |      * Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
 | 
        
           |  |  | 1744 |      * but are not anymore and are deleted during upgrades.
 | 
        
           |  |  | 1745 |      *
 | 
        
           |  |  | 1746 |      * The main purpose of this list is to hide missing plugins during upgrade.
 | 
        
           |  |  | 1747 |      *
 | 
        
           |  |  | 1748 |      * @param string $type plugin type
 | 
        
           |  |  | 1749 |      * @param string $name plugin name
 | 
        
           |  |  | 1750 |      * @return bool
 | 
        
           |  |  | 1751 |      */
 | 
        
           |  |  | 1752 |     public static function is_deleted_standard_plugin(
 | 
        
           |  |  | 1753 |         string $type,
 | 
        
           |  |  | 1754 |         string $name,
 | 
        
           |  |  | 1755 |     ): bool {
 | 
        
           |  |  | 1756 |         // Do not include plugins that were removed during upgrades to versions that are
 | 
        
           |  |  | 1757 |         // not supported as source versions for upgrade any more. For example, at MOODLE_23_STABLE
 | 
        
           |  |  | 1758 |         // branch, listed should be no plugins that were removed at 1.9.x - 2.1.x versions as
 | 
        
           |  |  | 1759 |         // Moodle 2.3 supports upgrades from 2.2.x only.
 | 
        
           |  |  | 1760 |         $plugins = static::load_standard_plugins()->deleted;
 | 
        
           |  |  | 1761 |   | 
        
           |  |  | 1762 |         if (property_exists($plugins, $type)) {
 | 
        
           |  |  | 1763 |             return in_array($name, $plugins->$type);
 | 
        
           |  |  | 1764 |         }
 | 
        
           |  |  | 1765 |   | 
        
           |  |  | 1766 |         return false;
 | 
        
           |  |  | 1767 |     }
 | 
        
           |  |  | 1768 |   | 
        
           |  |  | 1769 |     /**
 | 
        
           |  |  | 1770 |      * Fetches a list of all plugins shipped in the standard Moodle distribution.
 | 
        
           |  |  | 1771 |      *
 | 
        
           |  |  | 1772 |      * If a type is specified but does not exist, a false value is returned.
 | 
        
           |  |  | 1773 |      * Otherwise an array of the plugins of the specified type is returned.
 | 
        
           |  |  | 1774 |      *
 | 
        
           |  |  | 1775 |      * @param null|string $type
 | 
        
           |  |  | 1776 |      * @return false|array array of standard plugins or false if the type is unknown
 | 
        
           |  |  | 1777 |      */
 | 
        
           |  |  | 1778 |     public static function standard_plugins_list(string $type): array|false {
 | 
        
           |  |  | 1779 |         $plugins = static::load_standard_plugins()->standard;
 | 
        
           |  |  | 1780 |   | 
        
           |  |  | 1781 |         if (property_exists($plugins, $type)) {
 | 
        
           |  |  | 1782 |             return (array) $plugins->$type;
 | 
        
           |  |  | 1783 |         } else {
 | 
        
           |  |  | 1784 |             return false;
 | 
        
           |  |  | 1785 |         }
 | 
        
           |  |  | 1786 |     }
 | 
        
           |  |  | 1787 |   | 
        
           |  |  | 1788 |     /**
 | 
        
           |  |  | 1789 |      * Get all standard plugins by their component name.
 | 
        
           |  |  | 1790 |      *
 | 
        
           |  |  | 1791 |      * @return array
 | 
        
           |  |  | 1792 |      */
 | 
        
           |  |  | 1793 |     public static function get_standard_plugins(): array {
 | 
        
           |  |  | 1794 |         $plugins = static::load_standard_plugins()->standard;
 | 
        
           |  |  | 1795 |   | 
        
           |  |  | 1796 |         $result = [];
 | 
        
           |  |  | 1797 |         foreach ($plugins as $type => $list) {
 | 
        
           |  |  | 1798 |             foreach ($list as $plugin) {
 | 
        
           |  |  | 1799 |                 $result[] = "{$type}_{$plugin}";
 | 
        
           |  |  | 1800 |             }
 | 
        
           |  |  | 1801 |         }
 | 
        
           |  |  | 1802 |   | 
        
           |  |  | 1803 |         return $result;
 | 
        
           |  |  | 1804 |     }
 | 
        
           |  |  | 1805 |   | 
        
           |  |  | 1806 |     /**
 | 
        
           |  |  | 1807 |      * Get all deleted standard plugins by their component name.
 | 
        
           |  |  | 1808 |      *
 | 
        
           |  |  | 1809 |      * @return array
 | 
        
           |  |  | 1810 |      */
 | 
        
           |  |  | 1811 |     public static function get_deleted_plugins(): array {
 | 
        
           |  |  | 1812 |         $plugins = static::load_standard_plugins()->deleted;
 | 
        
           |  |  | 1813 |   | 
        
           |  |  | 1814 |         $result = [];
 | 
        
           |  |  | 1815 |         foreach ($plugins as $type => $list) {
 | 
        
           |  |  | 1816 |             foreach ($list as $plugin) {
 | 
        
           |  |  | 1817 |                 $result[] = "{$type}_{$plugin}";
 | 
        
           |  |  | 1818 |             }
 | 
        
           |  |  | 1819 |         }
 | 
        
           |  |  | 1820 |   | 
        
           |  |  | 1821 |         return $result;
 | 
        
           |  |  | 1822 |     }
 | 
        
           |  |  | 1823 |   | 
        
           |  |  | 1824 |     /**
 | 
        
           |  |  | 1825 |      * Remove the current plugin code from the dirroot.
 | 
        
           |  |  | 1826 |      *
 | 
        
           |  |  | 1827 |      * If removing the currently installed version (which happens during
 | 
        
           |  |  | 1828 |      * updates), we archive the code so that the upgrade can be cancelled.
 | 
        
           |  |  | 1829 |      *
 | 
        
           |  |  | 1830 |      * To prevent accidental data-loss, we also archive the existing plugin
 | 
        
           |  |  | 1831 |      * code if cancelling installation of it, so that the developer does not
 | 
        
           |  |  | 1832 |      * loose the only version of their work-in-progress.
 | 
        
           |  |  | 1833 |      *
 | 
        
           |  |  | 1834 |      * @param \core\plugininfo\base $plugin
 | 
        
           |  |  | 1835 |      */
 | 
        
           |  |  | 1836 |     public function remove_plugin_folder(\core\plugininfo\base $plugin) {
 | 
        
           |  |  | 1837 |   | 
        
           |  |  | 1838 |         if (!$this->is_plugin_folder_removable($plugin->component)) {
 | 
        
           |  |  | 1839 |             throw new moodle_exception(
 | 
        
           |  |  | 1840 |                 'err_removing_unremovable_folder',
 | 
        
           |  |  | 1841 |                 'core_plugin',
 | 
        
           |  |  | 1842 |                 '',
 | 
        
           |  |  | 1843 |                 ['plugin' => $plugin->component, 'rootdir' => $plugin->rootdir],
 | 
        
           |  |  | 1844 |                 'plugin root folder is not removable as expected'
 | 
        
           |  |  | 1845 |             );
 | 
        
           |  |  | 1846 |         }
 | 
        
           |  |  | 1847 |   | 
        
           |  |  | 1848 |         if ($plugin->get_status() === self::PLUGIN_STATUS_UPTODATE || $plugin->get_status() === self::PLUGIN_STATUS_NEW) {
 | 
        
           |  |  | 1849 |             $this->archive_plugin_version($plugin);
 | 
        
           |  |  | 1850 |         }
 | 
        
           |  |  | 1851 |   | 
        
           |  |  | 1852 |         remove_dir($plugin->rootdir);
 | 
        
           |  |  | 1853 |         clearstatcache();
 | 
        
           |  |  | 1854 |         if (function_exists('opcache_reset')) {
 | 
        
           |  |  | 1855 |             opcache_reset();
 | 
        
           |  |  | 1856 |         }
 | 
        
           |  |  | 1857 |     }
 | 
        
           |  |  | 1858 |   | 
        
           |  |  | 1859 |     /**
 | 
        
           |  |  | 1860 |      * Can the installation of the new plugin be cancelled?
 | 
        
           |  |  | 1861 |      *
 | 
        
           |  |  | 1862 |      * Subplugins can be cancelled only via their parent plugin, not separately
 | 
        
           |  |  | 1863 |      * (they are considered as implicit requirements if distributed together
 | 
        
           |  |  | 1864 |      * with the main package).
 | 
        
           |  |  | 1865 |      *
 | 
        
           |  |  | 1866 |      * @param \core\plugininfo\base $plugin
 | 
        
           |  |  | 1867 |      * @return bool
 | 
        
           |  |  | 1868 |      */
 | 
        
           |  |  | 1869 |     public function can_cancel_plugin_installation(\core\plugininfo\base $plugin) {
 | 
        
           |  |  | 1870 |         global $CFG;
 | 
        
           |  |  | 1871 |   | 
        
           |  |  | 1872 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1873 |             return false;
 | 
        
           |  |  | 1874 |         }
 | 
        
           |  |  | 1875 |   | 
        
           |  |  | 1876 |         if (
 | 
        
           |  |  | 1877 |             empty($plugin)
 | 
        
           |  |  | 1878 |             || $plugin->is_standard()
 | 
        
           |  |  | 1879 |             || $plugin->is_subplugin()
 | 
        
           |  |  | 1880 |             || !$this->is_plugin_folder_removable($plugin->component)
 | 
        
           |  |  | 1881 |         ) {
 | 
        
           |  |  | 1882 |             return false;
 | 
        
           |  |  | 1883 |         }
 | 
        
           |  |  | 1884 |   | 
        
           |  |  | 1885 |         if ($plugin->get_status() === self::PLUGIN_STATUS_NEW) {
 | 
        
           |  |  | 1886 |             return true;
 | 
        
           |  |  | 1887 |         }
 | 
        
           |  |  | 1888 |   | 
        
           |  |  | 1889 |         return false;
 | 
        
           |  |  | 1890 |     }
 | 
        
           |  |  | 1891 |   | 
        
           |  |  | 1892 |     /**
 | 
        
           |  |  | 1893 |      * Can the upgrade of the existing plugin be cancelled?
 | 
        
           |  |  | 1894 |      *
 | 
        
           |  |  | 1895 |      * Subplugins can be cancelled only via their parent plugin, not separately
 | 
        
           |  |  | 1896 |      * (they are considered as implicit requirements if distributed together
 | 
        
           |  |  | 1897 |      * with the main package).
 | 
        
           |  |  | 1898 |      *
 | 
        
           |  |  | 1899 |      * @param \core\plugininfo\base $plugin
 | 
        
           |  |  | 1900 |      * @return bool
 | 
        
           |  |  | 1901 |      */
 | 
        
           |  |  | 1902 |     public function can_cancel_plugin_upgrade(\core\plugininfo\base $plugin) {
 | 
        
           |  |  | 1903 |         global $CFG;
 | 
        
           |  |  | 1904 |   | 
        
           |  |  | 1905 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1906 |             // Cancelling the plugin upgrade is actually installation of the
 | 
        
           |  |  | 1907 |             // previously archived version.
 | 
        
           |  |  | 1908 |             return false;
 | 
        
           |  |  | 1909 |         }
 | 
        
           |  |  | 1910 |   | 
        
           |  |  | 1911 |         if (
 | 
        
           |  |  | 1912 |             empty($plugin)
 | 
        
           |  |  | 1913 |             || $plugin->is_standard()
 | 
        
           |  |  | 1914 |             || $plugin->is_subplugin()
 | 
        
           |  |  | 1915 |             || !$this->is_plugin_folder_removable($plugin->component)
 | 
        
           |  |  | 1916 |         ) {
 | 
        
           |  |  | 1917 |             return false;
 | 
        
           |  |  | 1918 |         }
 | 
        
           |  |  | 1919 |   | 
        
           |  |  | 1920 |         if ($plugin->get_status() === self::PLUGIN_STATUS_UPGRADE) {
 | 
        
           |  |  | 1921 |             if ($this->get_code_manager()->get_archived_plugin_version($plugin->component, $plugin->versiondb)) {
 | 
        
           |  |  | 1922 |                 return true;
 | 
        
           |  |  | 1923 |             }
 | 
        
           |  |  | 1924 |         }
 | 
        
           |  |  | 1925 |   | 
        
           |  |  | 1926 |         return false;
 | 
        
           |  |  | 1927 |     }
 | 
        
           |  |  | 1928 |   | 
        
           |  |  | 1929 |     /**
 | 
        
           |  |  | 1930 |      * Removes the plugin code directory if it is not installed yet.
 | 
        
           |  |  | 1931 |      *
 | 
        
           |  |  | 1932 |      * This is intended for the plugins check screen to give the admin a chance
 | 
        
           |  |  | 1933 |      * to cancel the installation of just unzipped plugin before the database
 | 
        
           |  |  | 1934 |      * upgrade happens.
 | 
        
           |  |  | 1935 |      *
 | 
        
           |  |  | 1936 |      * @param string $component
 | 
        
           |  |  | 1937 |      */
 | 
        
           |  |  | 1938 |     public function cancel_plugin_installation($component) {
 | 
        
           |  |  | 1939 |         global $CFG;
 | 
        
           |  |  | 1940 |   | 
        
           |  |  | 1941 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1942 |             return false;
 | 
        
           |  |  | 1943 |         }
 | 
        
           |  |  | 1944 |   | 
        
           |  |  | 1945 |         $plugin = $this->get_plugin_info($component);
 | 
        
           |  |  | 1946 |   | 
        
           |  |  | 1947 |         if ($this->can_cancel_plugin_installation($plugin)) {
 | 
        
           |  |  | 1948 |             $this->remove_plugin_folder($plugin);
 | 
        
           |  |  | 1949 |         }
 | 
        
           |  |  | 1950 |   | 
        
           |  |  | 1951 |         return false;
 | 
        
           |  |  | 1952 |     }
 | 
        
           |  |  | 1953 |   | 
        
           |  |  | 1954 |     /**
 | 
        
           |  |  | 1955 |      * Returns plugins, the installation of which can be cancelled.
 | 
        
           |  |  | 1956 |      *
 | 
        
           |  |  | 1957 |      * @return array [(string)component] => (\core\plugininfo\base)plugin
 | 
        
           |  |  | 1958 |      */
 | 
        
           |  |  | 1959 |     public function list_cancellable_installations() {
 | 
        
           |  |  | 1960 |         global $CFG;
 | 
        
           |  |  | 1961 |   | 
        
           |  |  | 1962 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1963 |             return [];
 | 
        
           |  |  | 1964 |         }
 | 
        
           |  |  | 1965 |   | 
        
           |  |  | 1966 |         $cancellable = [];
 | 
        
           |  |  | 1967 |         foreach ($this->get_plugins() as $type => $plugins) {
 | 
        
           |  |  | 1968 |             foreach ($plugins as $plugin) {
 | 
        
           |  |  | 1969 |                 if ($this->can_cancel_plugin_installation($plugin)) {
 | 
        
           |  |  | 1970 |                     $cancellable[$plugin->component] = $plugin;
 | 
        
           |  |  | 1971 |                 }
 | 
        
           |  |  | 1972 |             }
 | 
        
           |  |  | 1973 |         }
 | 
        
           |  |  | 1974 |   | 
        
           |  |  | 1975 |         return $cancellable;
 | 
        
           |  |  | 1976 |     }
 | 
        
           |  |  | 1977 |   | 
        
           |  |  | 1978 |     /**
 | 
        
           |  |  | 1979 |      * Archive the current on-disk plugin code.
 | 
        
           |  |  | 1980 |      *
 | 
        
           |  |  | 1981 |      * @param \core\plugininfo\base $plugin
 | 
        
           |  |  | 1982 |      * @return bool
 | 
        
           |  |  | 1983 |      */
 | 
        
           |  |  | 1984 |     public function archive_plugin_version(\core\plugininfo\base $plugin) {
 | 
        
           |  |  | 1985 |         return $this->get_code_manager()->archive_plugin_version($plugin->rootdir, $plugin->component, $plugin->versiondisk);
 | 
        
           |  |  | 1986 |     }
 | 
        
           |  |  | 1987 |   | 
        
           |  |  | 1988 |     /**
 | 
        
           |  |  | 1989 |      * Returns list of all archives that can be installed to cancel the plugin upgrade.
 | 
        
           |  |  | 1990 |      *
 | 
        
           |  |  | 1991 |      * @return array [(string)component] => {(string)->component, (string)->zipfilepath}
 | 
        
           |  |  | 1992 |      */
 | 
        
           |  |  | 1993 |     public function list_restorable_archives() {
 | 
        
           |  |  | 1994 |         global $CFG;
 | 
        
           |  |  | 1995 |   | 
        
           |  |  | 1996 |         if (!empty($CFG->disableupdateautodeploy)) {
 | 
        
           |  |  | 1997 |             return false;
 | 
        
           |  |  | 1998 |         }
 | 
        
           |  |  | 1999 |   | 
        
           |  |  | 2000 |         $codeman = $this->get_code_manager();
 | 
        
           |  |  | 2001 |         $restorable = [];
 | 
        
           |  |  | 2002 |         foreach ($this->get_plugins() as $type => $plugins) {
 | 
        
           |  |  | 2003 |             foreach ($plugins as $plugin) {
 | 
        
           |  |  | 2004 |                 if ($this->can_cancel_plugin_upgrade($plugin)) {
 | 
        
           |  |  | 2005 |                     $restorable[$plugin->component] = (object)[
 | 
        
           |  |  | 2006 |                         'component' => $plugin->component,
 | 
        
           |  |  | 2007 |                         'zipfilepath' => $codeman->get_archived_plugin_version($plugin->component, $plugin->versiondb),
 | 
        
           |  |  | 2008 |                     ];
 | 
        
           |  |  | 2009 |                 }
 | 
        
           |  |  | 2010 |             }
 | 
        
           |  |  | 2011 |         }
 | 
        
           |  |  | 2012 |   | 
        
           |  |  | 2013 |         return $restorable;
 | 
        
           |  |  | 2014 |     }
 | 
        
           |  |  | 2015 |   | 
        
           |  |  | 2016 |     /**
 | 
        
           |  |  | 2017 |      * Reorders plugin types into a sequence to be displayed
 | 
        
           |  |  | 2018 |      *
 | 
        
           |  |  | 2019 |      * For technical reasons, plugin types returned by {@link core_component::get_plugin_types()} are
 | 
        
           |  |  | 2020 |      * in a certain order that does not need to fit the expected order for the display.
 | 
        
           |  |  | 2021 |      * Particularly, activity modules should be displayed first as they represent the
 | 
        
           |  |  | 2022 |      * real heart of Moodle. They should be followed by other plugin types that are
 | 
        
           |  |  | 2023 |      * used to build the courses (as that is what one expects from LMS). After that,
 | 
        
           |  |  | 2024 |      * other supportive plugin types follow.
 | 
        
           |  |  | 2025 |      *
 | 
        
           |  |  | 2026 |      * @param array $types associative array
 | 
        
           |  |  | 2027 |      * @return array same array with altered order of items
 | 
        
           |  |  | 2028 |      */
 | 
        
           |  |  | 2029 |     protected function reorder_plugin_types(array $types) {
 | 
        
           |  |  | 2030 |         $fix = ['mod' => $types['mod']];
 | 
        
           |  |  | 2031 |         foreach (core_component::get_plugin_list('mod') as $plugin => $fulldir) {
 | 
        
           |  |  | 2032 |             if (!$subtypes = core_component::get_subplugins('mod_' . $plugin)) {
 | 
        
           |  |  | 2033 |                 continue;
 | 
        
           |  |  | 2034 |             }
 | 
        
           |  |  | 2035 |             foreach ($subtypes as $subtype => $ignored) {
 | 
        
           |  |  | 2036 |                 $fix[$subtype] = $types[$subtype];
 | 
        
           |  |  | 2037 |             }
 | 
        
           |  |  | 2038 |         }
 | 
        
           |  |  | 2039 |   | 
        
           |  |  | 2040 |         $fix['mod']        = $types['mod'];
 | 
        
           |  |  | 2041 |         $fix['block']      = $types['block'];
 | 
        
           |  |  | 2042 |         $fix['qtype']      = $types['qtype'];
 | 
        
           |  |  | 2043 |         $fix['qbank']      = $types['qbank'];
 | 
        
           |  |  | 2044 |         $fix['qbehaviour'] = $types['qbehaviour'];
 | 
        
           |  |  | 2045 |         $fix['qformat']    = $types['qformat'];
 | 
        
           |  |  | 2046 |         $fix['filter']     = $types['filter'];
 | 
        
           |  |  | 2047 |   | 
        
           |  |  | 2048 |         $fix['editor']     = $types['editor'];
 | 
        
           |  |  | 2049 |         foreach (core_component::get_plugin_list('editor') as $plugin => $fulldir) {
 | 
        
           |  |  | 2050 |             if (!$subtypes = core_component::get_subplugins('editor_' . $plugin)) {
 | 
        
           |  |  | 2051 |                 continue;
 | 
        
           |  |  | 2052 |             }
 | 
        
           |  |  | 2053 |             foreach ($subtypes as $subtype => $ignored) {
 | 
        
           |  |  | 2054 |                 $fix[$subtype] = $types[$subtype];
 | 
        
           |  |  | 2055 |             }
 | 
        
           |  |  | 2056 |         }
 | 
        
           |  |  | 2057 |   | 
        
           |  |  | 2058 |         $fix['enrol'] = $types['enrol'];
 | 
        
           |  |  | 2059 |         $fix['auth']  = $types['auth'];
 | 
        
           |  |  | 2060 |         $fix['tool']  = $types['tool'];
 | 
        
           |  |  | 2061 |         foreach (core_component::get_plugin_list('tool') as $plugin => $fulldir) {
 | 
        
           |  |  | 2062 |             if (!$subtypes = core_component::get_subplugins('tool_' . $plugin)) {
 | 
        
           |  |  | 2063 |                 continue;
 | 
        
           |  |  | 2064 |             }
 | 
        
           |  |  | 2065 |             foreach ($subtypes as $subtype => $ignored) {
 | 
        
           |  |  | 2066 |                 $fix[$subtype] = $types[$subtype];
 | 
        
           |  |  | 2067 |             }
 | 
        
           |  |  | 2068 |         }
 | 
        
           |  |  | 2069 |   | 
        
           |  |  | 2070 |         foreach ($types as $type => $path) {
 | 
        
           |  |  | 2071 |             if (!isset($fix[$type])) {
 | 
        
           |  |  | 2072 |                 $fix[$type] = $path;
 | 
        
           |  |  | 2073 |             }
 | 
        
           |  |  | 2074 |         }
 | 
        
           |  |  | 2075 |         return $fix;
 | 
        
           |  |  | 2076 |     }
 | 
        
           |  |  | 2077 |   | 
        
           |  |  | 2078 |     /**
 | 
        
           |  |  | 2079 |      * Check if the given directory can be removed by the web server process.
 | 
        
           |  |  | 2080 |      *
 | 
        
           |  |  | 2081 |      * This recursively checks that the given directory and all its contents
 | 
        
           |  |  | 2082 |      * it writable.
 | 
        
           |  |  | 2083 |      *
 | 
        
           |  |  | 2084 |      * @param string $fullpath
 | 
        
           |  |  | 2085 |      * @return boolean
 | 
        
           |  |  | 2086 |      */
 | 
        
           |  |  | 2087 |     public function is_directory_removable($fullpath) {
 | 
        
           |  |  | 2088 |   | 
        
           |  |  | 2089 |         if (!is_writable($fullpath)) {
 | 
        
           |  |  | 2090 |             return false;
 | 
        
           |  |  | 2091 |         }
 | 
        
           |  |  | 2092 |   | 
        
           |  |  | 2093 |         if (is_dir($fullpath)) {
 | 
        
           |  |  | 2094 |             $handle = opendir($fullpath);
 | 
        
           |  |  | 2095 |         } else {
 | 
        
           |  |  | 2096 |             return false;
 | 
        
           |  |  | 2097 |         }
 | 
        
           |  |  | 2098 |   | 
        
           |  |  | 2099 |         $result = true;
 | 
        
           |  |  | 2100 |   | 
        
           |  |  | 2101 |         while ($filename = readdir($handle)) {
 | 
        
           |  |  | 2102 |             if ($filename === '.' || $filename === '..') {
 | 
        
           |  |  | 2103 |                 continue;
 | 
        
           |  |  | 2104 |             }
 | 
        
           |  |  | 2105 |   | 
        
           |  |  | 2106 |             $subfilepath = $fullpath . '/' . $filename;
 | 
        
           |  |  | 2107 |   | 
        
           |  |  | 2108 |             if (is_dir($subfilepath)) {
 | 
        
           |  |  | 2109 |                 $result = $result && $this->is_directory_removable($subfilepath);
 | 
        
           |  |  | 2110 |             } else {
 | 
        
           |  |  | 2111 |                 $result = $result && is_writable($subfilepath);
 | 
        
           |  |  | 2112 |             }
 | 
        
           |  |  | 2113 |         }
 | 
        
           |  |  | 2114 |   | 
        
           |  |  | 2115 |         closedir($handle);
 | 
        
           |  |  | 2116 |   | 
        
           |  |  | 2117 |         return $result;
 | 
        
           |  |  | 2118 |     }
 | 
        
           |  |  | 2119 |   | 
        
           |  |  | 2120 |     /**
 | 
        
           |  |  | 2121 |      * Helper method that implements common uninstall prerequisites
 | 
        
           |  |  | 2122 |      *
 | 
        
           |  |  | 2123 |      * @param \core\plugininfo\base $pluginfo
 | 
        
           |  |  | 2124 |      * @return bool
 | 
        
           |  |  | 2125 |      */
 | 
        
           |  |  | 2126 |     protected function common_uninstall_check(\core\plugininfo\base $pluginfo) {
 | 
        
           |  |  | 2127 |         global $CFG;
 | 
        
           |  |  | 2128 |         // Check if uninstall is allowed from the GUI.
 | 
        
           |  |  | 2129 |         if (!empty($CFG->uninstallclionly) && (!CLI_SCRIPT)) {
 | 
        
           |  |  | 2130 |             return false;
 | 
        
           |  |  | 2131 |         }
 | 
        
           |  |  | 2132 |   | 
        
           |  |  | 2133 |         if (!$pluginfo->is_uninstall_allowed()) {
 | 
        
           |  |  | 2134 |             // The plugin's plugininfo class declares it should not be uninstalled.
 | 
        
           |  |  | 2135 |             return false;
 | 
        
           |  |  | 2136 |         }
 | 
        
           |  |  | 2137 |   | 
        
           |  |  | 2138 |         if ($pluginfo->get_status() === static::PLUGIN_STATUS_NEW) {
 | 
        
           |  |  | 2139 |             // The plugin is not installed. It should be either installed or removed from the disk.
 | 
        
           |  |  | 2140 |             // Relying on this temporary state may be tricky.
 | 
        
           |  |  | 2141 |             return false;
 | 
        
           |  |  | 2142 |         }
 | 
        
           |  |  | 2143 |   | 
        
           |  |  | 2144 |         if (method_exists($pluginfo, 'get_uninstall_url') && is_null($pluginfo->get_uninstall_url())) {
 | 
        
           |  |  | 2145 |             // Backwards compatibility.
 | 
        
           |  |  | 2146 |             debugging(
 | 
        
           |  |  | 2147 |                 '\core\plugininfo\base subclasses should use is_uninstall_allowed() ' .
 | 
        
           |  |  | 2148 |                     'instead of returning null in get_uninstall_url()',
 | 
        
           |  |  | 2149 |                 DEBUG_DEVELOPER
 | 
        
           |  |  | 2150 |             );
 | 
        
           |  |  | 2151 |             return false;
 | 
        
           |  |  | 2152 |         }
 | 
        
           |  |  | 2153 |   | 
        
           |  |  | 2154 |         return true;
 | 
        
           |  |  | 2155 |     }
 | 
        
           |  |  | 2156 |   | 
        
           |  |  | 2157 |     /**
 | 
        
           |  |  | 2158 |      * Returns a code_manager instance to be used for the plugins code operations.
 | 
        
           |  |  | 2159 |      *
 | 
        
           |  |  | 2160 |      * @return \core\update\code_manager
 | 
        
           |  |  | 2161 |      */
 | 
        
           |  |  | 2162 |     protected function get_code_manager() {
 | 
        
           |  |  | 2163 |   | 
        
           |  |  | 2164 |         if ($this->codemanager === null) {
 | 
        
           |  |  | 2165 |             $this->codemanager = new \core\update\code_manager();
 | 
        
           |  |  | 2166 |         }
 | 
        
           |  |  | 2167 |   | 
        
           |  |  | 2168 |         return $this->codemanager;
 | 
        
           |  |  | 2169 |     }
 | 
        
           |  |  | 2170 |   | 
        
           |  |  | 2171 |     /**
 | 
        
           |  |  | 2172 |      * Returns a client for https://download.moodle.org/api/
 | 
        
           |  |  | 2173 |      *
 | 
        
           |  |  | 2174 |      * @return \core\update\api
 | 
        
           |  |  | 2175 |      */
 | 
        
           |  |  | 2176 |     protected function get_update_api_client() {
 | 
        
           |  |  | 2177 |   | 
        
           |  |  | 2178 |         if ($this->updateapiclient === null) {
 | 
        
           |  |  | 2179 |             $this->updateapiclient = \core\update\api::client();
 | 
        
           |  |  | 2180 |         }
 | 
        
           |  |  | 2181 |   | 
        
           |  |  | 2182 |         return $this->updateapiclient;
 | 
        
           |  |  | 2183 |     }
 | 
        
           |  |  | 2184 | }
 | 
        
           |  |  | 2185 |   | 
        
           |  |  | 2186 | class_alias(plugin_manager::class, 'core_plugin_manager');
 |