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