Commit bde002b8 authored by Petr Škoda's avatar Petr Škoda
Browse files

MDL-41437 rework plugin_manager caching and version info in blocks and modules

This patch includes:

* version column removed from modules table, now using standard config, this allows decimal version for modules
* version column removed from block table, now using standard config, this allows decimal version for blocks
* module version.php can safely use $plugins instead of module
* new plugin_manager bulk caching, this should help with MUC performance when logged in as admin
* all missing plugins are now in plugin overview (previously only blocks and modules)
* simplified code and improved coding style
* reworked plugin_manager unit tests - now using real plugins instead of mocks
* unit tests now fail if any plugin does not contain proper version.php file
* allow uninstall of deleted filters
parent 81881cb9
......@@ -25,6 +25,7 @@
require_once(dirname(__FILE__) . '/../config.php');
require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->libdir . '/pluginlib.php');
$contextid = required_param('contextid',PARAM_INT);
$forfilter = optional_param('filter', '', PARAM_SAFEDIR);
......@@ -36,9 +37,6 @@ require_login($course, false, $cm);
require_capability('moodle/filter:manage', $context);
$PAGE->set_context($context);
// Purge all caches related to filter administration.
cache::make('core', 'plugininfo_filter')->purge();
$args = array('contextid'=>$contextid);
$baseurl = new moodle_url('/filter/manage.php', $args);
if (!empty($forfilter)) {
......
......@@ -51,12 +51,7 @@ $string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
$string['cachedef_langmenu'] = 'List of available languages';
$string['cachedef_locking'] = 'Locking';
$string['cachedef_observers'] = 'Event observers';
$string['cachedef_plugininfo_base'] = 'Plugin info - base';
$string['cachedef_plugininfo_block'] = 'Plugin info - blocks';
$string['cachedef_plugininfo_filter'] = 'Plugin info - filters';
$string['cachedef_plugininfo_mod'] = 'Plugin info - activity modules';
$string['cachedef_plugininfo_portfolio'] = 'Plugin info - portfolios';
$string['cachedef_plugininfo_repository'] = 'Plugin info - repositories';
$string['cachedef_plugin_manager'] = 'Plugin info manager';
$string['cachedef_questiondata'] = 'Question definitions';
$string['cachedef_repositories'] = 'Repositories instances data';
$string['cachedef_string'] = 'Language string cache';
......
......@@ -298,7 +298,10 @@ function uninstall_plugin($type, $name) {
$DB->delete_records('log_display', array('component' => $component));
// delete the module configuration records
unset_all_config_for_plugin($pluginname);
unset_all_config_for_plugin($component);
if ($type === 'mod') {
unset_all_config_for_plugin($pluginname);
}
// delete message provider
message_provider_uninstall($component);
......@@ -375,9 +378,11 @@ function get_component_version($component, $source='installed') {
if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
return false;
} else {
$module = new stdclass();
$plugin = new stdClass();
$plugin->version = null;
$module = $plugin;
include($mods[$name].'/version.php');
return $module->version;
return $plugin->version;
}
}
}
......
......@@ -927,17 +927,11 @@ $cache = '.var_export($cache, true).';
$plugs = self::fetch_plugins($type, $typedir);
}
foreach ($plugs as $plug => $fullplug) {
if ($type === 'mod') {
$module = new stdClass();
$module->version = null;
include($fullplug.'/version.php');
$versions[$type.'_'.$plug] = $module->version;
} else {
$plugin = new stdClass();
$plugin->version = null;
@include($fullplug.'/version.php');
$versions[$type.'_'.$plug] = $plugin->version;
}
$plugin = new stdClass();
$plugin->version = null;
$module = $plugin;
@include($fullplug.'/version.php');
$versions[$type.'_'.$plug] = $plugin->version;
}
}
......
......@@ -141,58 +141,13 @@ $definitions = array(
'persistentmaxsize' => 2,
),
// Cache used by the {@link plugininfo_base} class.
'plugininfo_base' => array(
// Cache used by the {@link plugin_manager} class.
// NOTE: this must be a shared cache.
'plugin_manager' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'persistent' => true,
'persistentmaxsize' => 2,
),
// Cache used by the {@link plugininfo_mod} class.
'plugininfo_mod' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'persistent' => true,
'persistentmaxsize' => 1,
),
// Cache used by the {@link plugininfo_block} class.
'plugininfo_block' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'persistent' => true,
'persistentmaxsize' => 1,
),
// Cache used by the {@link plugininfo_filter} class.
'plugininfo_filter' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'persistent' => true,
'persistentmaxsize' => 1,
),
// Cache used by the {@link plugininfo_repository} class.
'plugininfo_repository' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'persistent' => true,
'persistentmaxsize' => 1,
),
// Cache used by the {@link plugininfo_portfolio} class.
'plugininfo_portfolio' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'persistent' => true,
'persistentmaxsize' => 1,
'persistent' => false,
),
// Used to store the full tree of course categories
......
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20130913" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20130921" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
......@@ -668,7 +668,6 @@
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="version" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="cron" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="lastcron" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="search" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
......@@ -2487,7 +2486,6 @@
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="version" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="cron" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="lastcron" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="visible" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
......
......@@ -2450,5 +2450,52 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2013091300.01);
}
if ($oldversion < 2013092001.01) {
// Force uninstall of deleted tool.
if (!file_exists("$CFG->dirroot/$CFG->admin/tool/bloglevelupgrade")) {
// Remove capabilities.
capabilities_cleanup('tool_bloglevelupgrade');
// Remove all other associated config.
unset_all_config_for_plugin('tool_bloglevelupgrade');
}
upgrade_main_savepoint(true, 2013092001.01);
}
if ($oldversion < 2013092001.02) {
// Define field version to be dropped from modules.
$table = new xmldb_table('modules');
$field = new xmldb_field('version');
// Conditionally launch drop field version.
if ($dbman->field_exists($table, $field)) {
// Migrate all plugin version info to config_plugins table.
$modules = $DB->get_records('modules');
foreach ($modules as $module) {
set_config('version', $module->version, 'mod_'.$module->name);
}
unset($modules);
$dbman->drop_field($table, $field);
}
// Define field version to be dropped from block.
$table = new xmldb_table('block');
$field = new xmldb_field('version');
// Conditionally launch drop field version.
if ($dbman->field_exists($table, $field)) {
$blocks = $DB->get_records('block');
foreach ($blocks as $block) {
set_config('version', $block->version, 'block_'.$block->name);
}
unset($blocks);
$dbman->drop_field($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2013092001.02);
}
return true;
}
......@@ -74,25 +74,20 @@ class atto_texteditor extends texteditor {
* @param null $fpoptions
*/
public function use_editor($elementid, array $options=null, $fpoptions=null) {
global $PAGE, $CFG;
global $PAGE;
$PAGE->requires->yui_module('moodle-editor_atto-editor',
'M.editor_atto.init',
array($this->get_init_params($elementid, $options, $fpoptions)), true);
require_once($CFG->libdir . '/pluginlib.php');
$pluginman = plugin_manager::instance();
$plugins = $pluginman->get_subplugins_of_plugin('editor_atto');
$sortedplugins = array();
$plugins = core_component::get_plugin_list('atto');
foreach ($plugins as $id => $plugin) {
$sortorder = component_callback($plugin->type . '_' . $plugin->name, 'sort_order', array($elementid));
$sortedplugins[$sortorder] = $plugin;
foreach ($plugins as $name => $fulldir) {
$plugins[$name] = component_callback('atto_' . $name, 'sort_order', array($elementid));
}
ksort($sortedplugins);
foreach ($sortedplugins as $plugin) {
component_callback($plugin->type . '_' . $plugin->name, 'init_editor', array($elementid));
asort($plugins);
foreach ($plugins as $name => $sort) {
component_callback('atto_' . $name, 'init_editor', array($elementid));
}
}
......
......@@ -24,8 +24,6 @@
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->libdir/pluginlib.php");
/**
* Editor subplugin info class.
......@@ -35,6 +33,34 @@ require_once("$CFG->libdir/pluginlib.php");
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugininfo_tinymce extends plugininfo_base {
/**
* Finds all enabled plugins, the result may include missing plugins.
* @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown
*/
public static function get_enabled_plugins() {
$disabledsubplugins = array();
$config = get_config('editor_tinymce', 'disabledsubplugins');
if ($config) {
$config = explode(',', $config);
foreach ($config as $sp) {
$sp = trim($sp);
if ($sp !== '') {
$disabledsubplugins[$sp] = $sp;
}
}
}
$enabled = array();
$installed = core_component::get_plugin_list('tinymce');
foreach ($installed as $plugin => $fulldir) {
if (isset($disabledsubplugins[$plugin])) {
continue;
}
$enabled[$plugin] = $plugin;
}
return $enabled;
}
public function is_uninstall_allowed() {
return true;
......@@ -59,26 +85,6 @@ class plugininfo_tinymce extends plugininfo_base {
$ADMIN->add($parentnodename, $settings);
}
}
public function is_enabled() {
static $disabledsubplugins = null; // TODO: MDL-34344 remove this once get_config() is cached via MUC!
if (is_null($disabledsubplugins)) {
$disabledsubplugins = array();
$config = get_config('editor_tinymce', 'disabledsubplugins');
if ($config) {
$config = explode(',', $config);
foreach ($config as $sp) {
$sp = trim($sp);
if ($sp !== '') {
$disabledsubplugins[$sp] = $sp;
}
}
}
}
return !isset($disabledsubplugins[$this->name]);
}
}
......
......@@ -24,6 +24,7 @@
require(__DIR__ . '/../../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/pluginlib.php');
$disable = optional_param('disable', '', PARAM_PLUGIN);
$enable = optional_param('enable', '', PARAM_PLUGIN);
......@@ -61,5 +62,6 @@ if ($disable) {
}
set_config('disabledsubplugins', implode(',', $disabled), 'editor_tinymce');
plugin_manager::reset_caches();
redirect($returnurl);
......@@ -1589,6 +1589,9 @@ function purge_all_caches() {
theme_reset_all_caches();
get_string_manager()->reset_caches();
core_text::reset_caches();
if (class_exists('plugin_manager')) {
plugin_manager::reset_caches();
}
// Bump up cacherev field for all courses.
try {
......
This diff is collapsed.
<?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
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for the update deployer.
*
* @package core
* @category phpunit
* @copyright 2012 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir.'/pluginlib.php');
/**
* Test cases for {@link available_update_deployer} class.
*/
class core_available_update_deployer_testcase extends advanced_testcase {
public function test_magic_setters() {
$deployer = testable_available_update_deployer::instance();
$value = new moodle_url('/');
$deployer->set_returnurl($value);
$this->assertSame($deployer->get_returnurl(), $value);
}
public function test_prepare_authorization() {
global $CFG;
$deployer = testable_available_update_deployer::instance();
list($passfile, $password) = $deployer->prepare_authorization();
$filename = $CFG->phpunit_dataroot.'/mdeploy/auth/'.$passfile;
$this->assertFileExists($filename);
$stored = file($filename, FILE_IGNORE_NEW_LINES);
$this->assertCount(2, $stored);
$this->assertGreaterThan(23, strlen($stored[0]));
$this->assertSame($stored[0], $password);
$this->assertLessThan(60, time() - (int)$stored[1]);
}
}
/**
* Modified version of {@link available_update_checker} suitable for testing.
*/
class testable_available_update_checker extends available_update_checker {
/** @var replaces the default DB table storage for the fetched response */
protected $fakeresponsestorage;
/** @var int stores the fake recentfetch value */
public $fakerecentfetch = -1;
/** @var int stores the fake value of time() */
public $fakecurrenttimestamp = -1;
/**
* Factory method for this class.
*
* @return testable_available_update_checker the singleton instance
*/
public static function instance() {
global $CFG;
if (is_null(self::$singletoninstance)) {
self::$singletoninstance = new self();
}
return self::$singletoninstance;
}
protected function validate_response($response) {
}
protected function store_response($response) {
$this->fakeresponsestorage = $response;
}
protected function restore_response($forcereload = false) {
$this->recentfetch = time();
$this->recentresponse = $this->decode_response($this->get_fake_response());
}
public function compare_responses(array $old, array $new) {
return parent::compare_responses($old, $new);
}
public function is_same_release($remote, $local=null) {
return parent::is_same_release($remote, $local);
}
protected function load_current_environment($forcereload=false) {
}
public function fake_current_environment($version, $release, $branch, array $plugins) {
$this->currentversion = $version;
$this->currentrelease = $release;
$this->currentbranch = $branch;
$this->currentplugins = $plugins;
}
public function get_last_timefetched() {
if ($this->fakerecentfetch == -1) {
return parent::get_last_timefetched();
} else {
return $this->fakerecentfetch;
}
}
private function get_fake_response() {
$fakeresponse = array(
'status' => 'OK',
'provider' => 'http://download.moodle.org/api/1.0/updates.php',
'apiver' => '1.0',
'timegenerated' => time(),
'forversion' => '2012010100.00',
'forbranch' => '2.3',
'ticket' => sha1('No, I am not going to mention the word "frog" here. Oh crap. I just did.'),
'updates' => array(
'core' => array(
array(
'version' => 2012060103.00,
'release' => '2.3.3 (Build: 20121201)',
'maturity' => 200,
'url' => 'http://download.moodle.org/',
'download' => 'http://download.moodle.org/download.php/MOODLE_23_STABLE/moodle-2.3.3-latest.zip',
),
array(
'version' => 2012120100.00,
'release' => '2.4dev (Build: 20121201)',
'maturity' => 50,
'url' => 'http://download.moodle.org/',
'download' => 'http://download.moodle.org/download.php/MOODLE_24_STABLE/moodle-2.4.0-latest.zip',
),
),
'mod_foo' => array(
array(
'version' => 2012030501,
'requires' => 2012010100,
'maturity' => 200,
'release' => '1.1',
'url' => 'http://moodle.org/plugins/blahblahblah/',
'download' => 'http://moodle.org/plugins/download.php/blahblahblah',
),
array(
'version' => 2012030502,
'requires' => 2012010100,
'maturity' => 100,
'release' => '1.2 beta',
'url' => 'http://moodle.org/plugins/',
),
),
),
);
return json_encode($fakeresponse);
}
protected function cron_current_timestamp() {
if ($this->fakecurrenttimestamp == -1) {
return parent::cron_current_timestamp();
} else {
return $this->fakecurrenttimestamp;
}
}
protected function cron_mtrace($msg, $eol = PHP_EOL) {
}
protected function cron_autocheck_enabled() {
return true;
}
protected function cron_execution_offset() {
// Autofetch should run by the first cron after 01:42 AM.
return 42 * MINSECS;
}
protected function cron_execute() {
throw new testable_available_update_checker_cron_executed('Cron executed!');
}
}
/**
* Exception used to detect {@link available_update_checker::cron_execute()} calls.
*/
class testable_available_update_checker_cron_executed extends Exception {
}
/**
* Modified {@link available_update_deployer} suitable for testing purposes.
*/
class testable_available_update_deployer extends available_update_deployer {
}
<?php
$module->version = 2012030500;
$module->requires = 2012010100;
<?php
$plugin->version = 2013041103;
$plugin->requires = 2013010100;
$plugin->component = 'bazmeg_one';
<?php
$module->version = 2012030500;
$module->requires = 2012010100;
<?php
$plugin->version = 2013041103;
$plugin->requires = 2013010100;
$plugin->component = 'foolish_frog';
$plugin->dependencies = array('mod_foo' => 2012030500);
<?php
$plugin->version = 2013041103;
$plugin->requires = 2012010100;
$plugin->component = 'foolish_hippo';
$plugin->dependencies = array('foolish_frog' => ANY_VERSION);
<?php
$module->version = 2012030500;
$module->requires = 2012010100;
$module->component = 'mod_foo';
$module->dependencies = array(
'mod_bar' => 2012030500,
'mod_missing' => ANY_VERSION,