PageRenderTime 62ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/pluginlib.php

https://bitbucket.org/bmbrands/swets
PHP | 1496 lines | 789 code | 242 blank | 465 comment | 121 complexity | 7f865c2dff9aebe47938802416ec77f4 MD5 | raw file
Possible License(s): Apache-2.0, GPL-3.0, GPL-2.0, LGPL-2.1, BSD-3-Clause, AGPL-3.0, MPL-2.0-no-copyleft-exception
  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. * Defines classes used for plugins management
  18. *
  19. * This library provides a unified interface to various plugin types in
  20. * Moodle. It is mainly used by the plugins management admin page and the
  21. * plugins check page during the upgrade.
  22. *
  23. * @package core
  24. * @subpackage admin
  25. * @copyright 2011 David Mudrak <david@moodle.com>
  26. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27. */
  28. defined('MOODLE_INTERNAL') || die();
  29. /**
  30. * Singleton class providing general plugins management functionality
  31. */
  32. class plugin_manager {
  33. /** the plugin is shipped with standard Moodle distribution */
  34. const PLUGIN_SOURCE_STANDARD = 'std';
  35. /** the plugin is added extension */
  36. const PLUGIN_SOURCE_EXTENSION = 'ext';
  37. /** the plugin uses neither database nor capabilities, no versions */
  38. const PLUGIN_STATUS_NODB = 'nodb';
  39. /** the plugin is up-to-date */
  40. const PLUGIN_STATUS_UPTODATE = 'uptodate';
  41. /** the plugin is about to be installed */
  42. const PLUGIN_STATUS_NEW = 'new';
  43. /** the plugin is about to be upgraded */
  44. const PLUGIN_STATUS_UPGRADE = 'upgrade';
  45. /** the version at the disk is lower than the one already installed */
  46. const PLUGIN_STATUS_DOWNGRADE = 'downgrade';
  47. /** the plugin is installed but missing from disk */
  48. const PLUGIN_STATUS_MISSING = 'missing';
  49. /** @var plugin_manager holds the singleton instance */
  50. protected static $singletoninstance;
  51. /** @var array of raw plugins information */
  52. protected $pluginsinfo = null;
  53. /** @var array of raw subplugins information */
  54. protected $subpluginsinfo = null;
  55. /**
  56. * Direct initiation not allowed, use the factory method {@link self::instance()}
  57. *
  58. * @todo we might want to specify just a single plugin type to work with
  59. */
  60. protected function __construct() {
  61. $this->get_plugins(true);
  62. }
  63. /**
  64. * Sorry, this is singleton
  65. */
  66. protected function __clone() {
  67. }
  68. /**
  69. * Factory method for this class
  70. *
  71. * @return plugin_manager the singleton instance
  72. */
  73. public static function instance() {
  74. global $CFG;
  75. if (is_null(self::$singletoninstance)) {
  76. self::$singletoninstance = new self();
  77. }
  78. return self::$singletoninstance;
  79. }
  80. /**
  81. * Returns a tree of known plugins and information about them
  82. *
  83. * @param bool $disablecache force reload, cache can be used otherwise
  84. * @return array
  85. */
  86. public function get_plugins($disablecache=false) {
  87. if ($disablecache or is_null($this->pluginsinfo)) {
  88. $this->pluginsinfo = array();
  89. $plugintypes = get_plugin_types();
  90. foreach ($plugintypes as $plugintype => $plugintyperootdir) {
  91. if (in_array($plugintype, array('base', 'general'))) {
  92. throw new coding_exception('Illegal usage of reserved word for plugin type');
  93. }
  94. if (class_exists('plugintype_' . $plugintype)) {
  95. $plugintypeclass = 'plugintype_' . $plugintype;
  96. } else {
  97. $plugintypeclass = 'plugintype_general';
  98. }
  99. if (!in_array('plugintype_interface', class_implements($plugintypeclass))) {
  100. throw new coding_exception('Class ' . $plugintypeclass . ' must implement plugintype_interface');
  101. }
  102. $plugins = call_user_func(array($plugintypeclass, 'get_plugins'), $plugintype, $plugintyperootdir, $plugintypeclass);
  103. $this->pluginsinfo[$plugintype] = $plugins;
  104. }
  105. }
  106. return $this->pluginsinfo;
  107. }
  108. /**
  109. * Returns list of plugins that define their subplugins and information about them
  110. *
  111. * At the moment, only activity modules can define subplugins.
  112. *
  113. * @param double $disablecache force reload, cache can be used otherwise
  114. * @return array
  115. */
  116. public function get_subplugins($disablecache=false) {
  117. if ($disablecache or is_null($this->subpluginsinfo)) {
  118. $this->subpluginsinfo = array();
  119. $mods = get_plugin_list('mod');
  120. foreach ($mods as $mod => $moddir) {
  121. $modsubplugins = array();
  122. if (file_exists($moddir . '/db/subplugins.php')) {
  123. include($moddir . '/db/subplugins.php');
  124. foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
  125. $subplugin = new stdClass();
  126. $subplugin->type = $subplugintype;
  127. $subplugin->typerootdir = $subplugintyperootdir;
  128. $modsubplugins[$subplugintype] = $subplugin;
  129. }
  130. $this->subpluginsinfo['mod_' . $mod] = $modsubplugins;
  131. }
  132. }
  133. }
  134. return $this->subpluginsinfo;
  135. }
  136. /**
  137. * Returns the name of the plugin that defines the given subplugin type
  138. *
  139. * If the given subplugin type is not actually a subplugin, returns false.
  140. *
  141. * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
  142. * @return false|string the name of the parent plugin, eg. mod_workshop
  143. */
  144. public function get_parent_of_subplugin($subplugintype) {
  145. $parent = false;
  146. foreach ($this->get_subplugins() as $pluginname => $subplugintypes) {
  147. if (isset($subplugintypes[$subplugintype])) {
  148. $parent = $pluginname;
  149. break;
  150. }
  151. }
  152. return $parent;
  153. }
  154. /**
  155. * Returns a localized name of a given plugin
  156. *
  157. * @param string $plugin name of the plugin, eg mod_workshop or auth_ldap
  158. * @return string
  159. */
  160. public function plugin_name($plugin) {
  161. list($type, $name) = normalize_component($plugin);
  162. return $this->pluginsinfo[$type][$name]->displayname;
  163. }
  164. /**
  165. * Returns a localized name of a plugin type in plural form
  166. *
  167. * Most plugin types define their names in core_plugin lang file. In case of subplugins,
  168. * we try to ask the parent plugin for the name. In the worst case, we will return
  169. * the value of the passed $type parameter.
  170. *
  171. * @param string $type the type of the plugin, e.g. mod or workshopform
  172. * @return string
  173. */
  174. public function plugintype_name_plural($type) {
  175. if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
  176. // for most plugin types, their names are defined in core_plugin lang file
  177. return get_string('type_' . $type . '_plural', 'core_plugin');
  178. } else if ($parent = $this->get_parent_of_subplugin($type)) {
  179. // if this is a subplugin, try to ask the parent plugin for the name
  180. if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
  181. return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
  182. } else {
  183. return $this->plugin_name($parent) . ' / ' . $type;
  184. }
  185. } else {
  186. return $type;
  187. }
  188. }
  189. /**
  190. * Defines a white list of all plugins shipped in the standard Moodle distribution
  191. *
  192. * @return false|array array of standard plugins or false if the type is unknown
  193. */
  194. public static function standard_plugins_list($type) {
  195. static $standard_plugins = array(
  196. 'assignment' => array(
  197. 'offline', 'online', 'upload', 'uploadsingle'
  198. ),
  199. 'auth' => array(
  200. 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
  201. 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
  202. 'shibboleth', 'webservice'
  203. ),
  204. 'block' => array(
  205. 'activity_modules', 'admin_bookmarks', 'blog_menu',
  206. 'blog_recent', 'blog_tags', 'calendar_month',
  207. 'calendar_upcoming', 'comments', 'community',
  208. 'completionstatus', 'course_list', 'course_overview',
  209. 'course_summary', 'feedback', 'glossary_random', 'html',
  210. 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
  211. 'navigation', 'news_items', 'online_users', 'participants',
  212. 'private_files', 'quiz_results', 'recent_activity',
  213. 'rss_client', 'search', 'search_forums', 'section_links',
  214. 'selfcompletion', 'settings', 'site_main_menu',
  215. 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
  216. ),
  217. 'coursereport' => array(
  218. 'completion', 'log', 'outline', 'participation', 'progress', 'stats'
  219. ),
  220. 'datafield' => array(
  221. 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
  222. 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
  223. ),
  224. 'datapreset' => array(
  225. 'imagegallery'
  226. ),
  227. 'editor' => array(
  228. 'textarea', 'tinymce'
  229. ),
  230. 'enrol' => array(
  231. 'authorize', 'category', 'cohort', 'database', 'flatfile',
  232. 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
  233. 'paypal', 'self'
  234. ),
  235. 'filter' => array(
  236. 'activitynames', 'algebra', 'censor', 'emailprotect',
  237. 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
  238. 'urltolink', 'mod_data', 'mod_glossary'
  239. ),
  240. 'format' => array(
  241. 'scorm', 'social', 'topics', 'weeks'
  242. ),
  243. 'gradeexport' => array(
  244. 'ods', 'txt', 'xls', 'xml'
  245. ),
  246. 'gradeimport' => array(
  247. 'csv', 'xml'
  248. ),
  249. 'gradereport' => array(
  250. 'grader', 'outcomes', 'overview', 'user'
  251. ),
  252. 'local' => array(
  253. 'qeupgradehelper'
  254. ),
  255. 'message' => array(
  256. 'email', 'jabber', 'popup'
  257. ),
  258. 'mnetservice' => array(
  259. 'enrol'
  260. ),
  261. 'mod' => array(
  262. 'assignment', 'chat', 'choice', 'data', 'feedback', 'folder',
  263. 'forum', 'glossary', 'imscp', 'label', 'lesson', 'page',
  264. 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
  265. ),
  266. 'plagiarism' => array(
  267. ),
  268. 'portfolio' => array(
  269. 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
  270. ),
  271. 'profilefield' => array(
  272. 'checkbox', 'datetime', 'menu', 'text', 'textarea'
  273. ),
  274. 'qbehaviour' => array(
  275. 'adaptive', 'adaptivenopenalty', 'deferredcbm',
  276. 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
  277. 'informationitem', 'interactive', 'interactivecountback',
  278. 'manualgraded', 'missing'
  279. ),
  280. 'qformat' => array(
  281. 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
  282. 'learnwise', 'missingword', 'multianswer', 'qti_two', 'webct',
  283. 'xhtml', 'xml'
  284. ),
  285. 'qtype' => array(
  286. 'calculated', 'calculatedmulti', 'calculatedsimple',
  287. 'description', 'essay', 'match', 'missingtype', 'multianswer',
  288. 'multichoice', 'numerical', 'random', 'randomsamatch',
  289. 'shortanswer', 'truefalse'
  290. ),
  291. 'quiz' => array(
  292. 'grading', 'overview', 'responses', 'statistics'
  293. ),
  294. 'report' => array(
  295. 'backups', 'capability', 'configlog', 'courseoverview',
  296. 'customlang', 'log', 'profiling', 'questioninstances',
  297. 'security', 'spamcleaner', 'stats', 'unittest', 'unsuproles'
  298. ),
  299. 'repository' => array(
  300. 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'filesystem',
  301. 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
  302. 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
  303. 'wikimedia', 'youtube'
  304. ),
  305. 'theme' => array(
  306. 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
  307. 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
  308. 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero',
  309. 'overlay', 'serenity', 'sky_high', 'splash', 'standard',
  310. 'standardold'
  311. ),
  312. 'webservice' => array(
  313. 'amf', 'rest', 'soap', 'xmlrpc'
  314. ),
  315. 'workshopallocation' => array(
  316. 'manual', 'random'
  317. ),
  318. 'workshopeval' => array(
  319. 'best'
  320. ),
  321. 'workshopform' => array(
  322. 'accumulative', 'comments', 'numerrors', 'rubric'
  323. )
  324. );
  325. if (isset($standard_plugins[$type])) {
  326. return $standard_plugins[$type];
  327. } else {
  328. return false;
  329. }
  330. }
  331. }
  332. /**
  333. * All classes that represent a plugin of some type must implement this interface
  334. */
  335. interface plugintype_interface {
  336. /**
  337. * Gathers and returns the information about all plugins of the given type
  338. *
  339. * Passing the parameter $typeclass allows us to reach the same effect as with the
  340. * late binding in PHP 5.3. Once PHP 5.3 is required, we can refactor this to use
  341. * {@example $plugin = new static();} instead of {@example $plugin = new $typeclass()}
  342. *
  343. * @param string $type the name of the plugintype, eg. mod, auth or workshopform
  344. * @param string $typerootdir full path to the location of the plugin dir
  345. * @param string $typeclass the name of the actually called class
  346. * @return array of plugintype classes, indexed by the plugin name
  347. */
  348. public static function get_plugins($type, $typerootdir, $typeclass);
  349. /**
  350. * Sets $displayname property to a localized name of the plugin
  351. *
  352. * @return void
  353. */
  354. public function set_display_name();
  355. /**
  356. * Sets $versiondisk property to a numerical value representing the
  357. * version of the plugin's source code.
  358. *
  359. * If the value is null after calling this method, either the plugin
  360. * does not use versioning (typically does not have any database
  361. * data) or is missing from disk.
  362. *
  363. * @return void
  364. */
  365. public function set_version_disk();
  366. /**
  367. * Sets $versiondb property to a numerical value representing the
  368. * currently installed version of the plugin.
  369. *
  370. * If the value is null after calling this method, either the plugin
  371. * does not use versioning (typically does not have any database
  372. * data) or has not been installed yet.
  373. *
  374. * @return void
  375. */
  376. public function set_version_db();
  377. /**
  378. * Sets $versionrequires property to a numerical value representing
  379. * the version of Moodle core that this plugin requires.
  380. *
  381. * @return void
  382. */
  383. public function set_version_requires();
  384. /**
  385. * Sets $source property to one of plugin_manager::PLUGIN_SOURCE_xxx
  386. * constants.
  387. *
  388. * If the property's value is null after calling this method, then
  389. * the type of the plugin has not been recognized and you should throw
  390. * an exception.
  391. *
  392. * @return void
  393. */
  394. public function set_source();
  395. /**
  396. * Returns true if the plugin is shipped with the official distribution
  397. * of the current Moodle version, false otherwise.
  398. *
  399. * @return bool
  400. */
  401. public function is_standard();
  402. /**
  403. * Returns the status of the plugin
  404. *
  405. * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
  406. */
  407. public function get_status();
  408. /**
  409. * Returns the information about plugin availability
  410. *
  411. * True means that the plugin is enabled. False means that the plugin is
  412. * disabled. Null means that the information is not available, or the
  413. * plugin does not support configurable availability or the availability
  414. * can not be changed.
  415. *
  416. * @return null|bool
  417. */
  418. public function is_enabled();
  419. /**
  420. * Returns the URL of the plugin settings screen
  421. *
  422. * Null value means that the plugin either does not have the settings screen
  423. * or its location is not available via this library.
  424. *
  425. * @return null|moodle_url
  426. */
  427. public function get_settings_url();
  428. /**
  429. * Returns the URL of the screen where this plugin can be uninstalled
  430. *
  431. * Visiting that URL must be safe, that is a manual confirmation is needed
  432. * for actual uninstallation of the plugin. Null value means that the
  433. * plugin either does not support uninstallation, or does not require any
  434. * database cleanup or the location of the screen is not available via this
  435. * library.
  436. *
  437. * @return null|moodle_url
  438. */
  439. public function get_uninstall_url();
  440. /**
  441. * Returns relative directory of the plugin with heading '/'
  442. *
  443. * @example /mod/workshop
  444. * @return string
  445. */
  446. public function get_dir();
  447. }
  448. /**
  449. * Defines public properties that all plugintype classes must have
  450. * and provides default implementation of required methods.
  451. */
  452. abstract class plugintype_base {
  453. /** @var string the plugintype name, eg. mod, auth or workshopform */
  454. public $type;
  455. /** @var string full path to the location of all the plugins of this type */
  456. public $typerootdir;
  457. /** @var string the plugin name, eg. assignment, ldap */
  458. public $name;
  459. /** @var string the localized plugin name */
  460. public $displayname;
  461. /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
  462. public $source;
  463. /** @var fullpath to the location of this plugin */
  464. public $rootdir;
  465. /** @var int|string the version of the plugin's source code */
  466. public $versiondisk;
  467. /** @var int|string the version of the installed plugin */
  468. public $versiondb;
  469. /** @var int|float|string required version of Moodle core */
  470. public $versionrequires;
  471. /** @var int number of instances of the plugin - not supported yet */
  472. public $instances;
  473. /** @var int order of the plugin among other plugins of the same type - not supported yet */
  474. public $sortorder;
  475. /**
  476. * @see plugintype_interface::get_plugins()
  477. */
  478. public static function get_plugins($type, $typerootdir, $typeclass) {
  479. // get the information about plugins at the disk
  480. $plugins = get_plugin_list($type);
  481. $ondisk = array();
  482. foreach ($plugins as $pluginname => $pluginrootdir) {
  483. $plugin = new $typeclass();
  484. $plugin->type = $type;
  485. $plugin->typerootdir = $typerootdir;
  486. $plugin->name = $pluginname;
  487. $plugin->rootdir = $pluginrootdir;
  488. $plugin->set_display_name();
  489. $plugin->set_version_disk();
  490. $plugin->set_version_db();
  491. $plugin->set_version_requires();
  492. $plugin->set_source();
  493. $ondisk[$pluginname] = $plugin;
  494. }
  495. return $ondisk;
  496. }
  497. /**
  498. * @see plugintype_interface::set_display_name()
  499. */
  500. public function set_display_name() {
  501. if (! get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
  502. $this->displayname = '[pluginname,' . $this->type . '_' . $this->name . ']';
  503. } else {
  504. $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
  505. }
  506. }
  507. /**
  508. * @see plugintype_interface::set_version_disk()
  509. */
  510. public function set_version_disk() {
  511. if (empty($this->rootdir)) {
  512. return;
  513. }
  514. $versionfile = $this->rootdir . '/version.php';
  515. if (is_readable($versionfile)) {
  516. include($versionfile);
  517. if (isset($plugin->version)) {
  518. $this->versiondisk = $plugin->version;
  519. }
  520. }
  521. }
  522. /**
  523. * @see plugintype_interface::set_version_db()
  524. */
  525. public function set_version_db() {
  526. if ($ver = self::get_version_from_config_plugins($this->type . '_' . $this->name)) {
  527. $this->versiondb = $ver;
  528. }
  529. }
  530. /**
  531. * @see plugintype_interface::set_version_requires()
  532. */
  533. public function set_version_requires() {
  534. if (empty($this->rootdir)) {
  535. return;
  536. }
  537. $versionfile = $this->rootdir . '/version.php';
  538. if (is_readable($versionfile)) {
  539. include($versionfile);
  540. if (isset($plugin->requires)) {
  541. $this->versionrequires = $plugin->requires;
  542. }
  543. }
  544. }
  545. /**
  546. * @see plugintype_interface::set_source()
  547. */
  548. public function set_source() {
  549. $standard = plugin_manager::standard_plugins_list($this->type);
  550. if ($standard !== false) {
  551. $standard = array_flip($standard);
  552. if (isset($standard[$this->name])) {
  553. $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
  554. } else {
  555. $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
  556. }
  557. }
  558. }
  559. /**
  560. * @see plugintype_interface::is_standard()
  561. */
  562. public function is_standard() {
  563. return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
  564. }
  565. /**
  566. * @see plugintype_interface::get_status()
  567. */
  568. public function get_status() {
  569. if (is_null($this->versiondb) and is_null($this->versiondisk)) {
  570. return plugin_manager::PLUGIN_STATUS_NODB;
  571. } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
  572. return plugin_manager::PLUGIN_STATUS_NEW;
  573. } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
  574. return plugin_manager::PLUGIN_STATUS_MISSING;
  575. } else if ((string)$this->versiondb === (string)$this->versiondisk) {
  576. return plugin_manager::PLUGIN_STATUS_UPTODATE;
  577. } else if ($this->versiondb < $this->versiondisk) {
  578. return plugin_manager::PLUGIN_STATUS_UPGRADE;
  579. } else if ($this->versiondb > $this->versiondisk) {
  580. return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
  581. } else {
  582. // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
  583. throw new coding_exception('Unable to determine plugin state, check the plugin versions');
  584. }
  585. }
  586. /**
  587. * @see plugintype_interface::is_enabled()
  588. */
  589. public function is_enabled() {
  590. return null;
  591. }
  592. /**
  593. * @see plugintype_interface::get_settings_url()
  594. */
  595. public function get_settings_url() {
  596. return null;
  597. }
  598. /**
  599. * @see plugintype_interface::get_uninstall_url()
  600. */
  601. public function get_uninstall_url() {
  602. return null;
  603. }
  604. /**
  605. * @see plugintype_interface::get_dir()
  606. */
  607. public function get_dir() {
  608. global $CFG;
  609. return substr($this->rootdir, strlen($CFG->dirroot));
  610. }
  611. /**
  612. * Provides access to plugin versions from {config_plugins}
  613. *
  614. * @param string $plugin plugin name
  615. * @param double $disablecache optional, defaults to false
  616. * @return int|false the stored value or false if not found
  617. */
  618. protected function get_version_from_config_plugins($plugin, $disablecache=false) {
  619. global $DB;
  620. static $pluginversions = null;
  621. if (is_null($pluginversions) or $disablecache) {
  622. $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
  623. }
  624. if (!array_key_exists($plugin, $pluginversions)) {
  625. return false;
  626. }
  627. return $pluginversions[$plugin];
  628. }
  629. }
  630. /**
  631. * General class for all plugin types that do not have their own class
  632. */
  633. class plugintype_general extends plugintype_base implements plugintype_interface {
  634. }
  635. /**
  636. * Class for page side blocks
  637. */
  638. class plugintype_block extends plugintype_base implements plugintype_interface {
  639. /**
  640. * @see plugintype_interface::get_plugins()
  641. */
  642. public static function get_plugins($type, $typerootdir, $typeclass) {
  643. // get the information about blocks at the disk
  644. $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
  645. // add blocks missing from disk
  646. $blocksinfo = self::get_blocks_info();
  647. foreach ($blocksinfo as $blockname => $blockinfo) {
  648. if (isset($blocks[$blockname])) {
  649. continue;
  650. }
  651. $plugin = new $typeclass();
  652. $plugin->type = $type;
  653. $plugin->typerootdir = $typerootdir;
  654. $plugin->name = $blockname;
  655. $plugin->rootdir = null;
  656. $plugin->displayname = $blockname;
  657. $plugin->versiondb = $blockinfo->version;
  658. $plugin->set_source();
  659. $blocks[$blockname] = $plugin;
  660. }
  661. return $blocks;
  662. }
  663. /**
  664. * @see plugintype_interface::set_display_name()
  665. */
  666. public function set_display_name() {
  667. if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
  668. $this->displayname = get_string('pluginname', 'block_' . $this->name);
  669. } else if (($block = block_instance($this->name)) !== false) {
  670. $this->displayname = $block->get_title();
  671. } else {
  672. parent::set_display_name();
  673. }
  674. }
  675. /**
  676. * @see plugintype_interface::set_version_db()
  677. */
  678. public function set_version_db() {
  679. global $DB;
  680. $blocksinfo = self::get_blocks_info();
  681. if (isset($blocksinfo[$this->name]->version)) {
  682. $this->versiondb = $blocksinfo[$this->name]->version;
  683. }
  684. }
  685. /**
  686. * @see plugintype_interface::is_enabled()
  687. */
  688. public function is_enabled() {
  689. $blocksinfo = self::get_blocks_info();
  690. if (isset($blocksinfo[$this->name]->visible)) {
  691. if ($blocksinfo[$this->name]->visible) {
  692. return true;
  693. } else {
  694. return false;
  695. }
  696. } else {
  697. return parent::is_enabled();
  698. }
  699. }
  700. /**
  701. * @see plugintype_interface::get_settings_url()
  702. */
  703. public function get_settings_url() {
  704. if (($block = block_instance($this->name)) === false) {
  705. return parent::get_settings_url();
  706. } else if ($block->has_config()) {
  707. if (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php')) {
  708. return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
  709. } else {
  710. $blocksinfo = self::get_blocks_info();
  711. return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
  712. }
  713. } else {
  714. return parent::get_settings_url();
  715. }
  716. }
  717. /**
  718. * @see plugintype_interface::get_uninstall_url()
  719. */
  720. public function get_uninstall_url() {
  721. $blocksinfo = self::get_blocks_info();
  722. return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
  723. }
  724. /**
  725. * Provides access to the records in {block} table
  726. *
  727. * @param bool $disablecache do not use internal static cache
  728. * @return array array of stdClasses
  729. */
  730. protected static function get_blocks_info($disablecache=false) {
  731. global $DB;
  732. static $blocksinfocache = null;
  733. if (is_null($blocksinfocache) or $disablecache) {
  734. $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
  735. }
  736. return $blocksinfocache;
  737. }
  738. }
  739. /**
  740. * Class for text filters
  741. */
  742. class plugintype_filter extends plugintype_base implements plugintype_interface {
  743. /**
  744. * @see plugintype_interface::get_plugins()
  745. */
  746. public static function get_plugins($type, $typerootdir, $typeclass) {
  747. global $CFG, $DB;
  748. $filters = array();
  749. // get the list of filters from both /filter and /mod location
  750. $installed = filter_get_all_installed();
  751. foreach ($installed as $filterlegacyname => $displayname) {
  752. $plugin = new $typeclass();
  753. $plugin->type = $type;
  754. $plugin->typerootdir = $typerootdir;
  755. $plugin->name = self::normalize_legacy_name($filterlegacyname);
  756. $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
  757. $plugin->displayname = $displayname;
  758. $plugin->set_version_disk();
  759. $plugin->set_version_db();
  760. $plugin->set_version_requires();
  761. $plugin->set_source();
  762. $filters[$plugin->name] = $plugin;
  763. }
  764. $globalstates = self::get_global_states();
  765. if ($DB->get_manager()->table_exists('filter_active')) {
  766. // if we're upgrading from 1.9, the table does not exist yet
  767. // if it does, make sure that all installed filters are registered
  768. $needsreload = false;
  769. foreach (array_keys($installed) as $filterlegacyname) {
  770. if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
  771. filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
  772. $needsreload = true;
  773. }
  774. }
  775. if ($needsreload) {
  776. $globalstates = self::get_global_states(true);
  777. }
  778. }
  779. // make sure that all registered filters are installed, just in case
  780. foreach ($globalstates as $name => $info) {
  781. if (!isset($filters[$name])) {
  782. // oops, there is a record in filter_active but the filter is not installed
  783. $plugin = new $typeclass();
  784. $plugin->type = $type;
  785. $plugin->typerootdir = $typerootdir;
  786. $plugin->name = $name;
  787. $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
  788. $plugin->displayname = $info->legacyname;
  789. $plugin->set_version_db();
  790. if (is_null($plugin->versiondb)) {
  791. // this is a hack to stimulate 'Missing from disk' error
  792. // because $plugin->versiondisk will be null !== false
  793. $plugin->versiondb = false;
  794. }
  795. $filters[$plugin->name] = $plugin;
  796. }
  797. }
  798. return $filters;
  799. }
  800. /**
  801. * @see plugintype_interface::set_display_name()
  802. */
  803. public function set_display_name() {
  804. // do nothing, the name is set in self::get_plugins()
  805. }
  806. /**
  807. * @see plugintype_interface::set_version_disk()
  808. */
  809. public function set_version_disk() {
  810. if (strpos($this->name, 'mod_') === 0) {
  811. // filters bundled with modules do not use versioning
  812. return;
  813. }
  814. return parent::set_version_disk();
  815. }
  816. /**
  817. * @see plugintype_interface::set_version_requires()
  818. */
  819. public function set_version_requires() {
  820. if (strpos($this->name, 'mod_') === 0) {
  821. // filters bundled with modules do not use versioning
  822. return;
  823. }
  824. return parent::set_version_requires();
  825. }
  826. /**
  827. * @see plugintype_interface::is_enabled()
  828. */
  829. public function is_enabled() {
  830. $globalstates = self::get_global_states();
  831. foreach ($globalstates as $filterlegacyname => $info) {
  832. $name = self::normalize_legacy_name($filterlegacyname);
  833. if ($name === $this->name) {
  834. if ($info->active == TEXTFILTER_DISABLED) {
  835. return false;
  836. } else {
  837. // it may be 'On' or 'Off, but available'
  838. return null;
  839. }
  840. }
  841. }
  842. return null;
  843. }
  844. /**
  845. * @see plugintype_interface::get_settings_url()
  846. */
  847. public function get_settings_url() {
  848. $globalstates = self::get_global_states();
  849. $legacyname = $globalstates[$this->name]->legacyname;
  850. if (filter_has_global_settings($legacyname)) {
  851. return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
  852. } else {
  853. return null;
  854. }
  855. }
  856. /**
  857. * @see plugintype_interface::get_uninstall_url()
  858. */
  859. public function get_uninstall_url() {
  860. if (strpos($this->name, 'mod_') === 0) {
  861. return null;
  862. } else {
  863. $globalstates = self::get_global_states();
  864. $legacyname = $globalstates[$this->name]->legacyname;
  865. return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
  866. }
  867. }
  868. /**
  869. * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
  870. *
  871. * @param string $legacyfiltername legacy filter name
  872. * @return string frankenstyle-like name
  873. */
  874. protected static function normalize_legacy_name($legacyfiltername) {
  875. $name = str_replace('/', '_', $legacyfiltername);
  876. if (strpos($name, 'filter_') === 0) {
  877. $name = substr($name, 7);
  878. if (empty($name)) {
  879. throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
  880. }
  881. }
  882. return $name;
  883. }
  884. /**
  885. * Provides access to the results of {@link filter_get_global_states()}
  886. * but indexed by the normalized filter name
  887. *
  888. * The legacy filter name is available as ->legacyname property.
  889. *
  890. * @param bool $disablecache
  891. * @return array
  892. */
  893. protected static function get_global_states($disablecache=false) {
  894. global $DB;
  895. static $globalstatescache = null;
  896. if ($disablecache or is_null($globalstatescache)) {
  897. if (!$DB->get_manager()->table_exists('filter_active')) {
  898. // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
  899. // does not exist yet
  900. $globalstatescache = array();
  901. } else {
  902. foreach (filter_get_global_states() as $legacyname => $info) {
  903. $name = self::normalize_legacy_name($legacyname);
  904. $filterinfo = new stdClass();
  905. $filterinfo->legacyname = $legacyname;
  906. $filterinfo->active = $info->active;
  907. $filterinfo->sortorder = $info->sortorder;
  908. $globalstatescache[$name] = $filterinfo;
  909. }
  910. }
  911. }
  912. return $globalstatescache;
  913. }
  914. }
  915. /**
  916. * Class for activity modules
  917. */
  918. class plugintype_mod extends plugintype_base implements plugintype_interface {
  919. /**
  920. * @see plugintype_interface::get_plugins()
  921. */
  922. public static function get_plugins($type, $typerootdir, $typeclass) {
  923. // get the information about plugins at the disk
  924. $modules = parent::get_plugins($type, $typerootdir, $typeclass);
  925. // add modules missing from disk
  926. $modulesinfo = self::get_modules_info();
  927. foreach ($modulesinfo as $modulename => $moduleinfo) {
  928. if (isset($modules[$modulename])) {
  929. continue;
  930. }
  931. $plugin = new $typeclass();
  932. $plugin->type = $type;
  933. $plugin->typerootdir = $typerootdir;
  934. $plugin->name = $modulename;
  935. $plugin->rootdir = null;
  936. $plugin->displayname = $modulename;
  937. $plugin->versiondb = $moduleinfo->version;
  938. $plugin->set_source();
  939. $modules[$modulename] = $plugin;
  940. }
  941. return $modules;
  942. }
  943. /**
  944. * @see plugintype_interface::set_display_name()
  945. */
  946. public function set_display_name() {
  947. if (get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
  948. $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
  949. } else {
  950. $this->displayname = get_string('modulename', $this->type . '_' . $this->name);
  951. }
  952. }
  953. /**
  954. * @see plugintype_interface::set_version_disk()
  955. */
  956. public function set_version_disk() {
  957. if (empty($this->rootdir)) {
  958. return;
  959. }
  960. $versionfile = $this->rootdir . '/version.php';
  961. if (is_readable($versionfile)) {
  962. include($versionfile);
  963. if (isset($module->version)) {
  964. $this->versiondisk = $module->version;
  965. }
  966. }
  967. }
  968. /**
  969. * @see plugintype_interface::set_version_db()
  970. */
  971. public function set_version_db() {
  972. global $DB;
  973. $modulesinfo = self::get_modules_info();
  974. if (isset($modulesinfo[$this->name]->version)) {
  975. $this->versiondb = $modulesinfo[$this->name]->version;
  976. }
  977. }
  978. /**
  979. * @see plugintype_interface::set_version_requires()
  980. */
  981. public function set_version_requires() {
  982. if (empty($this->rootdir)) {
  983. return;
  984. }
  985. $versionfile = $this->rootdir . '/version.php';
  986. if (is_readable($versionfile)) {
  987. include($versionfile);
  988. if (isset($module->requires)) {
  989. $this->versionrequires = $module->requires;
  990. }
  991. }
  992. }
  993. /**
  994. * @see plugintype_interface::is_enabled()
  995. */
  996. public function is_enabled() {
  997. $modulesinfo = self::get_modules_info();
  998. if (isset($modulesinfo[$this->name]->visible)) {
  999. if ($modulesinfo[$this->name]->visible) {
  1000. return true;
  1001. } else {
  1002. return false;
  1003. }
  1004. } else {
  1005. return parent::is_enabled();
  1006. }
  1007. }
  1008. /**
  1009. * @see plugintype_interface::get_settings_url()
  1010. */
  1011. public function get_settings_url() {
  1012. if (!empty($this->rootdir) and (file_exists($this->rootdir . '/settings.php') or file_exists($this->rootdir . '/settingstree.php'))) {
  1013. return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
  1014. } else {
  1015. return parent::get_settings_url();
  1016. }
  1017. }
  1018. /**
  1019. * @see plugintype_interface::get_uninstall_url()
  1020. */
  1021. public function get_uninstall_url() {
  1022. if ($this->name !== 'forum') {
  1023. return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
  1024. } else {
  1025. return null;
  1026. }
  1027. }
  1028. /**
  1029. * Provides access to the records in {modules} table
  1030. *
  1031. * @param bool $disablecache do not use internal static cache
  1032. * @return array array of stdClasses
  1033. */
  1034. protected static function get_modules_info($disablecache=false) {
  1035. global $DB;
  1036. static $modulesinfocache = null;
  1037. if (is_null($modulesinfocache) or $disablecache) {
  1038. $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
  1039. }
  1040. return $modulesinfocache;
  1041. }
  1042. }
  1043. /**
  1044. * Class for question types
  1045. */
  1046. class plugintype_qtype extends plugintype_base implements plugintype_interface {
  1047. /**
  1048. * @see plugintype_interface::set_display_name()
  1049. */
  1050. public function set_display_name() {
  1051. $this->displayname = get_string($this->name, 'qtype_' . $this->name);
  1052. }
  1053. }
  1054. /**
  1055. * Class for question formats
  1056. */
  1057. class plugintype_qformat extends plugintype_base implements plugintype_interface {
  1058. /**
  1059. * @see plugintype_interface::set_display_name()
  1060. */
  1061. public function set_display_name() {
  1062. $this->displayname = get_string($this->name, 'qformat_' . $this->name);
  1063. }
  1064. }
  1065. /**
  1066. * Class for authentication plugins
  1067. */
  1068. class plugintype_auth extends plugintype_base implements plugintype_interface {
  1069. /**
  1070. * @see plugintype_interface::is_enabled()
  1071. */
  1072. public function is_enabled() {
  1073. global $CFG;
  1074. /** @var null|array list of enabled authentication plugins */
  1075. static $enabled = null;
  1076. if (in_array($this->name, array('nologin', 'manual'))) {
  1077. // these two are always enabled and can't be disabled
  1078. return null;
  1079. }
  1080. if (is_null($enabled)) {
  1081. $enabled = explode(',', $CFG->auth);
  1082. }
  1083. return isset($enabled[$this->name]);
  1084. }
  1085. /**
  1086. * @see plugintype_interface::get_settings_url()
  1087. */
  1088. public function get_settings_url() {
  1089. if (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php')) {
  1090. return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
  1091. } else {
  1092. return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
  1093. }
  1094. }
  1095. }
  1096. /**
  1097. * Class for enrolment plugins
  1098. */
  1099. class plugintype_enrol extends plugintype_base implements plugintype_interface {
  1100. /**
  1101. * We do not actually need whole enrolment classes here so we do not call
  1102. * {@link enrol_get_plugins()}. Note that this may produce slightly different
  1103. * results, for example if the enrolment plugin does not contain lib.php
  1104. * but it is listed in $CFG->enrol_plugins_enabled
  1105. *
  1106. * @see plugintype_interface::is_enabled()
  1107. */
  1108. public function is_enabled() {
  1109. global $CFG;
  1110. /** @var null|array list of enabled enrolment plugins */
  1111. static $enabled = null;
  1112. if (is_null($enabled)) {
  1113. $enabled = explode(',', $CFG->enrol_plugins_enabled);
  1114. }
  1115. return isset($enabled[$this->name]);
  1116. }
  1117. /**
  1118. * @see plugintype_interface::get_settings_url()
  1119. */
  1120. public function get_settings_url() {
  1121. if ($this->is_enabled() or (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php'))) {
  1122. return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
  1123. } else {
  1124. return parent::get_settings_url();
  1125. }
  1126. }
  1127. /**
  1128. * @see plugintype_interface::get_uninstall_url()
  1129. */
  1130. public function get_uninstall_url() {
  1131. return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
  1132. }
  1133. }
  1134. /**
  1135. * Class for messaging processors
  1136. */
  1137. class plugintype_message extends plugintype_base implements plugintype_interface {
  1138. /**
  1139. * @see plugintype_interface::get_settings_url()
  1140. */
  1141. public function get_settings_url() {
  1142. if ($this->name === 'jabber') {
  1143. return new moodle_url('/admin/settings.php', array('section' => 'jabber'));
  1144. }
  1145. if ($this->name === 'email') {
  1146. return new moodle_url('/admin/settings.php', array('section' => 'mail'));
  1147. }
  1148. }
  1149. }
  1150. /**
  1151. * Class for repositories
  1152. */
  1153. class plugintype_repository extends plugintype_base implements plugintype_interface {
  1154. /**
  1155. * @see plugintype_interface::is_enabled()
  1156. */
  1157. public function is_enabled() {
  1158. $enabled = self::get_enabled_repositories();
  1159. return isset($enabled[$this->name]);
  1160. }
  1161. /**
  1162. * @see plugintype_interface::get_settings_url()
  1163. */
  1164. public function get_settings_url() {
  1165. if ($this->is_enabled()) {
  1166. return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
  1167. } else {
  1168. return parent::get_settings_url();
  1169. }
  1170. }
  1171. /**
  1172. * Provides access to the records in {repository} table
  1173. *
  1174. * @param bool $disablecache do not use internal static cache
  1175. * @return array array of stdClasses
  1176. */
  1177. protected static function get_enabled_repositories($disablecache=false) {
  1178. global $DB;
  1179. static $repositories = null;
  1180. if (is_null($repositories) or $disablecache) {
  1181. $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
  1182. }
  1183. return $repositories;
  1184. }
  1185. }
  1186. /**
  1187. * Class for portfolios
  1188. */
  1189. class plugintype_portfolio extends plugintype_base implements plugintype_interface {
  1190. /**
  1191. * @see plugintype_interface::is_enabled()
  1192. */
  1193. public function is_enabled() {
  1194. $enabled = self::get_enabled_portfolios();
  1195. return isset($enabled[$this->name]);
  1196. }
  1197. /**
  1198. * Provides access to the records in {portfolio_instance} table
  1199. *
  1200. * @param bool $disablecache do not use internal static cache
  1201. * @return array array of stdClasses
  1202. */
  1203. protected static function get_enabled_portfolios($disablecache=false) {
  1204. global $DB;
  1205. static $portfolios = null;
  1206. if (is_null($portfolios) or $disablecache) {
  1207. $portfolios = array();
  1208. $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
  1209. foreach ($instances as $instance) {
  1210. if (isset($portfolios[$instance->plugin])) {
  1211. if ($instance->visible) {
  1212. $portfolios[$instance->plugin]->visible = $instance->visible;
  1213. }
  1214. } else {
  1215. $portfolios[$instance->plugin] = $instance;
  1216. }
  1217. }
  1218. }
  1219. return $portfolios;
  1220. }
  1221. }
  1222. /**
  1223. * Class for themes
  1224. */
  1225. class plugintype_theme extends plugintype_base implements plugintype_interface {
  1226. /**
  1227. * @see plugintype_interface::is_enabled()
  1228. */
  1229. public function is_enabled() {
  1230. global $CFG;
  1231. if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
  1232. (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
  1233. return true;
  1234. } else {
  1235. return parent::is_enabled();
  1236. }
  1237. }
  1238. }
  1239. /**
  1240. * Class representing an MNet service
  1241. */
  1242. class plugintype_mnetservice extends plugintype_base implements plugintype_interface {
  1243. /**
  1244. * @see plugintype_interface::is_enabled()
  1245. */
  1246. public function is_enabled() {
  1247. global $CFG;
  1248. if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
  1249. return false;
  1250. } else {
  1251. return parent::is_enabled();
  1252. }
  1253. }
  1254. }