MDL-49329 admin: Display missing dependencies on plugins check screen
[moodle.git] / lib / classes / plugin_manager.php
CommitLineData
e87214bd
PS
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/**
18 * Defines classes used for plugins management
19 *
20 * This library provides a unified interface to various plugin types in
21 * Moodle. It is mainly used by the plugins management admin page and the
22 * plugins check page during the upgrade.
23 *
24 * @package core
25 * @copyright 2011 David Mudrak <david@moodle.com>
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 */
28
29defined('MOODLE_INTERNAL') || die();
30
31/**
32 * Singleton class providing general plugins management functionality.
33 */
34class core_plugin_manager {
35
36 /** the plugin is shipped with standard Moodle distribution */
37 const PLUGIN_SOURCE_STANDARD = 'std';
38 /** the plugin is added extension */
39 const PLUGIN_SOURCE_EXTENSION = 'ext';
40
41 /** the plugin uses neither database nor capabilities, no versions */
42 const PLUGIN_STATUS_NODB = 'nodb';
43 /** the plugin is up-to-date */
44 const PLUGIN_STATUS_UPTODATE = 'uptodate';
45 /** the plugin is about to be installed */
46 const PLUGIN_STATUS_NEW = 'new';
47 /** the plugin is about to be upgraded */
48 const PLUGIN_STATUS_UPGRADE = 'upgrade';
49 /** the standard plugin is about to be deleted */
50 const PLUGIN_STATUS_DELETE = 'delete';
51 /** the version at the disk is lower than the one already installed */
52 const PLUGIN_STATUS_DOWNGRADE = 'downgrade';
53 /** the plugin is installed but missing from disk */
54 const PLUGIN_STATUS_MISSING = 'missing';
55
7eb87eff
DM
56 /** the given requirement/dependency is fulfilled */
57 const REQUIREMENT_STATUS_OK = 'ok';
58 /** the plugin requires higher core/other plugin version than is currently installed */
59 const REQUIREMENT_STATUS_OUTDATED = 'outdated';
60 /** the required dependency is not installed */
61 const REQUIREMENT_STATUS_MISSING = 'missing';
62
5a92cd0b
DM
63 /** the required dependency is available in the plugins directory */
64 const REQUIREMENT_AVAILABLE = 'available';
65 /** the required dependency is available in the plugins directory */
66 const REQUIREMENT_UNAVAILABLE = 'unavailable';
67
e87214bd
PS
68 /** @var core_plugin_manager holds the singleton instance */
69 protected static $singletoninstance;
70 /** @var array of raw plugins information */
71 protected $pluginsinfo = null;
72 /** @var array of raw subplugins information */
73 protected $subpluginsinfo = null;
5a92cd0b
DM
74 /** @var array cache information about availability in the plugins directory */
75 protected $remotepluginsinfo = null;
e87214bd
PS
76 /** @var array list of installed plugins $name=>$version */
77 protected $installedplugins = null;
78 /** @var array list of all enabled plugins $name=>$name */
79 protected $enabledplugins = null;
80 /** @var array list of all enabled plugins $name=>$diskversion */
81 protected $presentplugins = null;
82 /** @var array reordered list of plugin types */
83 protected $plugintypes = null;
84
85 /**
86 * Direct initiation not allowed, use the factory method {@link self::instance()}
87 */
88 protected function __construct() {
89 }
90
91 /**
92 * Sorry, this is singleton
93 */
94 protected function __clone() {
95 }
96
97 /**
98 * Factory method for this class
99 *
100 * @return core_plugin_manager the singleton instance
101 */
102 public static function instance() {
361feecd
DM
103 if (is_null(static::$singletoninstance)) {
104 static::$singletoninstance = new static();
e87214bd 105 }
361feecd 106 return static::$singletoninstance;
e87214bd
PS
107 }
108
109 /**
110 * Reset all caches.
111 * @param bool $phpunitreset
112 */
113 public static function reset_caches($phpunitreset = false) {
114 if ($phpunitreset) {
361feecd 115 static::$singletoninstance = null;
e87214bd 116 } else {
361feecd
DM
117 if (static::$singletoninstance) {
118 static::$singletoninstance->pluginsinfo = null;
119 static::$singletoninstance->subpluginsinfo = null;
120 static::$singletoninstance->installedplugins = null;
121 static::$singletoninstance->enabledplugins = null;
122 static::$singletoninstance->presentplugins = null;
123 static::$singletoninstance->plugintypes = null;
e87214bd
PS
124 }
125 }
126 $cache = cache::make('core', 'plugin_manager');
127 $cache->purge();
128 }
129
130 /**
131 * Returns the result of {@link core_component::get_plugin_types()} ordered for humans
132 *
133 * @see self::reorder_plugin_types()
134 * @return array (string)name => (string)location
135 */
136 public function get_plugin_types() {
137 if (func_num_args() > 0) {
138 if (!func_get_arg(0)) {
139 throw coding_exception('core_plugin_manager->get_plugin_types() does not support relative paths.');
140 }
141 }
142 if ($this->plugintypes) {
143 return $this->plugintypes;
144 }
145
146 $this->plugintypes = $this->reorder_plugin_types(core_component::get_plugin_types());
147 return $this->plugintypes;
148 }
149
150 /**
151 * Load list of installed plugins,
152 * always call before using $this->installedplugins.
153 *
154 * This method is caching results for all plugins.
155 */
156 protected function load_installed_plugins() {
157 global $DB, $CFG;
158
159 if ($this->installedplugins) {
160 return;
161 }
162
163 if (empty($CFG->version)) {
164 // Nothing installed yet.
165 $this->installedplugins = array();
166 return;
167 }
168
169 $cache = cache::make('core', 'plugin_manager');
170 $installed = $cache->get('installed');
171
172 if (is_array($installed)) {
173 $this->installedplugins = $installed;
174 return;
175 }
176
177 $this->installedplugins = array();
178
994e5662 179 // TODO: Delete this block once Moodle 2.6 or later becomes minimum required version to upgrade.
e87214bd
PS
180 if ($CFG->version < 2013092001.02) {
181 // We did not upgrade the database yet.
182 $modules = $DB->get_records('modules', array(), 'name ASC', 'id, name, version');
183 foreach ($modules as $module) {
184 $this->installedplugins['mod'][$module->name] = $module->version;
185 }
186 $blocks = $DB->get_records('block', array(), 'name ASC', 'id, name, version');
187 foreach ($blocks as $block) {
188 $this->installedplugins['block'][$block->name] = $block->version;
189 }
190 }
191
192 $versions = $DB->get_records('config_plugins', array('name'=>'version'));
193 foreach ($versions as $version) {
194 $parts = explode('_', $version->plugin, 2);
195 if (!isset($parts[1])) {
196 // Invalid component, there must be at least one "_".
197 continue;
198 }
199 // Do not verify here if plugin type and name are valid.
200 $this->installedplugins[$parts[0]][$parts[1]] = $version->value;
201 }
202
203 foreach ($this->installedplugins as $key => $value) {
204 ksort($this->installedplugins[$key]);
205 }
206
207 $cache->set('installed', $this->installedplugins);
208 }
209
210 /**
211 * Return list of installed plugins of given type.
212 * @param string $type
213 * @return array $name=>$version
214 */
215 public function get_installed_plugins($type) {
216 $this->load_installed_plugins();
217 if (isset($this->installedplugins[$type])) {
218 return $this->installedplugins[$type];
219 }
220 return array();
221 }
222
223 /**
224 * Load list of all enabled plugins,
225 * call before using $this->enabledplugins.
226 *
227 * This method is caching results from individual plugin info classes.
228 */
229 protected function load_enabled_plugins() {
230 global $CFG;
231
232 if ($this->enabledplugins) {
233 return;
234 }
235
236 if (empty($CFG->version)) {
237 $this->enabledplugins = array();
238 return;
239 }
240
241 $cache = cache::make('core', 'plugin_manager');
242 $enabled = $cache->get('enabled');
243
244 if (is_array($enabled)) {
245 $this->enabledplugins = $enabled;
246 return;
247 }
248
249 $this->enabledplugins = array();
250
251 require_once($CFG->libdir.'/adminlib.php');
252
253 $plugintypes = core_component::get_plugin_types();
254 foreach ($plugintypes as $plugintype => $fulldir) {
361feecd 255 $plugininfoclass = static::resolve_plugininfo_class($plugintype);
e87214bd
PS
256 if (class_exists($plugininfoclass)) {
257 $enabled = $plugininfoclass::get_enabled_plugins();
258 if (!is_array($enabled)) {
259 continue;
260 }
261 $this->enabledplugins[$plugintype] = $enabled;
262 }
263 }
264
265 $cache->set('enabled', $this->enabledplugins);
266 }
267
268 /**
269 * Get list of enabled plugins of given type,
270 * the result may contain missing plugins.
271 *
272 * @param string $type
273 * @return array|null list of enabled plugins of this type, null if unknown
274 */
275 public function get_enabled_plugins($type) {
276 $this->load_enabled_plugins();
277 if (isset($this->enabledplugins[$type])) {
278 return $this->enabledplugins[$type];
279 }
280 return null;
281 }
282
283 /**
284 * Load list of all present plugins - call before using $this->presentplugins.
285 */
286 protected function load_present_plugins() {
287 if ($this->presentplugins) {
288 return;
289 }
290
291 $cache = cache::make('core', 'plugin_manager');
292 $present = $cache->get('present');
293
294 if (is_array($present)) {
295 $this->presentplugins = $present;
296 return;
297 }
298
299 $this->presentplugins = array();
300
301 $plugintypes = core_component::get_plugin_types();
302 foreach ($plugintypes as $type => $typedir) {
303 $plugs = core_component::get_plugin_list($type);
304 foreach ($plugs as $plug => $fullplug) {
01889f01 305 $module = new stdClass();
e87214bd
PS
306 $plugin = new stdClass();
307 $plugin->version = null;
0b468c59 308 include($fullplug.'/version.php');
01889f01
DM
309
310 // Check if the legacy $module syntax is still used.
17d2a336 311 if (!is_object($module) or (count((array)$module) > 0)) {
01889f01
DM
312 debugging('Unsupported $module syntax detected in version.php of the '.$type.'_'.$plug.' plugin.');
313 $skipcache = true;
314 }
315
98ea6973
DM
316 // Check if the component is properly declared.
317 if (empty($plugin->component) or ($plugin->component !== $type.'_'.$plug)) {
318 debugging('Plugin '.$type.'_'.$plug.' does not declare valid $plugin->component in its version.php.');
319 $skipcache = true;
320 }
321
e87214bd
PS
322 $this->presentplugins[$type][$plug] = $plugin;
323 }
324 }
325
01889f01
DM
326 if (empty($skipcache)) {
327 $cache->set('present', $this->presentplugins);
328 }
e87214bd
PS
329 }
330
331 /**
332 * Get list of present plugins of given type.
333 *
334 * @param string $type
335 * @return array|null list of presnet plugins $name=>$diskversion, null if unknown
336 */
337 public function get_present_plugins($type) {
338 $this->load_present_plugins();
339 if (isset($this->presentplugins[$type])) {
340 return $this->presentplugins[$type];
341 }
342 return null;
343 }
344
345 /**
346 * Returns a tree of known plugins and information about them
347 *
348 * @return array 2D array. The first keys are plugin type names (e.g. qtype);
349 * the second keys are the plugin local name (e.g. multichoice); and
350 * the values are the corresponding objects extending {@link \core\plugininfo\base}
351 */
352 public function get_plugins() {
353 $this->init_pluginsinfo_property();
354
355 // Make sure all types are initialised.
356 foreach ($this->pluginsinfo as $plugintype => $list) {
357 if ($list === null) {
358 $this->get_plugins_of_type($plugintype);
359 }
360 }
361
362 return $this->pluginsinfo;
363 }
364
365 /**
366 * Returns list of known plugins of the given type.
367 *
368 * This method returns the subset of the tree returned by {@link self::get_plugins()}.
369 * If the given type is not known, empty array is returned.
370 *
371 * @param string $type plugin type, e.g. 'mod' or 'workshopallocation'
372 * @return \core\plugininfo\base[] (string)plugin name (e.g. 'workshop') => corresponding subclass of {@link \core\plugininfo\base}
373 */
374 public function get_plugins_of_type($type) {
375 global $CFG;
376
377 $this->init_pluginsinfo_property();
378
379 if (!array_key_exists($type, $this->pluginsinfo)) {
380 return array();
381 }
382
383 if (is_array($this->pluginsinfo[$type])) {
384 return $this->pluginsinfo[$type];
385 }
386
387 $types = core_component::get_plugin_types();
388
a35fce24
PS
389 if (!isset($types[$type])) {
390 // Orphaned subplugins!
361feecd 391 $plugintypeclass = static::resolve_plugininfo_class($type);
2d488c8f 392 $this->pluginsinfo[$type] = $plugintypeclass::get_plugins($type, null, $plugintypeclass, $this);
a35fce24
PS
393 return $this->pluginsinfo[$type];
394 }
395
e87214bd 396 /** @var \core\plugininfo\base $plugintypeclass */
361feecd 397 $plugintypeclass = static::resolve_plugininfo_class($type);
2d488c8f 398 $plugins = $plugintypeclass::get_plugins($type, $types[$type], $plugintypeclass, $this);
e87214bd
PS
399 $this->pluginsinfo[$type] = $plugins;
400
e87214bd
PS
401 return $this->pluginsinfo[$type];
402 }
403
404 /**
405 * Init placeholder array for plugin infos.
406 */
407 protected function init_pluginsinfo_property() {
408 if (is_array($this->pluginsinfo)) {
409 return;
410 }
411 $this->pluginsinfo = array();
412
413 $plugintypes = $this->get_plugin_types();
414
415 foreach ($plugintypes as $plugintype => $plugintyperootdir) {
416 $this->pluginsinfo[$plugintype] = null;
417 }
a35fce24
PS
418
419 // Add orphaned subplugin types.
420 $this->load_installed_plugins();
421 foreach ($this->installedplugins as $plugintype => $unused) {
422 if (!isset($plugintypes[$plugintype])) {
423 $this->pluginsinfo[$plugintype] = null;
424 }
425 }
e87214bd
PS
426 }
427
428 /**
429 * Find the plugin info class for given type.
430 *
431 * @param string $type
432 * @return string name of pluginfo class for give plugin type
433 */
434 public static function resolve_plugininfo_class($type) {
a35fce24
PS
435 $plugintypes = core_component::get_plugin_types();
436 if (!isset($plugintypes[$type])) {
437 return '\core\plugininfo\orphaned';
438 }
439
e87214bd
PS
440 $parent = core_component::get_subtype_parent($type);
441
442 if ($parent) {
443 $class = '\\'.$parent.'\plugininfo\\' . $type;
444 if (class_exists($class)) {
445 $plugintypeclass = $class;
446 } else {
447 if ($dir = core_component::get_component_directory($parent)) {
448 // BC only - use namespace instead!
449 if (file_exists("$dir/adminlib.php")) {
450 global $CFG;
451 include_once("$dir/adminlib.php");
452 }
453 if (class_exists('plugininfo_' . $type)) {
454 $plugintypeclass = 'plugininfo_' . $type;
455 debugging('Class "'.$plugintypeclass.'" is deprecated, migrate to "'.$class.'"', DEBUG_DEVELOPER);
456 } else {
457 debugging('Subplugin type "'.$type.'" should define class "'.$class.'"', DEBUG_DEVELOPER);
458 $plugintypeclass = '\core\plugininfo\general';
459 }
460 } else {
461 $plugintypeclass = '\core\plugininfo\general';
462 }
463 }
464 } else {
465 $class = '\core\plugininfo\\' . $type;
466 if (class_exists($class)) {
467 $plugintypeclass = $class;
468 } else {
469 debugging('All standard types including "'.$type.'" should have plugininfo class!', DEBUG_DEVELOPER);
470 $plugintypeclass = '\core\plugininfo\general';
471 }
472 }
473
474 if (!in_array('core\plugininfo\base', class_parents($plugintypeclass))) {
475 throw new coding_exception('Class ' . $plugintypeclass . ' must extend \core\plugininfo\base');
476 }
477
478 return $plugintypeclass;
479 }
480
481 /**
482 * Returns list of all known subplugins of the given plugin.
483 *
484 * For plugins that do not provide subplugins (i.e. there is no support for it),
485 * empty array is returned.
486 *
487 * @param string $component full component name, e.g. 'mod_workshop'
488 * @return array (string) component name (e.g. 'workshopallocation_random') => subclass of {@link \core\plugininfo\base}
489 */
490 public function get_subplugins_of_plugin($component) {
491
492 $pluginfo = $this->get_plugin_info($component);
493
494 if (is_null($pluginfo)) {
495 return array();
496 }
497
498 $subplugins = $this->get_subplugins();
499
500 if (!isset($subplugins[$pluginfo->component])) {
501 return array();
502 }
503
504 $list = array();
505
506 foreach ($subplugins[$pluginfo->component] as $subdata) {
507 foreach ($this->get_plugins_of_type($subdata->type) as $subpluginfo) {
508 $list[$subpluginfo->component] = $subpluginfo;
509 }
510 }
511
512 return $list;
513 }
514
515 /**
516 * Returns list of plugins that define their subplugins and the information
517 * about them from the db/subplugins.php file.
518 *
519 * @return array with keys like 'mod_quiz', and values the data from the
520 * corresponding db/subplugins.php file.
521 */
522 public function get_subplugins() {
523
524 if (is_array($this->subpluginsinfo)) {
525 return $this->subpluginsinfo;
526 }
527
528 $plugintypes = core_component::get_plugin_types();
529
530 $this->subpluginsinfo = array();
531 foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
532 foreach (core_component::get_plugin_list($type) as $plugin => $componentdir) {
533 $component = $type.'_'.$plugin;
534 $subplugins = core_component::get_subplugins($component);
535 if (!$subplugins) {
536 continue;
537 }
538 $this->subpluginsinfo[$component] = array();
539 foreach ($subplugins as $subplugintype => $ignored) {
540 $subplugin = new stdClass();
541 $subplugin->type = $subplugintype;
542 $subplugin->typerootdir = $plugintypes[$subplugintype];
543 $this->subpluginsinfo[$component][$subplugintype] = $subplugin;
544 }
545 }
546 }
547 return $this->subpluginsinfo;
548 }
549
550 /**
551 * Returns the name of the plugin that defines the given subplugin type
552 *
553 * If the given subplugin type is not actually a subplugin, returns false.
554 *
555 * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
556 * @return false|string the name of the parent plugin, eg. mod_workshop
557 */
558 public function get_parent_of_subplugin($subplugintype) {
559 $parent = core_component::get_subtype_parent($subplugintype);
560 if (!$parent) {
561 return false;
562 }
563 return $parent;
564 }
565
566 /**
567 * Returns a localized name of a given plugin
568 *
569 * @param string $component name of the plugin, eg mod_workshop or auth_ldap
570 * @return string
571 */
572 public function plugin_name($component) {
573
574 $pluginfo = $this->get_plugin_info($component);
575
576 if (is_null($pluginfo)) {
577 throw new moodle_exception('err_unknown_plugin', 'core_plugin', '', array('plugin' => $component));
578 }
579
580 return $pluginfo->displayname;
581 }
582
583 /**
584 * Returns a localized name of a plugin typed in singular form
585 *
586 * Most plugin types define their names in core_plugin lang file. In case of subplugins,
587 * we try to ask the parent plugin for the name. In the worst case, we will return
588 * the value of the passed $type parameter.
589 *
590 * @param string $type the type of the plugin, e.g. mod or workshopform
591 * @return string
592 */
593 public function plugintype_name($type) {
594
595 if (get_string_manager()->string_exists('type_' . $type, 'core_plugin')) {
596 // For most plugin types, their names are defined in core_plugin lang file.
597 return get_string('type_' . $type, 'core_plugin');
598
599 } else if ($parent = $this->get_parent_of_subplugin($type)) {
600 // If this is a subplugin, try to ask the parent plugin for the name.
601 if (get_string_manager()->string_exists('subplugintype_' . $type, $parent)) {
602 return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type, $parent);
603 } else {
604 return $this->plugin_name($parent) . ' / ' . $type;
605 }
606
607 } else {
608 return $type;
609 }
610 }
611
612 /**
613 * Returns a localized name of a plugin type in plural form
614 *
615 * Most plugin types define their names in core_plugin lang file. In case of subplugins,
616 * we try to ask the parent plugin for the name. In the worst case, we will return
617 * the value of the passed $type parameter.
618 *
619 * @param string $type the type of the plugin, e.g. mod or workshopform
620 * @return string
621 */
622 public function plugintype_name_plural($type) {
623
624 if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
625 // For most plugin types, their names are defined in core_plugin lang file.
626 return get_string('type_' . $type . '_plural', 'core_plugin');
627
628 } else if ($parent = $this->get_parent_of_subplugin($type)) {
629 // If this is a subplugin, try to ask the parent plugin for the name.
630 if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
631 return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
632 } else {
633 return $this->plugin_name($parent) . ' / ' . $type;
634 }
635
636 } else {
637 return $type;
638 }
639 }
640
641 /**
642 * Returns information about the known plugin, or null
643 *
644 * @param string $component frankenstyle component name.
645 * @return \core\plugininfo\base|null the corresponding plugin information.
646 */
647 public function get_plugin_info($component) {
648 list($type, $name) = core_component::normalize_component($component);
2384d331
PS
649 $plugins = $this->get_plugins_of_type($type);
650 if (isset($plugins[$name])) {
651 return $plugins[$name];
e87214bd
PS
652 } else {
653 return null;
654 }
655 }
656
657 /**
658 * Check to see if the current version of the plugin seems to be a checkout of an external repository.
659 *
660 * @see \core\update\deployer::plugin_external_source()
661 * @param string $component frankenstyle component name
662 * @return false|string
663 */
664 public function plugin_external_source($component) {
665
666 $plugininfo = $this->get_plugin_info($component);
667
668 if (is_null($plugininfo)) {
669 return false;
670 }
671
672 $pluginroot = $plugininfo->rootdir;
673
674 if (is_dir($pluginroot.'/.git')) {
675 return 'git';
676 }
677
a5d08dce
DM
678 if (is_file($pluginroot.'/.git')) {
679 return 'git-submodule';
680 }
681
e87214bd
PS
682 if (is_dir($pluginroot.'/CVS')) {
683 return 'cvs';
684 }
685
686 if (is_dir($pluginroot.'/.svn')) {
687 return 'svn';
688 }
689
0b515736
OS
690 if (is_dir($pluginroot.'/.hg')) {
691 return 'mercurial';
692 }
693
e87214bd
PS
694 return false;
695 }
696
697 /**
698 * Get a list of any other plugins that require this one.
699 * @param string $component frankenstyle component name.
700 * @return array of frankensyle component names that require this one.
701 */
702 public function other_plugins_that_require($component) {
703 $others = array();
704 foreach ($this->get_plugins() as $type => $plugins) {
705 foreach ($plugins as $plugin) {
706 $required = $plugin->get_other_required_plugins();
707 if (isset($required[$component])) {
708 $others[] = $plugin->component;
709 }
710 }
711 }
712 return $others;
713 }
714
715 /**
716 * Check a dependencies list against the list of installed plugins.
717 * @param array $dependencies compenent name to required version or ANY_VERSION.
718 * @return bool true if all the dependencies are satisfied.
719 */
720 public function are_dependencies_satisfied($dependencies) {
721 foreach ($dependencies as $component => $requiredversion) {
722 $otherplugin = $this->get_plugin_info($component);
723 if (is_null($otherplugin)) {
724 return false;
725 }
726
727 if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) {
728 return false;
729 }
730 }
731
732 return true;
733 }
734
735 /**
736 * Checks all dependencies for all installed plugins
737 *
738 * This is used by install and upgrade. The array passed by reference as the second
739 * argument is populated with the list of plugins that have failed dependencies (note that
740 * a single plugin can appear multiple times in the $failedplugins).
741 *
742 * @param int $moodleversion the version from version.php.
743 * @param array $failedplugins to return the list of plugins with non-satisfied dependencies
744 * @return bool true if all the dependencies are satisfied for all plugins.
745 */
746 public function all_plugins_ok($moodleversion, &$failedplugins = array()) {
747
748 $return = true;
749 foreach ($this->get_plugins() as $type => $plugins) {
750 foreach ($plugins as $plugin) {
751
752 if (!$plugin->is_core_dependency_satisfied($moodleversion)) {
753 $return = false;
754 $failedplugins[] = $plugin->component;
755 }
756
757 if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
758 $return = false;
759 $failedplugins[] = $plugin->component;
760 }
761 }
762 }
763
764 return $return;
765 }
766
7eb87eff
DM
767 /**
768 * Resolve requirements and dependencies of a plugin.
769 *
770 * Returns an array of objects describing the requirement/dependency,
771 * indexed by the frankenstyle name of the component. The returned array
772 * can be empty. The objects in the array have following properties:
773 *
774 * ->(numeric)hasver
775 * ->(numeric)reqver
776 * ->(string)status
5a92cd0b 777 * ->(string)availability
7eb87eff
DM
778 *
779 * @param \core\plugininfo\base $plugin the plugin we are checking
780 * @param null|string|int|double $moodleversion explicit moodle core version to check against, defaults to $CFG->version
781 * @param null|string|int $moodlebranch explicit moodle core branch to check against, defaults to $CFG->branch
782 * @return array of objects
783 */
784 public function resolve_requirements(\core\plugininfo\base $plugin, $moodleversion=null, $moodlebranch=null) {
785 global $CFG;
786
787 if ($moodleversion === null) {
788 $moodleversion = $CFG->version;
789 }
790
791 if ($moodlebranch === null) {
792 $moodlebranch = $CFG->branch;
793 }
794
795 $reqs = array();
796 $reqcore = $this->resolve_core_requirements($plugin, $moodleversion);
797
798 if (!empty($reqcore)) {
799 $reqs['core'] = $reqcore;
800 }
801
802 foreach ($plugin->get_other_required_plugins() as $reqplug => $reqver) {
803 $reqs[$reqplug] = $this->resolve_dependency_requirements($plugin, $reqplug, $reqver, $moodlebranch);
804 }
805
806 return $reqs;
807 }
808
809 /**
810 * Helper method to resolve plugin's requirements on the moodle core.
811 *
812 * @param \core\plugininfo\base $plugin the plugin we are checking
813 * @param string|int|double $moodleversion moodle core branch to check against
814 * @return stdObject
815 */
816 protected function resolve_core_requirements(\core\plugininfo\base $plugin, $moodleversion) {
817
5a92cd0b
DM
818 $reqs = (object)array(
819 'hasver' => null,
820 'reqver' => null,
821 'status' => null,
822 'availability' => null,
823 );
7eb87eff
DM
824
825 $reqs->hasver = $moodleversion;
826
827 if (empty($plugin->versionrequires)) {
828 $reqs->reqver = ANY_VERSION;
829 } else {
830 $reqs->reqver = $plugin->versionrequires;
831 }
832
833 if ($plugin->is_core_dependency_satisfied($moodleversion)) {
834 $reqs->status = self::REQUIREMENT_STATUS_OK;
835 } else {
836 $reqs->status = self::REQUIREMENT_STATUS_OUTDATED;
837 }
838
839 return $reqs;
840 }
841
842 /**
843 * Helper method to resolve plugin's dependecies on other plugins.
844 *
845 * @param \core\plugininfo\base $plugin the plugin we are checking
846 * @param string $otherpluginname
847 * @param string|int $requiredversion
848 * @param string|int $moodlebranch explicit moodle core branch to check against, defaults to $CFG->branch
849 * @return stdClass
850 */
851 protected function resolve_dependency_requirements(\core\plugininfo\base $plugin, $otherpluginname,
852 $requiredversion, $moodlebranch) {
853
5a92cd0b
DM
854 $reqs = (object)array(
855 'hasver' => null,
856 'reqver' => null,
857 'status' => null,
858 'availability' => null,
859 );
860
7eb87eff
DM
861 $otherplugin = $this->get_plugin_info($otherpluginname);
862
863 if ($otherplugin !== null) {
864 // The required plugin is installed.
865 $reqs->hasver = $otherplugin->versiondisk;
866 $reqs->reqver = $requiredversion;
867 // Check it has sufficient version.
868 if ($requiredversion == ANY_VERSION or $otherplugin->versiondisk >= $requiredversion) {
869 $reqs->status = self::REQUIREMENT_STATUS_OK;
870 } else {
871 $reqs->status = self::REQUIREMENT_STATUS_OUTDATED;
872 }
873
874 } else {
875 // The required plugin is not installed.
876 $reqs->hasver = null;
877 $reqs->reqver = $requiredversion;
878 $reqs->status = self::REQUIREMENT_STATUS_MISSING;
5a92cd0b
DM
879 }
880
881 if ($reqs->status !== self::REQUIREMENT_STATUS_OK) {
882 if ($this->is_remote_plugin_available($otherpluginname, $requiredversion)) {
883 $reqs->availability = self::REQUIREMENT_AVAILABLE;
884 } else {
885 $reqs->availability = self::REQUIREMENT_UNAVAILABLE;
886 }
7eb87eff
DM
887 }
888
889 return $reqs;
890 }
891
5a92cd0b
DM
892 /**
893 * Is the given plugin version available in the plugins directory?
894 *
895 * @param string $component
896 * @param string|int $requiredversion ANY_VERSION or the version number
897 * @return boolean
898 */
899 public function is_remote_plugin_available($component, $requiredversion) {
900
901 $info = $this->get_remote_plugin_info($component, $requiredversion);
902
903 if (empty($info)) {
904 // There is no available plugin of that name.
905 return false;
906 }
907
908 if (empty($info->version)) {
909 // Plugin is known, but no suitable version was found.
910 return false;
911 }
912
913 return true;
914 }
915
916 /**
917 * Returns information about a plugin in the plugins directory.
918 *
919 * See {@link \core\update\api::find_plugin()} for more details.
920 *
921 * @param string $component
922 * @param string|int $requiredversion ANY_VERSION or the version number
923 * @return stdClass|bool false or data object
924 */
925 public function get_remote_plugin_info($component, $requiredversion) {
926
927 if (!isset($this->remotepluginsinfo[$component][$requiredversion])) {
928 $client = \core\update\api::client();
929 $this->remotepluginsinfo[$component][$requiredversion] = $client->find_plugin($component, $requiredversion);
930 }
931
932 return $this->remotepluginsinfo[$component][$requiredversion];
933 }
934
935 /**
936 * Return a list of all missing dependencies.
937 *
938 * This should provide the full list of plugins that should be installed to
939 * fulfill the requirements of all plugins, if possible.
940 *
941 * @return array of stdClass|bool indexed by the component name
942 */
943 public function missing_dependencies() {
944
945 $dependencies = array();
946
947 foreach ($this->get_plugins() as $plugintype => $pluginfos) {
948 foreach ($pluginfos as $pluginname => $pluginfo) {
949 foreach ($this->resolve_requirements($pluginfo) as $reqname => $reqinfo) {
950 if ($reqname === 'core') {
951 continue;
952 }
953 if ($reqinfo->status != self::REQUIREMENT_STATUS_OK) {
954 if ($reqinfo->availability == self::REQUIREMENT_AVAILABLE) {
955 $remoteinfo = $this->get_remote_plugin_info($reqname, $reqinfo->reqver);
956
957 if (empty($dependencies[$reqname])) {
958 $dependencies[$reqname] = $remoteinfo;
959 } else {
960 // If two local plugins depend on the two different
961 // versions of the same remote plugin, pick the
962 // higher version.
963 if ($remoteinfo->version->version > $dependencies[$reqname]->version->version) {
964 $dependencies[$reqname] = $remoteinfo;
965 }
966 }
967
968 } else {
969 if (!isset($dependencies[$reqname])) {
970 // Unable to find a plugin fulfilling the requirements.
971 $dependencies[$reqname] = false;
972 }
973 }
974 }
975 }
976 }
977 }
978
979 return $dependencies;
980 }
981
e87214bd
PS
982 /**
983 * Is it possible to uninstall the given plugin?
984 *
985 * False is returned if the plugininfo subclass declares the uninstall should
986 * not be allowed via {@link \core\plugininfo\base::is_uninstall_allowed()} or if the
987 * core vetoes it (e.g. becase the plugin or some of its subplugins is required
988 * by some other installed plugin).
989 *
990 * @param string $component full frankenstyle name, e.g. mod_foobar
991 * @return bool
992 */
993 public function can_uninstall_plugin($component) {
994
995 $pluginfo = $this->get_plugin_info($component);
996
997 if (is_null($pluginfo)) {
998 return false;
999 }
1000
1001 if (!$this->common_uninstall_check($pluginfo)) {
1002 return false;
1003 }
1004
1005 // Verify only if something else requires the subplugins, do not verify their common_uninstall_check()!
1006 $subplugins = $this->get_subplugins_of_plugin($pluginfo->component);
1007 foreach ($subplugins as $subpluginfo) {
1008 // Check if there are some other plugins requiring this subplugin
1009 // (but the parent and siblings).
1010 foreach ($this->other_plugins_that_require($subpluginfo->component) as $requiresme) {
1011 $ismyparent = ($pluginfo->component === $requiresme);
1012 $ismysibling = in_array($requiresme, array_keys($subplugins));
1013 if (!$ismyparent and !$ismysibling) {
1014 return false;
1015 }
1016 }
1017 }
1018
1019 // Check if there are some other plugins requiring this plugin
1020 // (but its subplugins).
1021 foreach ($this->other_plugins_that_require($pluginfo->component) as $requiresme) {
1022 $ismysubplugin = in_array($requiresme, array_keys($subplugins));
1023 if (!$ismysubplugin) {
1024 return false;
1025 }
1026 }
1027
1028 return true;
1029 }
1030
1031 /**
1032 * Returns uninstall URL if exists.
1033 *
1034 * @param string $component
1035 * @param string $return either 'overview' or 'manage'
1036 * @return moodle_url uninstall URL, null if uninstall not supported
1037 */
1038 public function get_uninstall_url($component, $return = 'overview') {
1039 if (!$this->can_uninstall_plugin($component)) {
1040 return null;
1041 }
1042
1043 $pluginfo = $this->get_plugin_info($component);
1044
1045 if (is_null($pluginfo)) {
1046 return null;
1047 }
1048
1049 if (method_exists($pluginfo, 'get_uninstall_url')) {
1050 debugging('plugininfo method get_uninstall_url() is deprecated, all plugins should be uninstalled via standard URL only.');
1051 return $pluginfo->get_uninstall_url($return);
1052 }
1053
1054 return $pluginfo->get_default_uninstall_url($return);
1055 }
1056
1057 /**
1058 * Uninstall the given plugin.
1059 *
1060 * Automatically cleans-up all remaining configuration data, log records, events,
1061 * files from the file pool etc.
1062 *
1063 * In the future, the functionality of {@link uninstall_plugin()} function may be moved
1064 * into this method and all the code should be refactored to use it. At the moment, we
1065 * mimic this future behaviour by wrapping that function call.
1066 *
1067 * @param string $component
1068 * @param progress_trace $progress traces the process
1069 * @return bool true on success, false on errors/problems
1070 */
1071 public function uninstall_plugin($component, progress_trace $progress) {
1072
1073 $pluginfo = $this->get_plugin_info($component);
1074
1075 if (is_null($pluginfo)) {
1076 return false;
1077 }
1078
1079 // Give the pluginfo class a chance to execute some steps.
1080 $result = $pluginfo->uninstall($progress);
1081 if (!$result) {
1082 return false;
1083 }
1084
1085 // Call the legacy core function to uninstall the plugin.
1086 ob_start();
1087 uninstall_plugin($pluginfo->type, $pluginfo->name);
1088 $progress->output(ob_get_clean());
1089
1090 return true;
1091 }
1092
1093 /**
1094 * Checks if there are some plugins with a known available update
1095 *
1096 * @return bool true if there is at least one available update
1097 */
1098 public function some_plugins_updatable() {
1099 foreach ($this->get_plugins() as $type => $plugins) {
1100 foreach ($plugins as $plugin) {
1101 if ($plugin->available_updates()) {
1102 return true;
1103 }
1104 }
1105 }
1106
1107 return false;
1108 }
1109
c44bbe35
DM
1110 /**
1111 * Returns list of available updates for the given component.
1112 *
1113 * This method should be considered as internal API and is supposed to be
1114 * called by {@link \core\plugininfo\base::available_updates()} only
1115 * to lazy load the data once they are first requested.
1116 *
1117 * @param string $component frankenstyle name of the plugin
1118 * @return null|array array of \core\update\info objects or null
1119 */
1120 public function load_available_updates_for_plugin($component) {
1121 global $CFG;
1122
1123 $provider = \core\update\checker::instance();
1124
1125 if (!$provider->enabled() or during_initial_install()) {
1126 return null;
1127 }
1128
1129 if (isset($CFG->updateminmaturity)) {
1130 $minmaturity = $CFG->updateminmaturity;
1131 } else {
1132 // This can happen during the very first upgrade to 2.3.
1133 $minmaturity = MATURITY_STABLE;
1134 }
1135
1136 return $provider->get_update_info($component, array('minmaturity' => $minmaturity));
1137 }
1138
e87214bd
PS
1139 /**
1140 * Check to see if the given plugin folder can be removed by the web server process.
1141 *
1142 * @param string $component full frankenstyle component
1143 * @return bool
1144 */
1145 public function is_plugin_folder_removable($component) {
1146
1147 $pluginfo = $this->get_plugin_info($component);
1148
1149 if (is_null($pluginfo)) {
1150 return false;
1151 }
1152
1153 // To be able to remove the plugin folder, its parent must be writable, too.
1154 if (!is_writable(dirname($pluginfo->rootdir))) {
1155 return false;
1156 }
1157
1158 // Check that the folder and all its content is writable (thence removable).
1159 return $this->is_directory_removable($pluginfo->rootdir);
1160 }
1161
1162 /**
1163 * Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
1164 * but are not anymore and are deleted during upgrades.
1165 *
1166 * The main purpose of this list is to hide missing plugins during upgrade.
1167 *
1168 * @param string $type plugin type
1169 * @param string $name plugin name
1170 * @return bool
1171 */
1172 public static function is_deleted_standard_plugin($type, $name) {
1173 // Do not include plugins that were removed during upgrades to versions that are
1174 // not supported as source versions for upgrade any more. For example, at MOODLE_23_STABLE
1175 // branch, listed should be no plugins that were removed at 1.9.x - 2.1.x versions as
1176 // Moodle 2.3 supports upgrades from 2.2.x only.
1177 $plugins = array(
a75fa4c0 1178 'qformat' => array('blackboard', 'learnwise'),
e87214bd 1179 'enrol' => array('authorize'),
1170df12 1180 'tinymce' => array('dragmath'),
d6e7a63d 1181 'tool' => array('bloglevelupgrade', 'qeupgradehelper', 'timezoneimport'),
7a2dabcb
FM
1182 'theme' => array('mymobile', 'afterburner', 'anomaly', 'arialist', 'binarius', 'boxxie', 'brick', 'formal_white',
1183 'formfactor', 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero', 'overlay', 'serenity', 'sky_high',
1184 'splash', 'standard', 'standardold'),
e87214bd
PS
1185 );
1186
1187 if (!isset($plugins[$type])) {
1188 return false;
1189 }
1190 return in_array($name, $plugins[$type]);
1191 }
1192
1193 /**
1194 * Defines a white list of all plugins shipped in the standard Moodle distribution
1195 *
1196 * @param string $type
1197 * @return false|array array of standard plugins or false if the type is unknown
1198 */
1199 public static function standard_plugins_list($type) {
1200
1201 $standard_plugins = array(
1202
adca7326 1203 'atto' => array(
205c6db5
MG
1204 'accessibilitychecker', 'accessibilityhelper', 'align',
1205 'backcolor', 'bold', 'charmap', 'clear', 'collapse', 'emoticon',
1206 'equation', 'fontcolor', 'html', 'image', 'indent', 'italic',
1207 'link', 'managefiles', 'media', 'noautolink', 'orderedlist',
1208 'rtl', 'strike', 'subscript', 'superscript', 'table', 'title',
49a510ef 1209 'underline', 'undo', 'unorderedlist'
adca7326
DW
1210 ),
1211
e87214bd
PS
1212 'assignment' => array(
1213 'offline', 'online', 'upload', 'uploadsingle'
1214 ),
1215
1216 'assignsubmission' => array(
1217 'comments', 'file', 'onlinetext'
1218 ),
1219
1220 'assignfeedback' => array(
1221 'comments', 'file', 'offline', 'editpdf'
1222 ),
1223
e87214bd
PS
1224 'auth' => array(
1225 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
1226 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
1227 'shibboleth', 'webservice'
1228 ),
1229
d3db4b03 1230 'availability' => array(
1231 'completion', 'date', 'grade', 'group', 'grouping', 'profile'
1232 ),
1233
e87214bd 1234 'block' => array(
d6383f6a
SB
1235 'activity_modules', 'activity_results', 'admin_bookmarks', 'badges',
1236 'blog_menu', 'blog_recent', 'blog_tags', 'calendar_month',
e87214bd
PS
1237 'calendar_upcoming', 'comments', 'community',
1238 'completionstatus', 'course_list', 'course_overview',
1239 'course_summary', 'feedback', 'glossary_random', 'html',
1240 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
1241 'navigation', 'news_items', 'online_users', 'participants',
1242 'private_files', 'quiz_results', 'recent_activity',
1243 'rss_client', 'search_forums', 'section_links',
1244 'selfcompletion', 'settings', 'site_main_menu',
1245 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
1246 ),
1247
1248 'booktool' => array(
1249 'exportimscp', 'importhtml', 'print'
1250 ),
1251
1252 'cachelock' => array(
1253 'file'
1254 ),
1255
1256 'cachestore' => array(
1257 'file', 'memcache', 'memcached', 'mongodb', 'session', 'static'
1258 ),
1259
1260 'calendartype' => array(
1261 'gregorian'
1262 ),
1263
1264 'coursereport' => array(
1265 // Deprecated!
1266 ),
1267
1268 'datafield' => array(
1269 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
1270 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
1271 ),
1272
1273 'datapreset' => array(
1274 'imagegallery'
1275 ),
1276
1277 'editor' => array(
205c6db5 1278 'atto', 'textarea', 'tinymce'
e87214bd
PS
1279 ),
1280
1281 'enrol' => array(
1282 'category', 'cohort', 'database', 'flatfile',
1283 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
1284 'paypal', 'self'
1285 ),
1286
1287 'filter' => array(
1288 'activitynames', 'algebra', 'censor', 'emailprotect',
289ed254 1289 'emoticon', 'mathjaxloader', 'mediaplugin', 'multilang', 'tex', 'tidy',
e87214bd
PS
1290 'urltolink', 'data', 'glossary'
1291 ),
1292
1293 'format' => array(
1294 'singleactivity', 'social', 'topics', 'weeks'
1295 ),
1296
1297 'gradeexport' => array(
1298 'ods', 'txt', 'xls', 'xml'
1299 ),
1300
1301 'gradeimport' => array(
aa60bda9 1302 'csv', 'direct', 'xml'
e87214bd
PS
1303 ),
1304
1305 'gradereport' => array(
8ec7b088 1306 'grader', 'history', 'outcomes', 'overview', 'user', 'singleview'
e87214bd
PS
1307 ),
1308
1309 'gradingform' => array(
1310 'rubric', 'guide'
1311 ),
1312
1313 'local' => array(
1314 ),
1315
7eaca5a8
1316 'logstore' => array(
1317 'database', 'legacy', 'standard',
1318 ),
1319
e3f69b58 1320 'ltiservice' => array(
3562c426 1321 'memberships', 'profile', 'toolproxy', 'toolsettings'
e3f69b58 1322 ),
1323
e87214bd 1324 'message' => array(
324facf4 1325 'airnotifier', 'email', 'jabber', 'popup'
e87214bd
PS
1326 ),
1327
1328 'mnetservice' => array(
1329 'enrol'
1330 ),
1331
1332 'mod' => array(
1333 'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
1334 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
1335 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
1336 ),
1337
1338 'plagiarism' => array(
1339 ),
1340
1341 'portfolio' => array(
1342 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
1343 ),
1344
1345 'profilefield' => array(
1346 'checkbox', 'datetime', 'menu', 'text', 'textarea'
1347 ),
1348
1349 'qbehaviour' => array(
1350 'adaptive', 'adaptivenopenalty', 'deferredcbm',
1351 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
1352 'informationitem', 'interactive', 'interactivecountback',
1353 'manualgraded', 'missing'
1354 ),
1355
1356 'qformat' => array(
1357 'aiken', 'blackboard_six', 'examview', 'gift',
a75fa4c0 1358 'missingword', 'multianswer', 'webct',
e87214bd
PS
1359 'xhtml', 'xml'
1360 ),
1361
1362 'qtype' => array(
1363 'calculated', 'calculatedmulti', 'calculatedsimple',
6e28e150
TH
1364 'ddimageortext', 'ddmarker', 'ddwtos', 'description',
1365 'essay', 'gapselect', 'match', 'missingtype', 'multianswer',
e87214bd
PS
1366 'multichoice', 'numerical', 'random', 'randomsamatch',
1367 'shortanswer', 'truefalse'
1368 ),
1369
1370 'quiz' => array(
1371 'grading', 'overview', 'responses', 'statistics'
1372 ),
1373
1374 'quizaccess' => array(
1375 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
1376 'password', 'safebrowser', 'securewindow', 'timelimit'
1377 ),
1378
1379 'report' => array(
4f078f38 1380 'backups', 'completion', 'configlog', 'courseoverview', 'eventlist',
8064168e
PS
1381 'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats', 'performance',
1382 'usersessions',
e87214bd
PS
1383 ),
1384
1385 'repository' => array(
1386 'alfresco', 'areafiles', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem',
1387 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
1388 'picasa', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav',
1389 'wikimedia', 'youtube'
1390 ),
1391
1392 'scormreport' => array(
1393 'basic',
1394 'interactions',
1395 'graphs',
1396 'objectives'
1397 ),
1398
1399 'tinymce' => array(
1170df12 1400 'ctrlhelp', 'managefiles', 'moodleemoticon', 'moodleimage',
e87214bd
PS
1401 'moodlemedia', 'moodlenolink', 'pdw', 'spellchecker', 'wrap'
1402 ),
1403
1404 'theme' => array(
bfb6e97e 1405 'base', 'bootstrapbase', 'canvas', 'clean', 'more'
e87214bd
PS
1406 ),
1407
1408 'tool' => array(
d3db4b03 1409 'assignmentupgrade', 'availabilityconditions', 'behat', 'capability', 'customlang',
ae46ca5f 1410 'dbtransfer', 'filetypes', 'generator', 'health', 'innodb', 'installaddon',
92b40de9 1411 'langimport', 'log', 'messageinbound', 'multilangupgrade', 'monitor', 'phpunit', 'profiling',
274d79c9 1412 'replace', 'spamcleaner', 'task', 'templatelibrary',
e87214bd
PS
1413 'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'xmldb'
1414 ),
1415
1416 'webservice' => array(
1417 'amf', 'rest', 'soap', 'xmlrpc'
1418 ),
1419
1420 'workshopallocation' => array(
1421 'manual', 'random', 'scheduled'
1422 ),
1423
1424 'workshopeval' => array(
1425 'best'
1426 ),
1427
1428 'workshopform' => array(
1429 'accumulative', 'comments', 'numerrors', 'rubric'
1430 )
1431 );
1432
1433 if (isset($standard_plugins[$type])) {
1434 return $standard_plugins[$type];
1435 } else {
1436 return false;
1437 }
1438 }
1439
1440 /**
1441 * Reorders plugin types into a sequence to be displayed
1442 *
1443 * For technical reasons, plugin types returned by {@link core_component::get_plugin_types()} are
1444 * in a certain order that does not need to fit the expected order for the display.
1445 * Particularly, activity modules should be displayed first as they represent the
1446 * real heart of Moodle. They should be followed by other plugin types that are
1447 * used to build the courses (as that is what one expects from LMS). After that,
1448 * other supportive plugin types follow.
1449 *
1450 * @param array $types associative array
1451 * @return array same array with altered order of items
1452 */
1453 protected function reorder_plugin_types(array $types) {
1454 $fix = array('mod' => $types['mod']);
1455 foreach (core_component::get_plugin_list('mod') as $plugin => $fulldir) {
1456 if (!$subtypes = core_component::get_subplugins('mod_'.$plugin)) {
1457 continue;
1458 }
1459 foreach ($subtypes as $subtype => $ignored) {
1460 $fix[$subtype] = $types[$subtype];
1461 }
1462 }
1463
1464 $fix['mod'] = $types['mod'];
1465 $fix['block'] = $types['block'];
1466 $fix['qtype'] = $types['qtype'];
1467 $fix['qbehaviour'] = $types['qbehaviour'];
1468 $fix['qformat'] = $types['qformat'];
1469 $fix['filter'] = $types['filter'];
1470
1471 $fix['editor'] = $types['editor'];
1472 foreach (core_component::get_plugin_list('editor') as $plugin => $fulldir) {
1473 if (!$subtypes = core_component::get_subplugins('editor_'.$plugin)) {
1474 continue;
1475 }
1476 foreach ($subtypes as $subtype => $ignored) {
1477 $fix[$subtype] = $types[$subtype];
1478 }
1479 }
1480
1481 $fix['enrol'] = $types['enrol'];
1482 $fix['auth'] = $types['auth'];
1483 $fix['tool'] = $types['tool'];
1484 foreach (core_component::get_plugin_list('tool') as $plugin => $fulldir) {
1485 if (!$subtypes = core_component::get_subplugins('tool_'.$plugin)) {
1486 continue;
1487 }
1488 foreach ($subtypes as $subtype => $ignored) {
1489 $fix[$subtype] = $types[$subtype];
1490 }
1491 }
1492
1493 foreach ($types as $type => $path) {
1494 if (!isset($fix[$type])) {
1495 $fix[$type] = $path;
1496 }
1497 }
1498 return $fix;
1499 }
1500
1501 /**
1502 * Check if the given directory can be removed by the web server process.
1503 *
1504 * This recursively checks that the given directory and all its contents
1505 * it writable.
1506 *
1507 * @param string $fullpath
1508 * @return boolean
1509 */
1510 protected function is_directory_removable($fullpath) {
1511
1512 if (!is_writable($fullpath)) {
1513 return false;
1514 }
1515
1516 if (is_dir($fullpath)) {
1517 $handle = opendir($fullpath);
1518 } else {
1519 return false;
1520 }
1521
1522 $result = true;
1523
1524 while ($filename = readdir($handle)) {
1525
1526 if ($filename === '.' or $filename === '..') {
1527 continue;
1528 }
1529
1530 $subfilepath = $fullpath.'/'.$filename;
1531
1532 if (is_dir($subfilepath)) {
1533 $result = $result && $this->is_directory_removable($subfilepath);
1534
1535 } else {
1536 $result = $result && is_writable($subfilepath);
1537 }
1538 }
1539
1540 closedir($handle);
1541
1542 return $result;
1543 }
1544
1545 /**
1546 * Helper method that implements common uninstall prerequisites
1547 *
1548 * @param \core\plugininfo\base $pluginfo
1549 * @return bool
1550 */
1551 protected function common_uninstall_check(\core\plugininfo\base $pluginfo) {
1552
1553 if (!$pluginfo->is_uninstall_allowed()) {
1554 // The plugin's plugininfo class declares it should not be uninstalled.
1555 return false;
1556 }
1557
361feecd 1558 if ($pluginfo->get_status() === static::PLUGIN_STATUS_NEW) {
e87214bd
PS
1559 // The plugin is not installed. It should be either installed or removed from the disk.
1560 // Relying on this temporary state may be tricky.
1561 return false;
1562 }
1563
1564 if (method_exists($pluginfo, 'get_uninstall_url') and is_null($pluginfo->get_uninstall_url())) {
1565 // Backwards compatibility.
1566 debugging('\core\plugininfo\base subclasses should use is_uninstall_allowed() instead of returning null in get_uninstall_url()',
1567 DEBUG_DEVELOPER);
1568 return false;
1569 }
1570
1571 return true;
1572 }
1573}