Commit 29c442c5 authored by Andrew Nicols's avatar Andrew Nicols
Browse files

MDL-65646 core: Move component storage to json

parent f3507273
{
"plugintypes": {
"logstore": "admin\/tool\/log\/store"
}
}
......@@ -22,4 +22,5 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$subplugins = array('logstore' => 'admin/tool/log/store');
debugging('Use of subplugins.php has been deprecated. Please provide a subplugins.json instead.', DEBUG_DEVELOPER);
$subplugins = (array) json_decode(file_get_contents(__DIR__ . "/subplugins.json"))->plugintypes;
......@@ -173,7 +173,7 @@ abstract class backup_structure_step extends backup_step {
* looking for /mod/modulenanme subplugins. This new method is a generalization of the
* existing one for activities, supporting all subplugins injecting information everywhere.
*
* @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.php.
* @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.json.
* @param backup_nested_element $element element in the backup tree (anywhere) that
* we are going to add subplugin information to.
* @param bool $multiple to define if multiple subplugins can produce information
......@@ -206,11 +206,10 @@ abstract class backup_structure_step extends backup_step {
}
// Check the requested subplugintype is a valid one.
$subpluginsfile = core_component::get_component_directory($plugintype . '_' . $pluginname) . '/db/subplugins.php';
if (!file_exists($subpluginsfile)) {
throw new backup_step_exception('plugin_missing_subplugins_php_file', array($plugintype, $pluginname));
$subplugins = core_component::get_subplugins("{$plugintype}_{$pluginname}");
if (null === $subplugins) {
throw new backup_step_exception('plugin_missing_subplugins_configuration', [$plugintype, $pluginname]);
}
include($subpluginsfile);
if (!array_key_exists($subplugintype, $subplugins)) {
throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
}
......
......@@ -305,7 +305,7 @@ abstract class restore_structure_step extends restore_step {
* looking for /mod/modulenanme subplugins. This new method is a generalization of the
* existing one for activities, supporting all subplugins injecting information everywhere.
*
* @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.php.
* @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.json.
* @param restore_path_element $element element in the structure restore tree that
* we are going to add subplugin information to.
* @param string $plugintype type of the plugin.
......@@ -336,11 +336,10 @@ abstract class restore_structure_step extends restore_step {
}
// Check the requested subplugintype is a valid one.
$subpluginsfile = core_component::get_component_directory($plugintype . '_' . $pluginname) . '/db/subplugins.php';
if (!file_exists($subpluginsfile)) {
throw new restore_step_exception('plugin_missing_subplugins_php_file', array($plugintype, $pluginname));
$subplugins = core_component::get_subplugins("{$plugintype}_{$pluginname}");
if (null === $subplugins) {
throw new restore_step_exception('plugin_missing_subplugins_configuration', array($plugintype, $pluginname));
}
include($subpluginsfile);
if (!array_key_exists($subplugintype, $subplugins)) {
throw new restore_step_exception('incorrect_subplugin_type', $subplugintype);
}
......
......@@ -241,7 +241,7 @@ class backup_step_testcase extends advanced_testcase {
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('plugin_missing_subplugins_php_file', $e->errorcode);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong BC (defaulting to mod and modulename) use not having subplugins.
try {
......@@ -250,7 +250,7 @@ class backup_step_testcase extends advanced_testcase {
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('plugin_missing_subplugins_php_file', $e->errorcode);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong subplugin type.
try {
......@@ -362,7 +362,7 @@ class backup_step_testcase extends advanced_testcase {
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('plugin_missing_subplugins_php_file', $e->errorcode);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong BC (defaulting to mod and modulename) use not having subplugins.
try {
......@@ -371,7 +371,7 @@ class backup_step_testcase extends advanced_testcase {
$this->assertTrue(false, 'base_step_exception expected');
} catch (exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('plugin_missing_subplugins_php_file', $e->errorcode);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong subplugin type.
try {
......
......@@ -7010,20 +7010,27 @@ class context_module extends context {
$module = $DB->get_record('modules', array('id'=>$cm->module));
$subcaps = array();
$subpluginsfile = "$CFG->dirroot/mod/$module->name/db/subplugins.php";
if (file_exists($subpluginsfile)) {
$modulepath = "{$CFG->dirroot}/mod/{$module->name}";
if (file_exists("{$modulepath}/db/subplugins.json")) {
$subplugins = (array) json_decode(file_get_contents("{$modulepath}/db/subplugins.json"))->plugintypes;
} else if (file_exists("{$modulepath}/db/subplugins.php")) {
debugging('Use of subplugins.php has been deprecated. ' .
'Please update your plugin to provide a subplugins.json file instead.',
DEBUG_DEVELOPER);
$subplugins = array(); // should be redefined in the file
include($subpluginsfile);
if (!empty($subplugins)) {
foreach (array_keys($subplugins) as $subplugintype) {
foreach (array_keys(core_component::get_plugin_list($subplugintype)) as $subpluginname) {
$subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname)));
}
include("{$modulepath}/db/subplugins.php");
}
if (!empty($subplugins)) {
foreach (array_keys($subplugins) as $subplugintype) {
foreach (array_keys(core_component::get_plugin_list($subplugintype)) as $subpluginname) {
$subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname)));
}
}
}
$modfile = "$CFG->dirroot/mod/$module->name/lib.php";
$modfile = "{$modulepath}/lib.php";
$extracaps = array();
if (file_exists($modfile)) {
include_once($modfile);
......
......@@ -132,17 +132,26 @@ function uninstall_plugin($type, $name) {
$subplugintypes = core_component::get_plugin_types_with_subplugins();
if (isset($subplugintypes[$type])) {
$base = core_component::get_plugin_directory($type, $name);
if (file_exists("$base/db/subplugins.php")) {
$subplugins = array();
include("$base/db/subplugins.php");
foreach ($subplugins as $subplugintype=>$dir) {
$subpluginsfile = "{$base}/db/subplugins.json";
if (file_exists($subpluginsfile)) {
$subplugins = (array) json_decode(file_get_contents($subpluginsfile))->plugintypes;
} else if (file_exists("{$base}/db/subplugins.php")) {
debugging('Use of subplugins.php has been deprecated. ' .
'Please update your plugin to provide a subplugins.json file instead.',
DEBUG_DEVELOPER);
$subplugins = [];
include("{$base}/db/subplugins.php");
}
if (!empty($subplugins)) {
foreach (array_keys($subplugins) as $subplugintype) {
$instances = core_component::get_plugin_list($subplugintype);
foreach ($instances as $subpluginname => $notusedpluginpath) {
uninstall_plugin($subplugintype, $subpluginname);
}
}
}
}
$component = $type . '_' . $name; // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
......
......@@ -47,6 +47,8 @@ class core_component {
/** @var array list plugin types that support subplugins, do not add more here unless absolutely necessary */
protected static $supportsubplugins = array('mod', 'editor', 'tool', 'local');
/** @var object JSON source of the component data */
protected static $componentsource = null;
/** @var array cache of plugin types */
protected static $plugintypes = null;
/** @var array cache of plugin locations */
......@@ -416,79 +418,20 @@ $cache = '.var_export($cache, true).';
global $CFG;
// NOTE: Any additions here must be verified to not collide with existing add-on modules and subplugins!!!
$info = [];
foreach (self::fetch_component_source('subsystems') as $subsystem => $path) {
// Replace admin/ directory with the config setting.
if ($CFG->admin !== 'admin') {
if ($path === 'admin') {
$path = $CFG->admin;
}
if (strpos($path, 'admin/') === 0) {
$path = $CFG->admin . substr($path, 0, 5);
}
}
$info = array(
'access' => null,
'admin' => $CFG->dirroot.'/'.$CFG->admin,
'analytics' => $CFG->dirroot . '/analytics',
'antivirus' => $CFG->dirroot . '/lib/antivirus',
'auth' => $CFG->dirroot.'/auth',
'availability' => $CFG->dirroot . '/availability',
'backup' => $CFG->dirroot.'/backup/util/ui',
'badges' => $CFG->dirroot.'/badges',
'block' => $CFG->dirroot.'/blocks',
'blog' => $CFG->dirroot.'/blog',
'bulkusers' => null,
'cache' => $CFG->dirroot.'/cache',
'calendar' => $CFG->dirroot.'/calendar',
'cohort' => $CFG->dirroot.'/cohort',
'comment' => $CFG->dirroot.'/comment',
'competency' => $CFG->dirroot.'/competency',
'completion' => $CFG->dirroot.'/completion',
'countries' => null,
'course' => $CFG->dirroot.'/course',
'currencies' => null,
'customfield' => $CFG->dirroot.'/customfield',
'dbtransfer' => null,
'debug' => null,
'editor' => $CFG->dirroot.'/lib/editor',
'edufields' => null,
'enrol' => $CFG->dirroot.'/enrol',
'error' => null,
'favourites' => $CFG->dirroot . '/favourites',
'filepicker' => null,
'fileconverter' => $CFG->dirroot.'/files/converter',
'files' => $CFG->dirroot.'/files',
'filters' => $CFG->dirroot.'/filter',
//'fonts' => null, // Bogus.
'form' => $CFG->dirroot.'/lib/form',
'grades' => $CFG->dirroot.'/grade',
'grading' => $CFG->dirroot.'/grade/grading',
'group' => $CFG->dirroot.'/group',
'help' => null,
'hub' => null,
'imscc' => null,
'install' => null,
'iso6392' => null,
'langconfig' => null,
'license' => null,
'mathslib' => null,
'media' => $CFG->dirroot.'/media',
'message' => $CFG->dirroot.'/message',
'mimetypes' => null,
'mnet' => $CFG->dirroot.'/mnet',
//'moodle.org' => null, // Not used any more.
'my' => $CFG->dirroot.'/my',
'notes' => $CFG->dirroot.'/notes',
'pagetype' => null,
'pix' => null,
'plagiarism' => $CFG->dirroot.'/plagiarism',
'plugin' => null,
'portfolio' => $CFG->dirroot.'/portfolio',
'privacy' => $CFG->dirroot . '/privacy',
'question' => $CFG->dirroot.'/question',
'rating' => $CFG->dirroot.'/rating',
'repository' => $CFG->dirroot.'/repository',
'rss' => $CFG->dirroot.'/rss',
'role' => $CFG->dirroot.'/'.$CFG->admin.'/roles',
'search' => $CFG->dirroot.'/search',
'table' => null,
'tag' => $CFG->dirroot.'/tag',
'timezones' => null,
'user' => $CFG->dirroot.'/user',
'userkey' => $CFG->dirroot.'/lib/userkey',
'webservice' => $CFG->dirroot.'/webservice',
);
$info[$subsystem] = empty($path) ? null : "{$CFG->dirroot}/{$path}";
}
return $info;
}
......@@ -500,43 +443,15 @@ $cache = '.var_export($cache, true).';
protected static function fetch_plugintypes() {
global $CFG;
$types = array(
'antivirus' => $CFG->dirroot . '/lib/antivirus',
'availability' => $CFG->dirroot . '/availability/condition',
'qtype' => $CFG->dirroot.'/question/type',
'mod' => $CFG->dirroot.'/mod',
'auth' => $CFG->dirroot.'/auth',
'calendartype' => $CFG->dirroot.'/calendar/type',
'customfield' => $CFG->dirroot.'/customfield/field',
'enrol' => $CFG->dirroot.'/enrol',
'message' => $CFG->dirroot.'/message/output',
'block' => $CFG->dirroot.'/blocks',
'media' => $CFG->dirroot.'/media/player',
'filter' => $CFG->dirroot.'/filter',
'editor' => $CFG->dirroot.'/lib/editor',
'format' => $CFG->dirroot.'/course/format',
'dataformat' => $CFG->dirroot.'/dataformat',
'profilefield' => $CFG->dirroot.'/user/profile/field',
'report' => $CFG->dirroot.'/report',
'coursereport' => $CFG->dirroot.'/course/report', // Must be after system reports.
'gradeexport' => $CFG->dirroot.'/grade/export',
'gradeimport' => $CFG->dirroot.'/grade/import',
'gradereport' => $CFG->dirroot.'/grade/report',
'gradingform' => $CFG->dirroot.'/grade/grading/form',
'mlbackend' => $CFG->dirroot.'/lib/mlbackend',
'mnetservice' => $CFG->dirroot.'/mnet/service',
'webservice' => $CFG->dirroot.'/webservice',
'repository' => $CFG->dirroot.'/repository',
'portfolio' => $CFG->dirroot.'/portfolio',
'search' => $CFG->dirroot.'/search/engine',
'qbehaviour' => $CFG->dirroot.'/question/behaviour',
'qformat' => $CFG->dirroot.'/question/format',
'plagiarism' => $CFG->dirroot.'/plagiarism',
'tool' => $CFG->dirroot.'/'.$CFG->admin.'/tool',
'cachestore' => $CFG->dirroot.'/cache/stores',
'cachelock' => $CFG->dirroot.'/cache/locks',
'fileconverter' => $CFG->dirroot.'/files/converter',
);
$types = [];
foreach (self::fetch_component_source('plugintypes') as $plugintype => $path) {
// Replace admin/ with the config setting.
if ($CFG->admin !== 'admin' && strpos($path, 'admin/') === 0) {
$path = $CFG->admin . substr($path, 0, 5);
}
$types[$plugintype] = "{$CFG->dirroot}/{$path}";
}
$parents = array();
$subplugins = array();
......@@ -596,6 +511,19 @@ $cache = '.var_export($cache, true).';
return array($types, $parents, $subplugins);
}
/**
* Returns the component source content as loaded from /lib/components.json.
*
* @return array
*/
protected static function fetch_component_source(string $key) {
if (null === self::$componentsource) {
self::$componentsource = (array) json_decode(file_get_contents(__DIR__ . '/../components.json'));
}
return (array) self::$componentsource[$key];
}
/**
* Returns list of subtypes.
* @param string $ownerdir
......@@ -605,28 +533,35 @@ $cache = '.var_export($cache, true).';
global $CFG;
$types = array();
if (file_exists("$ownerdir/db/subplugins.php")) {
$subplugins = array();
$subplugins = array();
if (file_exists("$ownerdir/db/subplugins.json")) {
$subplugins = (array) json_decode(file_get_contents("$ownerdir/db/subplugins.json"))->plugintypes;
} else if (file_exists("$ownerdir/db/subplugins.php")) {
debugging('Use of subplugins.php has been deprecated. ' .
'Please update your plugin to provide a subplugins.json file instead.',
DEBUG_DEVELOPER);
include("$ownerdir/db/subplugins.php");
foreach ($subplugins as $subtype => $dir) {
if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) {
error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present.");
continue;
}
if (isset(self::$subsystems[$subtype])) {
error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem.");
continue;
}
if ($CFG->admin !== 'admin' and strpos($dir, 'admin/') === 0) {
$dir = preg_replace('|^admin/|', "$CFG->admin/", $dir);
}
if (!is_dir("$CFG->dirroot/$dir")) {
error_log("Invalid subtype directory '$dir' detected in '$ownerdir'.");
continue;
}
$types[$subtype] = "$CFG->dirroot/$dir";
}
foreach ($subplugins as $subtype => $dir) {
if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) {
error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present.");
continue;
}
if (isset(self::$subsystems[$subtype])) {
error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem.");
continue;
}
if ($CFG->admin !== 'admin' and strpos($dir, 'admin/') === 0) {
$dir = preg_replace('|^admin/|', "$CFG->admin/", $dir);
}
if (!is_dir("$CFG->dirroot/$dir")) {
error_log("Invalid subtype directory '$dir' detected in '$ownerdir'.");
continue;
}
$types[$subtype] = "$CFG->dirroot/$dir";
}
return $types;
}
......
......@@ -511,10 +511,10 @@ class core_plugin_manager {
/**
* Returns list of plugins that define their subplugins and the information
* about them from the db/subplugins.php file.
* about them from the db/subplugins.json file.
*
* @return array with keys like 'mod_quiz', and values the data from the
* corresponding db/subplugins.php file.
* corresponding db/subplugins.json file.
*/
public function get_subplugins() {
......
{
"plugintypes": {
"antivirus": "lib\/antivirus",
"availability": "availability\/condition",
"qtype": "question\/type",
"mod": "mod",
"auth": "auth",
"calendartype": "calendar\/type",
"customfield": "customfield\/field",
"enrol": "enrol",
"message": "message\/output",
"block": "blocks",
"media": "media\/player",
"filter": "filter",
"editor": "lib\/editor",
"format": "course\/format",
"dataformat": "dataformat",
"profilefield": "user\/profile\/field",
"report": "report",
"coursereport": "course\/report",
"gradeexport": "grade\/export",
"gradeimport": "grade\/import",
"gradereport": "grade\/report",
"gradingform": "grade\/grading\/form",
"mlbackend": "lib\/mlbackend",
"mnetservice": "mnet\/service",
"webservice": "webservice",
"repository": "repository",
"portfolio": "portfolio",
"search": "search\/engine",
"qbehaviour": "question\/behaviour",
"qformat": "question\/format",
"plagiarism": "plagiarism",
"tool": "admin\/tool",
"cachestore": "cache\/stores",
"cachelock": "cache\/locks",
"fileconverter": "files\/converter",
"theme": "theme",
"local": "local"
},
"subsystems": {
"access": null,
"admin": "admin",
"analytics": "analytics",
"antivirus": "lib\/antivirus",
"auth": "auth",
"availability": "availability",
"backup": "backup\/util\/ui",
"badges": "badges",
"block": "blocks",
"blog": "blog",
"bulkusers": null,
"cache": "cache",
"calendar": "calendar",
"cohort": "cohort",
"comment": "comment",
"competency": "competency",
"completion": "completion",
"countries": null,
"course": "course",
"currencies": null,
"customfield": "customfield",
"dbtransfer": null,
"debug": null,
"editor": "lib\/editor",
"edufields": null,
"enrol": "enrol",
"error": null,
"favourites": "favourites",
"filepicker": null,
"fileconverter": "files\/converter",
"files": "files",
"filters": "filter",
"form": "lib\/form",
"grades": "grade",
"grading": "grade\/grading",
"group": "group",
"help": null,
"hub": null,
"imscc": null,
"install": null,
"iso6392": null,
"langconfig": null,
"license": null,
"mathslib": null,
"media": "media",
"message": "message",
"mimetypes": null,
"mnet": "mnet",
"my": "my",
"notes": "notes",
"pagetype": null,
"pix": null,
"plagiarism": "plagiarism",
"plugin": null,
"portfolio": "portfolio",
"privacy": "privacy",
"question": "question",
"rating": "rating",
"repository": "repository",
"rss": "rss",
"role": "admin\/roles",
"search": "search",
"table": null,
"tag": "tag",
"timezones": null,
"user": "user",
"userkey": "lib\/userkey",
"webservice": "webservice"
}
}
{
"plugintypes": {
"atto": "lib\/editor\/atto\/plugins"
}
}
......@@ -24,4 +24,5 @@
defined('MOODLE_INTERNAL') || die();
$subplugins = array('atto' => 'lib/editor/atto/plugins');
debugging('Use of subplugins.php has been deprecated. Please provide a subplugins.json instead.', DEBUG_DEVELOPER);
$subplugins = (array) json_decode(file_get_contents(__DIR__ . "/subplugins.json"))->plugintypes;
{
"plugintypes": {
"tinymce": "lib\/editor\/tinymce\/plugins"
}
}
......@@ -24,4 +24,5 @@
defined('MOODLE_INTERNAL') || die();
$subplugins = array('tinymce' => 'lib/editor/tinymce/plugins');
debugging('Use of subplugins.php has been deprecated. Please provide a subplugins.json instead.', DEBUG_DEVELOPER);
$subplugins = (array) json_decode(file_get_contents(__DIR__ . "/subplugins.json"))->plugintypes;
......@@ -3,6 +3,8 @@ information provided here is intended especially for developers.
=== 3.8 ===
* The yui checknet module is removed. Call \core\session\manager::keepalive instead.
* Core components are now defined in /lib/components.json instead of coded into /lib/classes/component.php
* Subplugins should now be defined using /db/subplugins.json instead of /db/subplugins.php (which will still work in order to maintain backwards compantibility).
=== 3.7 ===
* Nodes in the navigation api can have labels for each group. See set/get_collectionlabel().
......
{
"plugintypes": {
"assignsubmission": "mod\/assign\/submission",
"assignfeedback": "mod\/assign\/feedback"
}
}
......@@ -22,4 +22,5 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$subplugins = array('assignsubmission'=>'mod/assign/submission', 'assignfeedback'=>'mod/assign/feedback');
debugging('Use of subplugins.php has been deprecated. Please provide a subplugins.json instead.', DEBUG_DEVELOPER);
$subplugins = (array) json_decode(file_get_contents(__DIR__ . "/subplugins.json"))->plugintypes;
{
"plugintypes": {
"assignment": "mod\/assignment\/type"
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License