MDL-41437 rework plugin_manager caching and version info in blocks and modules
authorPetr Škoda <commits@skodak.org>
Sat, 14 Sep 2013 21:57:21 +0000 (23:57 +0200)
committerPetr Škoda <commits@skodak.org>
Sun, 22 Sep 2013 19:25:26 +0000 (21:25 +0200)
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

50 files changed:
admin/auth.php
admin/block.php [deleted file]
admin/blocks.php
admin/courseformats.php
admin/editors.php
admin/enrol.php
admin/filters.php
admin/localplugins.php
admin/message.php
admin/modules.php
admin/plagiarism.php
admin/portfolio.php
admin/qbehaviours.php
admin/reports.php
admin/repository.php
admin/repositoryinstance.php
backup/converter/moodle1/handlerlib.php
backup/moodle2/backup_stepslib.php
blocks/upgrade.txt
cache/locks/file/version.php [new file with mode: 0644]
filter/manage.php
lang/en/cache.php
lib/adminlib.php
lib/classes/component.php
lib/db/caches.php
lib/db/install.xml
lib/db/upgrade.php
lib/editor/atto/lib.php
lib/editor/tinymce/adminlib.php
lib/editor/tinymce/subplugins.php
lib/moodlelib.php
lib/pluginlib.php
lib/tests/available_update_checker_test.php [new file with mode: 0644]
lib/tests/available_update_deployer_test.php [new file with mode: 0644]
lib/tests/fixtures/mockplugins/mod/bar/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/baz/meg/one/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/baz/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/foo/lish/frog/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/foo/lish/hippo/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/foo/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/new/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/qux/cat/one/version.php [deleted file]
lib/tests/fixtures/mockplugins/mod/qux/version.php [deleted file]
lib/tests/plugin_manager_test.php [new file with mode: 0644]
lib/tests/pluginlib_test.php [deleted file]
lib/upgrade.txt
lib/upgradelib.php
mod/resource/backup/moodle1/lib.php
user/portfolio.php
version.php

index 934acd3..4dc0a0d 100644 (file)
@@ -10,6 +10,7 @@
 require_once('../config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/tablelib.php');
+require_once($CFG->libdir.'/pluginlib.php');
 
 require_login();
 require_capability('moodle/site:config', context_system::instance());
@@ -52,6 +53,7 @@ switch ($action) {
             set_config('registerauth', '');
         }
         session_gc(); // remove stale sessions
+        plugin_manager::reset_caches();
         break;
 
     case 'enable':
@@ -62,6 +64,7 @@ switch ($action) {
             set_config('auth', implode(',', $authsenabled));
         }
         session_gc(); // remove stale sessions
+        plugin_manager::reset_caches();
         break;
 
     case 'down':
diff --git a/admin/block.php b/admin/block.php
deleted file mode 100644 (file)
index 59938ec..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-// block.php - allows admin to edit all local configuration variables for a block
-
-    require_once('../config.php');
-    require_once($CFG->libdir.'/adminlib.php');
-
-    $blockid = required_param('block', PARAM_INT);
-
-    if(!$blockrecord = blocks_get_record($blockid)) {
-        print_error('blockdoesnotexist', 'error');
-    }
-
-    admin_externalpage_setup('blocksetting'.$blockrecord->name);
-
-    $block = block_instance($blockrecord->name);
-    if($block === false) {
-        print_error('blockcannotinistantiate', 'error');
-    }
-
-    // Define the data we're going to silently include in the instance config form here,
-    // so we can strip them from the submitted data BEFORE handling it.
-    $hiddendata = array(
-        'block' => $blockid,
-        'sesskey' => sesskey()
-    );
-
-    /// If data submitted, then process and store.
-
-    if ($config = data_submitted()) {
-
-        if (!confirm_sesskey()) {
-             print_error('confirmsesskeybad', 'error');
-        }
-        if(!$block->has_config()) {
-            print_error('blockcannotconfig', 'error');
-        }
-        $remove = array_keys($hiddendata);
-        foreach($remove as $item) {
-            unset($config->$item);
-        }
-        $block->config_save($config);
-        redirect("$CFG->wwwroot/$CFG->admin/blocks.php", get_string("changessaved"), 1);
-        exit;
-    }
-
-    /// Otherwise print the form.
-
-    $strmanageblocks = get_string('manageblocks');
-    $strblockname = $block->get_title();
-
-    echo $OUTPUT->header();
-
-    echo $OUTPUT->heading($strblockname);
-
-    echo $OUTPUT->notification('This block still uses an old-style config_global.html file. ' .
-            'It must be updated by a developer to use a settings.php file.');
-
-    echo $OUTPUT->box(get_string('configwarning', 'admin'), 'generalbox boxwidthnormal boxaligncenter');
-    echo '<br />';
-
-    echo '<form method="post" action="block.php">';
-    echo '<p>';
-    foreach($hiddendata as $name => $val) {
-        echo '<input type="hidden" name="'. $name .'" value="'. $val .'" />';
-    }
-    echo '</p>';
-
-    echo $OUTPUT->box_start();
-    include($CFG->dirroot.'/blocks/'. $block->name() .'/config_global.html');
-    echo $OUTPUT->box_end();
-
-    echo '</form>';
-    echo $OUTPUT->footer();
-
-
index 6a2d31e..1e37dd2 100644 (file)
@@ -5,6 +5,7 @@
     require_once('../config.php');
     require_once($CFG->libdir.'/adminlib.php');
     require_once($CFG->libdir.'/tablelib.php');
+    require_once($CFG->libdir.'/pluginlib.php');
 
     admin_externalpage_setup('manageblocks');
 
@@ -29,9 +30,6 @@
     $strprotect = get_string('blockprotect', 'admin');
     $strunprotect = get_string('blockunprotect', 'admin');
 
-    // Purge all caches related to blocks administration.
-    cache::make('core', 'plugininfo_block')->purge();
-
 /// If data submitted, then process and store.
 
     if (!empty($hide) && confirm_sesskey()) {
@@ -39,6 +37,7 @@
             print_error('blockdoesnotexist', 'error');
         }
         $DB->set_field('block', 'visible', '0', array('id'=>$block->id));      // Hide block
+        plugin_manager::reset_caches();
         admin_get_root(true, false);  // settings not required - only pages
     }
 
@@ -47,6 +46,7 @@
             print_error('blockdoesnotexist', 'error');
         }
         $DB->set_field('block', 'visible', '1', array('id'=>$block->id));      // Show block
+        plugin_manager::reset_caches();
         admin_get_root(true, false);  // settings not required - only pages
     }
 
     foreach ($blocknames as $blockid=>$strblockname) {
         $block = $blocks[$blockid];
         $blockname = $block->name;
+        $dbversion = get_config('block_'.$block->name, 'version');
 
         if (!file_exists("$CFG->dirroot/blocks/$blockname/block_$blockname.php")) {
             $blockobject  = false;
             $strblockname = '<span class="notifyproblem">'.$strblockname.' ('.get_string('missingfromdisk').')</span>';
             $plugin = new stdClass();
-            $plugin->version = $block->version;
+            $plugin->version = $dbversion;
 
         } else {
             $plugin = new stdClass();
             $class = ' class="dimmed_text"'; // Leading space required!
         }
 
-        if ($block->version == $plugin->version) {
-            $version = $block->version;
+        if ($dbversion == $plugin->version) {
+            $version = $dbversion;
         } else {
-            $version = "$block->version ($plugin->version)";
+            $version = "$dbversion ($plugin->version)";
         }
 
         if (!$blockobject) {
index 7ea15e8..f86c4be 100644 (file)
@@ -53,11 +53,13 @@ switch ($action) {
                 print_error('cannotdisableformat', 'error', $return);
             }
             set_config('disabled', 1, 'format_'. $formatname);
+            plugin_manager::reset_caches();
         }
         break;
     case 'enable':
         if (!$formatplugins[$formatname]->is_enabled()) {
             unset_config('disabled', 'format_'. $formatname);
+            plugin_manager::reset_caches();
         }
         break;
     case 'up':
index da846fa..50ee7a5 100644 (file)
@@ -7,6 +7,7 @@
 require_once('../config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/tablelib.php');
+require_once($CFG->libdir.'/pluginlib.php');
 
 $action  = required_param('action', PARAM_ALPHANUMEXT);
 $editor  = required_param('editor', PARAM_PLUGIN);
@@ -93,6 +94,7 @@ if (empty($active_editors)) {
 }
 
 set_config('texteditors', implode(',', $active_editors));
+plugin_manager::reset_caches();
 
 if ($return) {
     redirect ($returnurl);
index 59f77c5..14a7649 100644 (file)
@@ -27,6 +27,7 @@ define('NO_OUTPUT_BUFFERING', true);
 
 require_once('../config.php');
 require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/pluginlib.php');
 
 $action  = required_param('action', PARAM_ALPHANUMEXT);
 $enrol   = required_param('enrol', PARAM_PLUGIN);
@@ -50,6 +51,7 @@ switch ($action) {
     case 'disable':
         unset($enabled[$enrol]);
         set_config('enrol_plugins_enabled', implode(',', array_keys($enabled)));
+        plugin_manager::reset_caches();
         $syscontext->mark_dirty(); // resets all enrol caches
         break;
 
@@ -60,6 +62,7 @@ switch ($action) {
         $enabled = array_keys($enabled);
         $enabled[] = $enrol;
         set_config('enrol_plugins_enabled', implode(',', $enabled));
+        plugin_manager::reset_caches();
         $syscontext->mark_dirty(); // resets all enrol caches
         break;
 
index 6c58504..334dc71 100644 (file)
@@ -33,6 +33,7 @@
 
     require_once(dirname(__FILE__) . '/../config.php');
     require_once($CFG->libdir . '/adminlib.php');
+    require_once($CFG->libdir . '/pluginlib.php');
 
     $action = optional_param('action', '', PARAM_ALPHANUMEXT);
     $filterpath = optional_param('filterpath', '', PARAM_SAFEDIR);
@@ -44,9 +45,6 @@
     $returnurl = "$CFG->wwwroot/$CFG->admin/filters.php";
     admin_externalpage_setup('managefilters');
 
-    // Purge all caches related to filter administration.
-    cache::make('core', 'plugininfo_filter')->purge();
-
     $filters = filter_get_global_states();
 
     // In case any new filters have been installed, but not put in the table yet.
@@ -59,7 +57,7 @@
 /// Process actions ============================================================
 
     if ($action) {
-        if (!isset($filters[$filterpath]) && !isset($newfilters[$filterpath])) {
+        if ($action !== 'delete' and !isset($filters[$filterpath]) and !isset($newfilters[$filterpath])) {
             throw new moodle_exception('filternotinstalled', 'error', $returnurl, $filterpath);
         }
 
 
     // Reset caches and return
     if ($action) {
+        plugin_manager::reset_caches();
         reset_text_filters_cache();
         redirect($returnurl);
     }
index be09151..8286e96 100644 (file)
@@ -30,6 +30,7 @@
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/tablelib.php');
+require_once($CFG->libdir.'/pluginlib.php');
 
 admin_externalpage_setup('managelocalplugins');
 
index 88e8f85..67a60b0 100644 (file)
@@ -24,6 +24,7 @@
 require_once(dirname(__FILE__) . '/../config.php');
 require_once($CFG->dirroot . '/message/lib.php');
 require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/pluginlib.php');
 
 // This is an admin page
 admin_externalpage_setup('managemessageoutputs');
@@ -44,6 +45,7 @@ if (!empty($disable) && confirm_sesskey()) {
         print_error('outputdoesnotexist', 'message');
     }
     $DB->set_field('message_processors', 'enabled', '0', array('id'=>$processor->id));      // Disable output
+    plugin_manager::reset_caches();
 }
 
 if (!empty($enable) && confirm_sesskey()) {
@@ -51,6 +53,7 @@ if (!empty($enable) && confirm_sesskey()) {
         print_error('outputdoesnotexist', 'message');
     }
     $DB->set_field('message_processors', 'enabled', '1', array('id'=>$processor->id));      // Enable output
+    plugin_manager::reset_caches();
 }
 
 if (!empty($uninstall) && confirm_sesskey()) {
index c8f69d4..a964c73 100644 (file)
@@ -5,6 +5,7 @@
     require_once('../course/lib.php');
     require_once($CFG->libdir.'/adminlib.php');
     require_once($CFG->libdir.'/tablelib.php');
+    require_once($CFG->libdir.'/pluginlib.php');
 
     // defines
     define('MODULE_TABLE','module_administration_table');
@@ -27,9 +28,6 @@
     $stractivitymodule = get_string("activitymodule");
     $strshowmodulecourse = get_string('showmodulecourse');
 
-    // Purge all caches related to activity modules administration.
-    cache::make('core', 'plugininfo_mod')->purge();
-
 /// If data submitted, then process and store.
 
     if (!empty($hide) and confirm_sesskey()) {
@@ -50,6 +48,7 @@
                                 FROM {course_modules}
                                WHERE visibleold=1 AND module=?)",
                 array($module->id));
+        plugin_manager::reset_caches();
         admin_get_root(true, false);  // settings not required - only pages
     }
 
@@ -66,6 +65,7 @@
                                 FROM {course_modules}
                                WHERE visible=1 AND module=?)",
                 array($module->id));
+        plugin_manager::reset_caches();
         admin_get_root(true, false);  // settings not required - only pages
     }
 
             $visible = "";
             $class = "";
         }
-
+        $version = get_config('mod_'.$module->name, 'version');
 
         $table->add_data(array(
             '<span'.$class.'>'.$strmodulename.'</span>',
             $countlink,
-            '<span'.$class.'>'.$module->version.'</span>',
+            '<span'.$class.'>'.$version.'</span>',
             $visible,
             $uninstall,
             $settings
index ace710f..d5fc85a 100644 (file)
@@ -29,6 +29,8 @@
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/tablelib.php');
+require_once($CFG->libdir.'/pluginlib.php');
+
 
 admin_externalpage_setup('manageplagiarismplugins');
 
index f594053..3b460fd 100644 (file)
@@ -4,6 +4,7 @@ require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir . '/portfoliolib.php');
 require_once($CFG->libdir . '/portfolio/forms.php');
 require_once($CFG->libdir . '/adminlib.php');
+require_once($CFG->libdir . '/pluginlib.php');
 
 $portfolio     = optional_param('pf', '', PARAM_ALPHANUMEXT);
 $action        = optional_param('action', '', PARAM_ALPHA);
@@ -43,9 +44,6 @@ $configstr  = get_string('manageportfolios', 'portfolio');
 
 $return = true; // direct back to the main page
 
-// Purge all caches related to portfolio administration.
-cache::make('core', 'plugininfo_portfolio')->purge();
-
 /**
  * Helper function that generates a moodle_url object
  * relevant to the portfolio
@@ -91,6 +89,7 @@ if (($action == 'edit') || ($action == 'new')) {
         } else {
             portfolio_static_function($plugin, 'create_instance', $plugin, $fromform->name, $fromform);
         }
+        plugin_manager::reset_caches();
         $savedstr = get_string('instancesaved', 'portfolio');
         redirect($baseurl, $savedstr, 1);
         exit;
@@ -119,6 +118,7 @@ if (($action == 'edit') || ($action == 'new')) {
 
     $instance->set('visible', $visible);
     $instance->save();
+    plugin_manager::reset_caches();
     $return = true;
 } else if ($action == 'delete') {
     $instance = portfolio_instance($portfolio);
index 85f0c8e..8e98af4 100644 (file)
@@ -92,6 +92,7 @@ if (($disable = optional_param('disable', '', PARAM_PLUGIN)) && confirm_sesskey(
         $disabledbehaviours[] = $disable;
         set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question');
     }
+    plugin_manager::reset_caches();
     redirect($thispageurl);
 }
 
@@ -109,6 +110,7 @@ if (($enable = optional_param('enable', '', PARAM_PLUGIN)) && confirm_sesskey())
         unset($disabledbehaviours[$key]);
         set_config('disabledbehaviours', implode(',', $disabledbehaviours), 'question');
     }
+    plugin_manager::reset_caches();
     redirect($thispageurl);
 }
 
index 0a73d22..879497c 100644 (file)
@@ -30,6 +30,7 @@
 require_once(dirname(__FILE__) . '/../config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/tablelib.php');
+require_once($CFG->libdir.'/pluginlib.php');
 
 admin_externalpage_setup('managereports');
 
index 8f976cf..05fc755 100644 (file)
@@ -17,6 +17,7 @@
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->dirroot . '/repository/lib.php');
 require_once($CFG->libdir . '/adminlib.php');
+require_once($CFG->libdir . '/pluginlib.php');
 
 $repository       = optional_param('repos', '', PARAM_ALPHANUMEXT);
 $action           = optional_param('action', '', PARAM_ALPHANUMEXT);
@@ -61,9 +62,6 @@ if (!empty($action)) {
     require_sesskey();
 }
 
-// Purge all caches related to repositories administration.
-cache::make('core', 'plugininfo_repository')->purge();
-
 /**
  * Helper function that generates a moodle_url object
  * relevant to the repository
@@ -151,6 +149,7 @@ if (($action == 'edit') || ($action == 'new')) {
         }
         if ($success) {
             // configs saved
+            plugin_manager::reset_caches();
             redirect($baseurl);
         } else {
             print_error('instancenotsaved', 'repository', $baseurl);
@@ -191,6 +190,7 @@ if (($action == 'edit') || ($action == 'new')) {
         print_error('invalidplugin', 'repository', '', $repository);
     }
     $repositorytype->update_visibility(true);
+    plugin_manager::reset_caches();
     $return = true;
 } else if ($action == 'hide') {
     if (!confirm_sesskey()) {
@@ -201,6 +201,7 @@ if (($action == 'edit') || ($action == 'new')) {
         print_error('invalidplugin', 'repository', '', $repository);
     }
     $repositorytype->update_visibility(false);
+    plugin_manager::reset_caches();
     $return = true;
 } else if ($action == 'delete') {
     $repositorytype = repository::get_type_by_typename($repository);
@@ -211,6 +212,7 @@ if (($action == 'edit') || ($action == 'new')) {
         }
 
         if ($repositorytype->delete($downloadcontents)) {
+            plugin_manager::reset_caches();
             redirect($baseurl);
         } else {
             print_error('instancenotdeleted', 'repository', $baseurl);
index b5eab73..3def4f3 100644 (file)
@@ -17,6 +17,7 @@
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->dirroot . '/repository/lib.php');
 require_once($CFG->libdir . '/adminlib.php');
+require_once($CFG->libdir . '/pluginlib.php');
 
 require_sesskey();
 
@@ -102,6 +103,7 @@ if (!empty($edit) || !empty($new)) {
             $data = data_submitted();
         }
         if ($success) {
+            plugin_manager::reset_caches();
             redirect($parenturl);
         } else {
             print_error('instancenotsaved', 'repository', $parenturl);
@@ -118,6 +120,7 @@ if (!empty($edit) || !empty($new)) {
 } else if (!empty($hide)) {
     $instance = repository::get_type_by_typename($hide);
     $instance->hide();
+    plugin_manager::reset_caches();
     $return = true;
 } else if (!empty($delete)) {
     $instance = repository::get_instance($delete);
@@ -130,6 +133,7 @@ if (!empty($edit) || !empty($new)) {
     if ($sure) {
         if ($instance->delete($downloadcontents)) {
             $deletedstr = get_string('instancedeleted', 'repository');
+            plugin_manager::reset_caches();
             redirect($parenturl, $deletedstr, 3);
         } else {
             print_error('instancenotdeleted', 'repository', $parenturl);
index c3df06b..3973a8b 100644 (file)
@@ -866,9 +866,11 @@ class moodle1_course_outline_handler extends moodle1_xml_handler {
         // host...
         $versionfile = $CFG->dirroot.'/mod/'.$data['modulename'].'/version.php';
         if (file_exists($versionfile)) {
-            $module = new stdClass();
+            $plugin = new stdClass();
+            $plugin->version = null;
+            $module = $plugin;
             include($versionfile);
-            $data['version'] = $module->version;
+            $data['version'] = $plugin->version;
         } else {
             $data['version'] = null;
         }
index e2f47f6..ed53da7 100644 (file)
@@ -307,6 +307,7 @@ abstract class backup_block_structure_step extends backup_structure_step {
 class backup_module_structure_step extends backup_structure_step {
 
     protected function define_structure() {
+        global $DB;
 
         // Define each element separated
 
@@ -339,12 +340,14 @@ class backup_module_structure_step extends backup_structure_step {
         $availinfo->add_child($availabilityfield);
 
         // Set the sources
-        $module->set_source_sql('
-            SELECT cm.*, m.version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
+        $concat = $DB->sql_concat("'mod_'", 'm.name');
+        $module->set_source_sql("
+            SELECT cm.*, cp.value AS version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
               FROM {course_modules} cm
               JOIN {modules} m ON m.id = cm.module
+              JOIN {config_plugins} cp ON cp.plugin = $concat AND cp.name = 'version'
               JOIN {course_sections} s ON s.id = cm.section
-             WHERE cm.id = ?', array(backup::VAR_MODID));
+             WHERE cm.id = ?", array(backup::VAR_MODID));
 
         $availability->set_source_table('course_modules_availability', array('coursemoduleid' => backup::VAR_MODID));
         $availabilityfield->set_source_sql('
@@ -1363,7 +1366,7 @@ class backup_block_instance_structure_step extends backup_structure_step {
         }
         $blockrec->contextid = $this->task->get_contextid();
         // Get the version of the block
-        $blockrec->version = $DB->get_field('block', 'version', array('name' => $this->task->get_blockname()));
+        $blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version');
 
         // Define sources
 
index 2351805..1a9ad48 100644 (file)
@@ -1,6 +1,10 @@
 This files describes API changes in /blocks/* - activity modules,
 information provided here is intended especially for developers.
 
+=== 2.6 ===
+
+* Deprecated /admin/block.php was removed, make sure blocks are using settings.php instead.
+
 === 2.4 ===
 
 Created new capability 'blocks/xxx:myaddinstance' that determines whether a user can add
diff --git a/cache/locks/file/version.php b/cache/locks/file/version.php
new file mode 100644 (file)
index 0000000..b7955da
--- /dev/null
@@ -0,0 +1,30 @@
+<?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/>.
+
+/**
+ * File locking for the Cache API
+ *
+ * @package    cachelock_file
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2013091300;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2013091300;        // Requires this Moodle version
+$plugin->component = 'cachelock_file';  // Full name of the plugin (used for diagnostics)
index 49d80ce..15b493c 100644 (file)
@@ -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)) {
index 2ec0c97..40021ed 100644 (file)
@@ -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';
index efcf9a1..7ca2127 100644 (file)
@@ -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;
             }
         }
     }
index 0e21af3..e193399 100644 (file)
@@ -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;
             }
         }
 
index 305fdbc..0b1a99d 100644 (file)
@@ -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
index d79981f..8fe1b98 100644 (file)
@@ -1,5 +1,5 @@
 <?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"
 >
       <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"/>
       <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"/>
index 543d1b3..4768e85 100644 (file)
@@ -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;
 }
index f2ec963..2b9b8a6 100644 (file)
@@ -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));
         }
 
     }
index d30f3bf..2bc6130 100644 (file)
@@ -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]);
-    }
 }
 
 
index 29236e8..6eb01c4 100644 (file)
@@ -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);
index 92091ea..ad1e363 100644 (file)
@@ -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 {
index cc8ae89..9172fc1 100644 (file)
@@ -61,6 +61,12 @@ class plugin_manager {
     protected $pluginsinfo = null;
     /** @var array of raw subplugins information */
     protected $subpluginsinfo = null;
+    /** @var array list of installed plugins $name=>$version */
+    protected $installedplugins = null;
+    /** @var array list of all enabled plugins $name=>$name */
+    protected $enabledplugins = null;
+    /** @var array list of all enabled plugins $name=>$diskversion */
+    protected $presentplugins = null;
 
     /**
      * Direct initiation not allowed, use the factory method {@link self::instance()}
@@ -87,94 +93,273 @@ class plugin_manager {
     }
 
     /**
-     * Reset any caches
+     * Reset all caches.
      * @param bool $phpunitreset
      */
     public static function reset_caches($phpunitreset = false) {
         if ($phpunitreset) {
             self::$singletoninstance = null;
+        } else {
+            if (self::$singletoninstance) {
+                self::$singletoninstance->pluginsinfo = null;
+                self::$singletoninstance->subpluginsinfo = null;
+                self::$singletoninstance->installedplugins = null;
+                self::$singletoninstance->enabledplugins = null;
+                self::$singletoninstance->presentplugins = null;
+            }
         }
+        $cache = cache::make('core', 'plugin_manager');
+        $cache->purge();
     }
 
     /**
      * Returns the result of {@link core_component::get_plugin_types()} ordered for humans
      *
      * @see self::reorder_plugin_types()
-     * @param bool $fullpaths false means relative paths from dirroot
      * @return array (string)name => (string)location
      */
-    public function get_plugin_types($fullpaths = true) {
-        return $this->reorder_plugin_types(core_component::get_plugin_types($fullpaths));
+    public function get_plugin_types() {
+        if (func_num_args() > 0) {
+            if (!func_get_arg(0)) {
+                throw coding_exception('plugin_manager->get_plugin_types() does not support relative paths.');
+            }
+        }
+        return $this->reorder_plugin_types(core_component::get_plugin_types());
     }
 
     /**
-     * Returns list of known plugins of the given type
+     * Load list of installed plugins,
+     * always call before using $this->installedplugins.
      *
-     * This method returns the subset of the tree returned by {@link self::get_plugins()}.
-     * If the given type is not known, empty array is returned.
+     * This method is caching results for all plugins.
+     */
+    protected function load_installed_plugins() {
+        global $DB, $CFG;
+
+        if ($this->installedplugins) {
+            return;
+        }
+
+        if (empty($CFG->version)) {
+            // Nothing installed yet.
+            $this->installedplugins = array();
+            return;
+        }
+
+        $cache = cache::make('core', 'plugin_manager');
+        $installed = $cache->get('installed');
+
+        if (is_array($installed)) {
+            $this->installedplugins = $installed;
+            return;
+        }
+
+        $this->installedplugins = array();
+
+        if ($CFG->version < 2013092001.02) {
+            // We did not upgrade the database yet.
+            $modules = $DB->get_records('modules', array(), 'name ASC', 'id, name, version');
+            foreach ($modules as $module) {
+                $this->installedplugins['mod'][$module->name] = $module->version;
+            }
+            $blocks = $DB->get_records('block', array(), 'name ASC', 'id, name, version');
+            foreach ($blocks as $block) {
+                $this->installedplugins['block'][$block->name] = $block->version;
+            }
+        }
+
+        $versions = $DB->get_records('config_plugins', array('name'=>'version'));
+        foreach ($versions as $version) {
+            $parts = explode('_', $version->plugin, 2);
+            if (!isset($parts[1])) {
+                // Invalid component, there must be at least one "_".
+                continue;
+            }
+            // Do not verify here if plugin type and name are valid.
+            $this->installedplugins[$parts[0]][$parts[1]] = $version->value;
+        }
+
+        foreach ($this->installedplugins as $key => $value) {
+            ksort($this->installedplugins[$key]);
+        }
+
+        $cache->set('installed', $this->installedplugins);
+    }
+
+    /**
+     * Return list of installed plugins of given type.
+     * @param string $type
+     * @return array $name=>$version
+     */
+    public function get_installed_plugins($type) {
+        $this->load_installed_plugins();
+        if (isset($this->installedplugins[$type])) {
+            return $this->installedplugins[$type];
+        }
+        return array();
+    }
+
+    /**
+     * Load list of all enabled plugins,
+     * call before using $this->enabledplugins.
      *
-     * @param string $type plugin type, e.g. 'mod' or 'workshopallocation'
-     * @param bool $disablecache force reload, cache can be used otherwise
-     * @return array (string)plugin name (e.g. 'workshop') => corresponding subclass of {@link plugininfo_base}
+     * This method is caching results from individual plugin info classes.
      */
-    public function get_plugins_of_type($type, $disablecache=false) {
+    protected function load_enabled_plugins() {
+        global $CFG;
 
-        $plugins = $this->get_plugins($disablecache);
+        if ($this->enabledplugins) {
+            return;
+        }
 
-        if (!isset($plugins[$type])) {
-            return array();
+        if (empty($CFG->version)) {
+            $this->enabledplugins = array();
+            return;
         }
 
-        return $plugins[$type];
+        $cache = cache::make('core', 'plugin_manager');
+        $enabled = $cache->get('enabled');
+
+        if (is_array($enabled)) {
+            $this->enabledplugins = $enabled;
+            return;
+        }
+
+        $this->enabledplugins = array();
+
+        require_once($CFG->libdir.'/adminlib.php');
+
+        $plugintypes = core_component::get_plugin_types();
+        foreach ($plugintypes as $plugintype => $fulldir) {
+            // Hack: include mod and editor subplugin management classes first,
+            //       the adminlib.php is supposed to contain extra admin settings too.
+            $plugininfoclass = 'plugininfo_' . $plugintype;
+            if (!class_exists($plugininfoclass) and file_exists("$fulldir/adminlib.php")) {
+                include_once("$fulldir/adminlib.php");
+            }
+            if (class_exists($plugininfoclass)) {
+                $enabled = $plugininfoclass::get_enabled_plugins();
+                if (!is_array($enabled)) {
+                    continue;
+                }
+                $this->enabledplugins[$plugintype] = $enabled;
+            }
+        }
+
+        $cache->set('enabled', $this->enabledplugins);
+    }
+
+    /**
+     * Get list of enabled plugins of given type,
+     * the result may contain missing plugins.
+     *
+     * @param string $type
+     * @return array|null  list of enabled plugins of this type, null if unknown
+     */
+    public function get_enabled_plugins($type) {
+        $this->load_enabled_plugins();
+        if (isset($this->enabledplugins[$type])) {
+            return $this->enabledplugins[$type];
+        }
+        return null;
+    }
+
+    /**
+     * Load list of all present plugins - call before using $this->presentplugins.
+     */
+    protected function load_present_plugins() {
+        if ($this->presentplugins) {
+            return;
+        }
+
+        $cache = cache::make('core', 'plugin_manager');
+        $present = $cache->get('present');
+
+        if (is_array($present)) {
+            $this->presentplugins = $present;
+            return;
+        }
+
+        $this->presentplugins = array();
+
+        $plugintypes = core_component::get_plugin_types();
+        foreach ($plugintypes as $type => $typedir) {
+            $plugs = core_component::get_plugin_list($type);
+            foreach ($plugs as $plug => $fullplug) {
+                $plugin = new stdClass();
+                $plugin->version = null;
+                $module = $plugin;
+                @include($fullplug.'/version.php');
+                $this->presentplugins[$type][$plug] = $plugin;
+            }
+        }
+
+        $cache->set('present', $this->presentplugins);
+    }
+
+    /**
+     * Get list of present plugins of given type.
+     *
+     * @param string $type
+     * @return array|null  list of presnet plugins $name=>$diskversion, null if unknown
+     */
+    public function get_present_plugins($type) {
+        $this->load_present_plugins();
+        if (isset($this->presentplugins[$type])) {
+            return $this->presentplugins[$type];
+        }
+        return null;
     }
 
     /**
      * Returns a tree of known plugins and information about them
      *
-     * @param bool $disablecache force reload, cache can be used otherwise
      * @return array 2D array. The first keys are plugin type names (e.g. qtype);
      *      the second keys are the plugin local name (e.g. multichoice); and
      *      the values are the corresponding objects extending {@link plugininfo_base}
      */
-    public function get_plugins($disablecache=false) {
+    public function get_plugins() {
         global $CFG;
 
-        if ($disablecache or is_null($this->pluginsinfo)) {
-            // Hack: include mod and editor subplugin management classes first,
-            //       the adminlib.php is supposed to contain extra admin settings too.
-            require_once($CFG->libdir.'/adminlib.php');
-            foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
-                foreach (core_component::get_plugin_list($type) as $dir) {
-                    if (file_exists("$dir/adminlib.php")) {
-                        include_once("$dir/adminlib.php");
-                    }
+        if (is_array($this->pluginsinfo)) {
+            return $this->pluginsinfo;
+        }
+
+        $this->pluginsinfo = array();
+
+        // Hack: include mod and editor subplugin management classes first,
+        //       the adminlib.php is supposed to contain extra admin settings too.
+        require_once($CFG->libdir.'/adminlib.php');
+        foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
+            foreach (core_component::get_plugin_list($type) as $dir) {
+                if (file_exists("$dir/adminlib.php")) {
+                    include_once("$dir/adminlib.php");
                 }
             }
-            $this->pluginsinfo = array();
-            $plugintypes = $this->get_plugin_types();
-            foreach ($plugintypes as $plugintype => $plugintyperootdir) {
-                if (in_array($plugintype, array('base', 'general'))) {
-                    throw new coding_exception('Illegal usage of reserved word for plugin type');
-                }
-                if (class_exists('plugininfo_' . $plugintype)) {
-                    $plugintypeclass = 'plugininfo_' . $plugintype;
-                } else {
-                    $plugintypeclass = 'plugininfo_general';
-                }
-                if (!in_array('plugininfo_base', class_parents($plugintypeclass))) {
-                    throw new coding_exception('Class ' . $plugintypeclass . ' must extend plugininfo_base');
-                }
-                $plugins = call_user_func(array($plugintypeclass, 'get_plugins'), $plugintype, $plugintyperootdir, $plugintypeclass);
-                $this->pluginsinfo[$plugintype] = $plugins;
+        }
+        $plugintypes = $this->get_plugin_types();
+        foreach ($plugintypes as $plugintype => $plugintyperootdir) {
+            if (in_array($plugintype, array('base', 'general'))) {
+                throw new coding_exception('Illegal usage of reserved word for plugin type');
+            }
+            if (class_exists('plugininfo_' . $plugintype)) {
+                $plugintypeclass = 'plugininfo_' . $plugintype;
+            } else {
+                $plugintypeclass = 'plugininfo_general';
+            }
+            if (!in_array('plugininfo_base', class_parents($plugintypeclass))) {
+                throw new coding_exception('Class ' . $plugintypeclass . ' must extend plugininfo_base');
             }
+            $plugins = $plugintypeclass::get_plugins($plugintype, $plugintyperootdir, $plugintypeclass);
+            $this->pluginsinfo[$plugintype] = $plugins;
+        }
 
-            if (empty($CFG->disableupdatenotifications) and !during_initial_install()) {
-                // append the information about available updates provided by {@link available_update_checker()}
-                $provider = available_update_checker::instance();
-                foreach ($this->pluginsinfo as $plugintype => $plugins) {
-                    foreach ($plugins as $plugininfoholder) {
-                        $plugininfoholder->check_available_updates($provider);
-                    }
+        if (empty($CFG->disableupdatenotifications) and !during_initial_install()) {
+            // append the information about available updates provided by {@link available_update_checker()}
+            $provider = available_update_checker::instance();
+            foreach ($this->pluginsinfo as $plugintype => $plugins) {
+                foreach ($plugins as $plugininfoholder) {
+                    $plugininfoholder->check_available_updates($provider);
                 }
             }
         }
@@ -183,24 +368,43 @@ class plugin_manager {
     }
 
     /**
-     * Returns list of all known subplugins of the given plugin
+     * Returns list of known plugins of the given type.
+     *
+     * This method returns the subset of the tree returned by {@link self::get_plugins()}.
+     * If the given type is not known, empty array is returned.
+     *
+     * @param string $type plugin type, e.g. 'mod' or 'workshopallocation'
+     * @return array (string)plugin name (e.g. 'workshop') => corresponding subclass of {@link plugininfo_base}
+     */
+    public function get_plugins_of_type($type) {
+
+        $plugins = $this->get_plugins();
+
+        if (!isset($plugins[$type])) {
+            return array();
+        }
+
+        return $plugins[$type];
+    }
+
+    /**
+     * Returns list of all known subplugins of the given plugin.
      *
      * For plugins that do not provide subplugins (i.e. there is no support for it),
      * empty array is returned.
      *
      * @param string $component full component name, e.g. 'mod_workshop'
-     * @param bool $disablecache force reload, cache can be used otherwise
      * @return array (string) component name (e.g. 'workshopallocation_random') => subclass of {@link plugininfo_base}
      */
-    public function get_subplugins_of_plugin($component, $disablecache=false) {
+    public function get_subplugins_of_plugin($component) {
 
-        $pluginfo = $this->get_plugin_info($component, $disablecache);
+        $pluginfo = $this->get_plugin_info($component);
 
         if (is_null($pluginfo)) {
             return array();
         }
 
-        $subplugins = $this->get_subplugins($disablecache);
+        $subplugins = $this->get_subplugins();
 
         if (!isset($subplugins[$pluginfo->component])) {
             return array();
@@ -221,28 +425,29 @@ class plugin_manager {
      * Returns list of plugins that define their subplugins and the information
      * about them from the db/subplugins.php file.
      *
-     * @param bool $disablecache force reload, cache can be used otherwise
      * @return array with keys like 'mod_quiz', and values the data from the
      *      corresponding db/subplugins.php file.
      */
-    public function get_subplugins($disablecache=false) {
-
-        if ($disablecache or is_null($this->subpluginsinfo)) {
-            $this->subpluginsinfo = array();
-            foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
-                foreach (core_component::get_plugin_list($type) as $component => $ownerdir) {
-                    $componentsubplugins = array();
-                    if (file_exists($ownerdir . '/db/subplugins.php')) {
-                        $subplugins = array();
-                        include($ownerdir . '/db/subplugins.php');
-                        foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
-                            $subplugin = new stdClass();
-                            $subplugin->type = $subplugintype;
-                            $subplugin->typerootdir = $subplugintyperootdir;
-                            $componentsubplugins[$subplugintype] = $subplugin;
-                        }
-                        $this->subpluginsinfo[$type . '_' . $component] = $componentsubplugins;
+    public function get_subplugins() {
+
+        if (is_array($this->subpluginsinfo)) {
+            return $this->subpluginsinfo;
+        }
+
+        $this->subpluginsinfo = array();
+        foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
+            foreach (core_component::get_plugin_list($type) as $component => $ownerdir) {
+                $componentsubplugins = array();
+                if (file_exists($ownerdir . '/db/subplugins.php')) {
+                    $subplugins = array();
+                    include($ownerdir . '/db/subplugins.php');
+                    foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
+                        $subplugin = new stdClass();
+                        $subplugin->type = $subplugintype;
+                        $subplugin->typerootdir = $subplugintyperootdir;
+                        $componentsubplugins[$subplugintype] = $subplugin;
                     }
+                    $this->subpluginsinfo[$type . '_' . $component] = $componentsubplugins;
                 }
             }
         }
@@ -350,12 +555,11 @@ class plugin_manager {
      * Returns information about the known plugin, or null
      *
      * @param string $component frankenstyle component name.
-     * @param bool $disablecache force reload, cache can be used otherwise
      * @return plugininfo_base|null the corresponding plugin information.
      */
-    public function get_plugin_info($component, $disablecache=false) {
-        list($type, $name) = $this->normalize_component($component);
-        $plugins = $this->get_plugins($disablecache);
+    public function get_plugin_info($component) {
+        list($type, $name) = core_component::normalize_component($component);
+        $plugins = $this->get_plugins();
         if (isset($plugins[$type][$name])) {
             return $plugins[$type][$name];
         } else {
@@ -637,6 +841,7 @@ class plugin_manager {
         $plugins = array(
             'qformat' => array('blackboard'),
             'enrol' => array('authorize'),
+            'tool' => array('bloglevelupgrade'),
         );
 
         if (!isset($plugins[$type])) {
@@ -879,18 +1084,6 @@ class plugin_manager {
         }
     }
 
-    /**
-     * Wrapper for the core function {@link core_component::normalize_component()}.
-     *
-     * This is here just to make it possible to mock it in unit tests.
-     *
-     * @param string $component
-     * @return array
-     */
-    protected function normalize_component($component) {
-        return core_component::normalize_component($component);
-    }
-
     /**
      * Reorders plugin types into a sequence to be displayed
      *
@@ -2420,7 +2613,6 @@ class plugininfo_default_factory {
         $plugin->init_display_name();
         $plugin->load_disk_version();
         $plugin->load_db_version();
-        $plugin->load_required_main_version();
         $plugin->init_is_standard();
 
         return $plugin;
@@ -2445,7 +2637,7 @@ abstract class plugininfo_base {
     public $displayname;
     /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
     public $source;
-    /** @var fullpath to the location of this plugin */
+    /** @var string fullpath to the location of this plugin */
     public $rootdir;
     /** @var int|string the version of the plugin's source code */
     public $versiondisk;
@@ -2463,7 +2655,16 @@ abstract class plugininfo_base {
     public $availableupdates;
 
     /**
-     * Gathers and returns the information about all plugins of the given type
+     * 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() {
+        return null;
+    }
+
+    /**
+     * Gathers and returns the information about all plugins of the given type,
+     * either on disk or previously installed.
      *
      * @param string $type the name of the plugintype, eg. mod, auth or workshopform
      * @param string $typerootdir full path to the location of the plugin dir
@@ -2471,15 +2672,35 @@ abstract class plugininfo_base {
      * @return array of plugintype classes, indexed by the plugin name
      */
     public static function get_plugins($type, $typerootdir, $typeclass) {
-
-        // get the information about plugins at the disk
+        // Get the information about plugins at the disk.
         $plugins = core_component::get_plugin_list($type);
-        $ondisk = array();
+        $return = array();
         foreach ($plugins as $pluginname => $pluginrootdir) {
-            $ondisk[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
+            $return[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
                 $pluginname, $pluginrootdir, $typeclass);
         }
-        return $ondisk;
+
+        // Fetch missing incorrectly uninstalled plugins.
+        $manager = plugin_manager::instance();
+        $plugins = $manager->get_installed_plugins($type);
+
+        foreach ($plugins as $name => $version) {
+            if (isset($return[$name])) {
+                continue;
+            }
+            $plugin              = new $typeclass();
+            $plugin->type        = $type;
+            $plugin->typerootdir = $typerootdir;
+            $plugin->name        = $name;
+            $plugin->rootdir     = null;
+            $plugin->displayname = $name;
+            $plugin->versiondb   = $version;
+            $plugin->init_is_standard();
+
+            $return[$name] = $plugin;
+        }
+
+        return $return;
     }
 
     /**
@@ -2524,34 +2745,6 @@ abstract class plugininfo_base {
         return $this->rootdir . '/' . $relativepath;
     }
 
-    /**
-     * Load the data from version.php.
-     *
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return stdClass the object called $plugin defined in version.php
-     */
-    protected function load_version_php($disablecache=false) {
-
-        $cache = cache::make('core', 'plugininfo_base');
-
-        $versionsphp = $cache->get('versions_php');
-
-        if (!$disablecache and $versionsphp !== false and isset($versionsphp[$this->component])) {
-            return $versionsphp[$this->component];
-        }
-
-        $versionfile = $this->full_path('version.php');
-
-        $plugin = new stdClass();
-        if (is_readable($versionfile)) {
-            include($versionfile);
-        }
-        $versionsphp[$this->component] = $plugin;
-        $cache->set('versions_php', $versionsphp);
-
-        return $plugin;
-    }
-
     /**
      * Sets {@link $versiondisk} property to a numerical value representing the
      * version of the plugin's source code.
@@ -2561,33 +2754,26 @@ abstract class plugininfo_base {
      * data) or is missing from disk.
      */
     public function load_disk_version() {
-        $plugin = $this->load_version_php();
+        $versions = plugin_manager::instance()->get_present_plugins($this->type);
+
+        $this->versiondisk = null;
+        $this->versionrequires = null;
+        $this->dependencies = array();
+
+        if (!isset($versions[$this->name])) {
+            return;
+        }
+
+        $plugin = $versions[$this->name];
+
         if (isset($plugin->version)) {
             $this->versiondisk = $plugin->version;
         }
-    }
-
-    /**
-     * Sets {@link $versionrequires} property to a numerical value representing
-     * the version of Moodle core that this plugin requires.
-     */
-    public function load_required_main_version() {
-        $plugin = $this->load_version_php();
         if (isset($plugin->requires)) {
             $this->versionrequires = $plugin->requires;
         }
-    }
-
-    /**
-     * Initialise {@link $dependencies} to the list of other plugins (in any)
-     * that this one requires to be installed.
-     */
-    protected function load_other_required_plugins() {
-        $plugin = $this->load_version_php();
-        if (!empty($plugin->dependencies)) {
+        if (isset($plugin->dependencies)) {
             $this->dependencies = $plugin->dependencies;
-        } else {
-            $this->dependencies = array(); // By default, no dependencies.
         }
     }
 
@@ -2599,7 +2785,7 @@ abstract class plugininfo_base {
      */
     public function get_other_required_plugins() {
         if (is_null($this->dependencies)) {
-            $this->load_other_required_plugins();
+            $this->load_disk_version();
         }
         return $this->dependencies;
     }
@@ -2631,8 +2817,12 @@ abstract class plugininfo_base {
      * data) or has not been installed yet.
      */
     public function load_db_version() {
-        if ($ver = self::get_version_from_config_plugins($this->component)) {
-            $this->versiondb = $ver;
+        $versions = plugin_manager::instance()->get_installed_plugins($this->type);
+
+        if (isset($versions[$this->name])) {
+            $this->versiondb = $versions[$this->name];
+        } else {
+            $this->versiondb = null;
         }
     }
 
@@ -2707,7 +2897,9 @@ abstract class plugininfo_base {
                 return plugin_manager::PLUGIN_STATUS_MISSING;
             }
 
-        } else if ((string)$this->versiondb === (string)$this->versiondisk) {
+        } else if ((float)$this->versiondb === (float)$this->versiondisk) {
+            // Note: the float comparison should work fine here
+            //       because there are no arithmetic operations with the numbers.
             return plugin_manager::PLUGIN_STATUS_UPTODATE;
 
         } else if ($this->versiondb < $this->versiondisk) {
@@ -2733,7 +2925,18 @@ abstract class plugininfo_base {
      * @return null|bool
      */
     public function is_enabled() {
-        return null;
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return false;
+        }
+
+        $enabled = plugin_manager::instance()->get_enabled_plugins($this->type);
+
+        if (!is_array($enabled)) {
+            return null;
+        }
+
+        return isset($enabled[$this->name]);
     }
 
     /**
@@ -2842,7 +3045,7 @@ abstract class plugininfo_base {
      * Note that even if true is returned, the core may still prohibit the uninstallation,
      * e.g. in case there are other plugins that depend on this one.
      *
-     * @return boolean
+     * @return bool
      */
     public function is_uninstall_allowed() {
 
@@ -2920,41 +3123,10 @@ abstract class plugininfo_base {
         ));
     }
 
-    /**
-     * Provides access to plugin versions from the {config_plugins} table
-     *
-     * @param string $plugin plugin name
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return int|bool the stored value or false if not found
-     */
-    protected function get_version_from_config_plugins($plugin, $disablecache=false) {
-        global $DB;
-
-        $cache = cache::make('core', 'plugininfo_base');
-
-        $pluginversions = $cache->get('versions_db');
-
-        if ($pluginversions === false or $disablecache) {
-            try {
-                $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
-            } catch (dml_exception $e) {
-                // before install
-                $pluginversions = array();
-            }
-            $cache->set('versions_db', $pluginversions);
-        }
-
-        if (isset($pluginversions[$plugin])) {
-            return $pluginversions[$plugin];
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Provides access to the plugin_manager singleton.
      *
-     * @return plugin_manmager
+     * @return plugin_manager
      */
     protected function get_plugin_manager() {
         return plugin_manager::instance();
@@ -2973,31 +3145,14 @@ class plugininfo_general extends plugininfo_base {
  * Class for page side blocks
  */
 class plugininfo_block 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() {
+        global $DB;
 
-    public static function get_plugins($type, $typerootdir, $typeclass) {
-
-        // get the information about blocks at the disk
-        $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
-
-        // add blocks missing from disk
-        $blocksinfo = self::get_blocks_info();
-        foreach ($blocksinfo as $blockname => $blockinfo) {
-            if (isset($blocks[$blockname])) {
-                continue;
-            }
-            $plugin                 = new $typeclass();
-            $plugin->type           = $type;
-            $plugin->typerootdir    = $typerootdir;
-            $plugin->name           = $blockname;
-            $plugin->rootdir        = null;
-            $plugin->displayname    = $blockname;
-            $plugin->versiondb      = $blockinfo->version;
-            $plugin->init_is_standard();
-
-            $blocks[$blockname]   = $plugin;
-        }
-
-        return $blocks;
+        return $DB->get_records_menu('block', array('visible'=>1), 'name ASC', 'name, name AS val');
     }
 
     /**
@@ -3030,29 +3185,6 @@ class plugininfo_block extends plugininfo_base {
         }
     }
 
-    public function load_db_version() {
-        global $DB;
-
-        $blocksinfo = self::get_blocks_info();
-        if (isset($blocksinfo[$this->name]->version)) {
-            $this->versiondb = $blocksinfo[$this->name]->version;
-        }
-    }
-
-    public function is_enabled() {
-
-        $blocksinfo = self::get_blocks_info();
-        if (isset($blocksinfo[$this->name]->visible)) {
-            if ($blocksinfo[$this->name]->visible) {
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return parent::is_enabled();
-        }
-    }
-
     public function get_settings_section_name() {
         return 'blocksetting' . $this->name;
     }
@@ -3061,6 +3193,12 @@ class plugininfo_block extends plugininfo_base {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // may be used in settings.php
         $block = $this; // also can be used inside settings.php
+
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         $section = $this->get_settings_section_name();
 
         if (!$hassiteconfig || (($blockinstance = block_instance($this->name)) === false)) {
@@ -3073,11 +3211,6 @@ class plugininfo_block extends plugininfo_base {
                 $settings = new admin_settingpage($section, $this->displayname,
                         'moodle/site:config', $this->is_enabled() === false);
                 include($this->full_path('settings.php')); // this may also set $settings to null
-            } else {
-                $blocksinfo = self::get_blocks_info();
-                $settingsurl = new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
-                $settings = new admin_externalpage($section, $this->displayname,
-                        $settingsurl, 'moodle/site:config', $this->is_enabled() === false);
             }
         }
         if ($settings) {
@@ -3103,33 +3236,7 @@ class plugininfo_block extends plugininfo_base {
 
         return '<p>'.get_string('uninstallextraconfirmblock', 'core_plugin', array('instances'=>$count)).'</p>';
     }
-
-    /**
-     * Provides access to the records in {block} table
-     *
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return array array of stdClasses
-     */
-    protected static function get_blocks_info($disablecache=false) {
-        global $DB;
-
-        $cache = cache::make('core', 'plugininfo_block');
-
-        $blocktypes = $cache->get('blocktypes');
-
-        if ($blocktypes === false or $disablecache) {
-            try {
-                $blocktypes = $DB->get_records('block', null, 'name', 'name,id,version,visible');
-            } catch (dml_exception $e) {
-                // before install
-                $blocktypes = array();
-            }
-            $cache->set('blocktypes', $blocktypes);
-        }
-
-        return $blocktypes;
-    }
-}
+}
 
 
 /**
@@ -3137,80 +3244,29 @@ class plugininfo_block extends plugininfo_base {
  */
 class plugininfo_filter extends plugininfo_base {
 
-    public static function get_plugins($type, $typerootdir, $typeclass) {
-        global $CFG, $DB;
-
-        $filters = array();
-
-        // get the list of filters in /filter location
-        $installed = filter_get_all_installed();
-
-        foreach ($installed as $name => $displayname) {
-            $plugin                 = new $typeclass();
-            $plugin->type           = $type;
-            $plugin->typerootdir    = $typerootdir;
-            $plugin->name           = $name;
-            $plugin->rootdir        = "$CFG->dirroot/filter/$name";
-            $plugin->displayname    = $displayname;
-
-            $plugin->load_disk_version();
-            $plugin->load_db_version();
-            $plugin->load_required_main_version();
-            $plugin->init_is_standard();
-
-            $filters[$plugin->name] = $plugin;
-        }
-
-        // Do not mess with filter registration here!
-
-        $globalstates = self::get_global_states();
-
-        // make sure that all registered filters are installed, just in case
-        foreach ($globalstates as $name => $info) {
-            if (!isset($filters[$name])) {
-                // oops, there is a record in filter_active but the filter is not installed
-                $plugin                 = new $typeclass();
-                $plugin->type           = $type;
-                $plugin->typerootdir    = $typerootdir;
-                $plugin->name           = $name;
-                $plugin->rootdir        = "$CFG->dirroot/filter/$name";
-                $plugin->displayname    = $name;
-
-                $plugin->load_db_version();
-
-                if (is_null($plugin->versiondb)) {
-                    // this is a hack to stimulate 'Missing from disk' error
-                    // because $plugin->versiondisk will be null !== false
-                    $plugin->versiondb = false;
-                }
-
-                $filters[$plugin->name] = $plugin;
-            }
-        }
-
-        return $filters;
-    }
-
     public function init_display_name() {
-        // do nothing, the name is set in self::get_plugins()
+        if (!get_string_manager()->string_exists('filtername', $this->component)) {
+            $this->displayname = '[filtername,' . $this->component . ']';
+        } else {
+            $this->displayname = get_string('filtername', $this->component);
+        }
     }
 
-    public function is_enabled() {
-
-        $globalstates = self::get_global_states();
+    /**
+     * 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() {
+        global $DB, $CFG;
+        require_once("$CFG->libdir/filterlib.php");
 
-        foreach ($globalstates as $name => $info) {
-            if ($name === $this->name) {
-                if ($info->active == TEXTFILTER_DISABLED) {
-                    return false;
-                } else {
-                    // it may be 'On' or 'Off, but available'
-                    return null;
-                }
-            }
+        $enabled = array();
+        $filters = $DB->get_records_select('filter_active', "active <> :disabled", array('disabled'=>TEXTFILTER_DISABLED), 'filter ASC', 'id, filter');
+        foreach ($filters as $filter) {
+            $enabled[$filter->filter] = $filter->filter;
         }
 
-        return null;
+        return $enabled;
     }
 
     public function get_settings_section_name() {
@@ -3222,6 +3278,11 @@ class plugininfo_filter extends plugininfo_base {
         $ADMIN = $adminroot; // may be used in settings.php
         $filter = $this; // also can be used inside settings.php
 
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         $settings = null;
         if ($hassiteconfig && file_exists($this->full_path('filtersettings.php'))) {
             $section = $this->get_settings_section_name();
@@ -3241,50 +3302,6 @@ class plugininfo_filter extends plugininfo_base {
     public function get_uninstall_url() {
         return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $this->name, 'action' => 'delete'));
     }
-
-    /**
-     * Provides access to the results of {@link filter_get_global_states()}
-     * but indexed by the normalized filter name
-     *
-     * The legacy filter name is available as ->legacyname property.
-     *
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return array
-     */
-    protected static function get_global_states($disablecache=false) {
-        global $DB;
-
-        $cache = cache::make('core', 'plugininfo_filter');
-
-        $globalstates = $cache->get('globalstates');
-
-        if ($globalstates === false or $disablecache) {
-
-            if (!$DB->get_manager()->table_exists('filter_active')) {
-                // Not installed yet.
-                $cache->set('globalstates', array());
-                return array();
-            }
-
-            $globalstates = array();
-
-            foreach (filter_get_global_states() as $name => $info) {
-                if (strpos($name, '/') !== false) {
-                    // Skip existing before upgrade to new names.
-                    continue;
-                }
-
-                $filterinfo = new stdClass();
-                $filterinfo->active = $info->active;
-                $filterinfo->sortorder = $info->sortorder;
-                $globalstates[$name] = $filterinfo;
-            }
-
-            $cache->set('globalstates', $globalstates);
-        }
-
-        return $globalstates;
-    }
 }
 
 
@@ -3292,31 +3309,13 @@ class plugininfo_filter extends plugininfo_base {
  * Class for activity modules
  */
 class plugininfo_mod extends plugininfo_base {
-
-    public static function get_plugins($type, $typerootdir, $typeclass) {
-
-        // get the information about plugins at the disk
-        $modules = parent::get_plugins($type, $typerootdir, $typeclass);
-
-        // add modules missing from disk
-        $modulesinfo = self::get_modules_info();
-        foreach ($modulesinfo as $modulename => $moduleinfo) {
-            if (isset($modules[$modulename])) {
-                continue;
-            }
-            $plugin                 = new $typeclass();
-            $plugin->type           = $type;
-            $plugin->typerootdir    = $typerootdir;
-            $plugin->name           = $modulename;
-            $plugin->rootdir        = null;
-            $plugin->displayname    = $modulename;
-            $plugin->versiondb      = $moduleinfo->version;
-            $plugin->init_is_standard();
-
-            $modules[$modulename]   = $plugin;
-        }
-
-        return $modules;
+    /**
+     * 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() {
+        global $DB;
+        return $DB->get_records_menu('modules', array('visible'=>1), 'name ASC', 'name, name AS val');
     }
 
     /**
@@ -3344,61 +3343,6 @@ class plugininfo_mod extends plugininfo_base {
         }
     }
 
-    /**
-     * Load the data from version.php.
-     *
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return object the data object defined in version.php.
-     */
-    protected function load_version_php($disablecache=false) {
-
-        $cache = cache::make('core', 'plugininfo_mod');
-
-        $versionsphp = $cache->get('versions_php');
-
-        if (!$disablecache and $versionsphp !== false and isset($versionsphp[$this->component])) {
-            return $versionsphp[$this->component];
-        }
-
-        $versionfile = $this->full_path('version.php');
-
-        $module = new stdClass();
-        $plugin = new stdClass();
-        if (is_readable($versionfile)) {
-            include($versionfile);
-        }
-        if (!isset($module->version) and isset($plugin->version)) {
-            $module = $plugin;
-        }
-        $versionsphp[$this->component] = $module;
-        $cache->set('versions_php', $versionsphp);
-
-        return $module;
-    }
-
-    public function load_db_version() {
-        global $DB;
-
-        $modulesinfo = self::get_modules_info();
-        if (isset($modulesinfo[$this->name]->version)) {
-            $this->versiondb = $modulesinfo[$this->name]->version;
-        }
-    }
-
-    public function is_enabled() {
-
-        $modulesinfo = self::get_modules_info();
-        if (isset($modulesinfo[$this->name]->visible)) {
-            if ($modulesinfo[$this->name]->visible) {
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return parent::is_enabled();
-        }
-    }
-
     public function get_settings_section_name() {
         return 'modsetting' . $this->name;
     }
@@ -3407,11 +3351,16 @@ class plugininfo_mod extends plugininfo_base {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // may be used in settings.php
         $module = $this; // also can be used inside settings.php
+
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         $section = $this->get_settings_section_name();
 
-        $modulesinfo = self::get_modules_info();
         $settings = null;
-        if ($hassiteconfig && isset($modulesinfo[$this->name]) && file_exists($this->full_path('settings.php'))) {
+        if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
             $settings = new admin_settingpage($section, $this->displayname,
                     'moodle/site:config', $this->is_enabled() === false);
             include($this->full_path('settings.php')); // this may also set $settings to null
@@ -3462,32 +3411,6 @@ class plugininfo_mod extends plugininfo_base {
 
         return '<p>'.get_string('uninstallextraconfirmmod', 'core_plugin', array('instances'=>$count, 'courses'=>$courses)).'</p>';
     }
-
-    /**
-     * Provides access to the records in {modules} table
-     *
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return array array of stdClasses
-     */
-    protected static function get_modules_info($disablecache=false) {
-        global $DB;
-
-        $cache = cache::make('core', 'plugininfo_mod');
-
-        $modulesinfo = $cache->get('modulesinfo');
-
-        if ($modulesinfo === false or $disablecache) {
-            try {
-                $modulesinfo = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
-            } catch (dml_exception $e) {
-                // before install
-                $modulesinfo = array();
-            }
-            $cache->set('modulesinfo', $modulesinfo);
-        }
-
-        return $modulesinfo;
-    }
 }
 
 
@@ -3529,6 +3452,11 @@ class plugininfo_qtype extends plugininfo_base {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // may be used in settings.php
         $qtype = $this; // also can be used inside settings.php
+
+        if (!$this->rootdir) {
+            // Most probably somebody deleted dir without proper uninstall.
+            return;
+        }
         $section = $this->get_settings_section_name();
 
         $settings = null;
@@ -3550,18 +3478,20 @@ class plugininfo_qtype extends plugininfo_base {
  * Class for authentication plugins
  */
 class plugininfo_auth extends plugininfo_base {
-
-    public function is_enabled() {
+    /**
+     * 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() {
         global $CFG;
 
-        if (in_array($this->name, array('nologin', 'manual'))) {
-            // these two are always enabled and can't be disabled
-            return null;
+        // These two are always enabled and can't be disabled.
+        $enabled = array('nologin'=>'nologin', 'manual'=>'manual');
+        foreach (explode(',', $CFG->auth) as $auth) {
+            $enabled[$auth] = $auth;
         }
 
-        $enabled = array_flip(explode(',', $CFG->auth));
-
-        return isset($enabled[$this->name]);
+        return $enabled;
     }
 
     public function get_settings_section_name() {
@@ -3572,6 +3502,12 @@ class plugininfo_auth extends plugininfo_base {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // may be used in settings.php
         $auth = $this; // also to be used inside settings.php
+
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         $section = $this->get_settings_section_name();
 
         $settings = null;
@@ -3598,18 +3534,19 @@ class plugininfo_auth extends plugininfo_base {
  * Class for enrolment plugins
  */
 class plugininfo_enrol extends plugininfo_base {
-
-    public function is_enabled() {
+    /**
+     * 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() {
         global $CFG;
 
-        // We do not actually need whole enrolment classes here so we do not call
-        // {@link enrol_get_plugins()}. Note that this may produce slightly different
-        // results, for example if the enrolment plugin does not contain lib.php
-        // but it is listed in $CFG->enrol_plugins_enabled
-
-        $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
+        $enabled = array();
+        foreach (explode(',', $CFG->enrol_plugins_enabled) as $enrol) {
+            $enabled[$enrol] = $enrol;
+        }
 
-        return isset($enabled[$this->name]);
+        return $enabled;
     }
 
     public function get_settings_section_name() {
@@ -3623,6 +3560,11 @@ class plugininfo_enrol extends plugininfo_base {
     public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
 
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) {
             return;
         }
@@ -3681,6 +3623,14 @@ class plugininfo_enrol extends plugininfo_base {
  * Class for messaging processors
  */
 class plugininfo_message 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() {
+        global $DB;
+        return $DB->get_records_menu('message_processors', array('enabled'=>1), 'name ASC', 'name, name AS val');
+    }
 
     public function get_settings_section_name() {
         return 'messagesetting' . $this->name;
@@ -3689,6 +3639,12 @@ class plugininfo_message extends plugininfo_base {
     public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // may be used in settings.php
+
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         if (!$hassiteconfig) {
             return;
         }
@@ -3709,18 +3665,6 @@ class plugininfo_message extends plugininfo_base {
         }
     }
 
-    /**
-     * @see plugintype_interface::is_enabled()
-     */
-    public function is_enabled() {
-        $processors = get_message_processors();
-        if (isset($processors[$this->name])) {
-            return $processors[$this->name]->configured && $processors[$this->name]->enabled;
-        } else {
-            return parent::is_enabled();
-        }
-    }
-
     public function is_uninstall_allowed() {
         $processors = get_message_processors();
         if (isset($processors[$this->name])) {
@@ -3744,12 +3688,13 @@ class plugininfo_message extends plugininfo_base {
  * Class for repositories
  */
 class plugininfo_repository extends plugininfo_base {
-
-    public function is_enabled() {
-
-        $enabled = self::get_enabled_repositories();
-
-        return isset($enabled[$this->name]);
+    /**
+     * 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() {
+        global $DB;
+        return $DB->get_records_menu('repository', array('visible'=>1), 'type ASC', 'type, type AS val');
     }
 
     public function get_settings_section_name() {
@@ -3757,6 +3702,11 @@ class plugininfo_repository extends plugininfo_base {
     }
 
     public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         if ($hassiteconfig && $this->is_enabled()) {
             // completely no access to repository setting when it is not enabled
             $sectionname = $this->get_settings_section_name();
@@ -3767,27 +3717,6 @@ class plugininfo_repository extends plugininfo_base {
             $adminroot->add($parentnodename, $settings);
         }
     }
-
-    /**
-     * Provides access to the records in {repository} table
-     *
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return array array of stdClasses
-     */
-    protected static function get_enabled_repositories($disablecache=false) {
-        global $DB;
-
-        $cache = cache::make('core', 'plugininfo_repository');
-
-        $enabled = $cache->get('enabled');
-
-        if ($enabled === false or $disablecache) {
-            $enabled = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
-            $cache->set('enabled', $enabled);
-        }
-
-        return $enabled;
-    }
 }
 
 
@@ -3795,44 +3724,17 @@ class plugininfo_repository extends plugininfo_base {
  * Class for portfolios
  */
 class plugininfo_portfolio extends plugininfo_base {
-
-    public function is_enabled() {
-
-        $enabled = self::get_enabled_portfolios();
-
-        return isset($enabled[$this->name]);
-    }
-
     /**
-     * Returns list of enabled portfolio plugins
-     *
-     * Portfolio plugin is enabled if there is at least one record in the {portfolio_instance}
-     * table for it.
-     *
-     * @param bool $disablecache do not attempt to obtain data from the cache
-     * @return array array of stdClasses with properties plugin and visible indexed by plugin
+     * Finds all enabled plugins, the result may include missing plugins.
+     * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown
      */
-    protected static function get_enabled_portfolios($disablecache=false) {
+    public static function get_enabled_plugins() {
         global $DB;
 
-        $cache = cache::make('core', 'plugininfo_portfolio');
-
-        $enabled = $cache->get('enabled');
-
-        if ($enabled === false or $disablecache) {
-            $enabled = array();
-            $instances = $DB->get_recordset('portfolio_instance', null, '', 'plugin,visible');
-            foreach ($instances as $instance) {
-                if (isset($enabled[$instance->plugin])) {
-                    if ($instance->visible) {
-                        $enabled[$instance->plugin]->visible = $instance->visible;
-                    }
-                } else {
-                    $enabled[$instance->plugin] = $instance;
-                }
-            }
-            $instances->close();
-            $cache->set('enabled', $enabled);
+        $enabled = array();
+        $rs = $DB->get_recordset('portfolio_instance', array('visible'=>1), 'plugin ASC', 'plugin');
+        foreach ($rs as $repository) {
+            $enabled[$repository->plugin] = $repository->plugin;
         }
 
         return $enabled;
@@ -3911,6 +3813,24 @@ class plugininfo_local extends plugininfo_base {
  * Class for HTML editors
  */
 class plugininfo_editor 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() {
+        global $CFG;
+
+        if (empty($CFG->texteditors)) {
+            return array('tinymce'=>'tinymce', 'textarea'=>'textarea');
+        }
+
+        $enabled = array();
+        foreach (explode(',', $CFG->texteditors) as $editor) {
+            $enabled[$editor] = $editor;
+        }
+
+        return $enabled;
+    }
 
     public function get_settings_section_name() {
         return 'editorsettings' . $this->name;
@@ -3920,6 +3840,12 @@ class plugininfo_editor extends plugininfo_base {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // may be used in settings.php
         $editor = $this; // also can be used inside settings.php
+
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         $section = $this->get_settings_section_name();
 
         $settings = null;
@@ -3943,27 +3869,6 @@ class plugininfo_editor extends plugininfo_base {
             return true;
         }
     }
-
-    /**
-     * Returns the information about plugin availability
-     *
-     * True means that the plugin is enabled. False means that the plugin is
-     * disabled. Null means that the information is not available, or the
-     * plugin does not support configurable availability or the availability
-     * can not be changed.
-     *
-     * @return null|bool
-     */
-    public function is_enabled() {
-        global $CFG;
-        if (empty($CFG->texteditors)) {
-            $CFG->texteditors = 'tinymce,textarea';
-        }
-        if (in_array($this->name, explode(',', $CFG->texteditors))) {
-            return true;
-        }
-        return false;
-    }
 }
 
 /**
@@ -3976,6 +3881,11 @@ class plugininfo_plagiarism extends plugininfo_base {
     }
 
     public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         // plagiarism plugin just redirect to settings.php in the plugins directory
         if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
             $section = $this->get_settings_section_name();
@@ -3995,6 +3905,24 @@ class plugininfo_plagiarism extends plugininfo_base {
  * Class for webservice protocols
  */
 class plugininfo_webservice 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() {
+        global $CFG;
+
+        if (empty($CFG->enablewebservices) or empty($CFG->webserviceprotocols)) {
+            return array();
+        }
+
+        $enabled = array();
+        foreach (explode(',', $CFG->webserviceprotocols) as $protocol) {
+            $enabled[$protocol] = $protocol;
+        }
+
+        return $enabled;
+    }
 
     public function get_settings_section_name() {
         return 'webservicesetting' . $this->name;
@@ -4004,6 +3932,12 @@ class plugininfo_webservice extends plugininfo_base {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // may be used in settings.php
         $webservice = $this; // also can be used inside settings.php
+
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         $section = $this->get_settings_section_name();
 
         $settings = null;
@@ -4017,18 +3951,6 @@ class plugininfo_webservice extends plugininfo_base {
         }
     }
 
-    public function is_enabled() {
-        global $CFG;
-        if (empty($CFG->enablewebservices)) {
-            return false;
-        }
-        $active_webservices = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
-        if (in_array($this->name, $active_webservices)) {
-            return true;
-        }
-        return false;
-    }
-
     public function is_uninstall_allowed() {
         return false;
     }
@@ -4038,6 +3960,36 @@ class plugininfo_webservice extends plugininfo_base {
  * Class for course formats
  */
 class plugininfo_format 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() {
+        global $DB;
+
+        $plugins = plugin_manager::instance()->get_installed_plugins('format');
+        $installed = array();
+        foreach ($plugins as $plugin => $version) {
+            $installed[] = 'format_'.$plugin;
+        }
+
+        list($installed, $params) = $DB->get_in_or_equal($installed, SQL_PARAMS_NAMED);
+        $disabled = $DB->get_recordset_select('config_plugins', "plugin $installed AND name = 'disabled'", $params, 'plugin ASC');
+        foreach ($disabled as $conf) {
+            if (empty($conf->value)) {
+                continue;
+            }
+            list($type, $name) = explode('_', $conf->component, 2);
+            unset($plugins[$name]);
+        }
+
+        $enabled = array();
+        foreach ($plugins as $plugin => $version) {
+            $enabled[$plugin] = $plugin;
+        }
+
+        return $enabled;
+    }
 
     /**
      * Gathers and returns the information about all plugins of the given type
@@ -4066,6 +4018,12 @@ class plugininfo_format extends plugininfo_base {
     public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
         global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
         $ADMIN = $adminroot; // also may be used in settings.php
+
+        if (!$this->rootdir) {
+            // Plugin missing.
+            return;
+        }
+
         $section = $this->get_settings_section_name();
 
         $settings = null;
@@ -4079,10 +4037,6 @@ class plugininfo_format extends plugininfo_base {
         }
     }
 
-    public function is_enabled() {
-        return !get_config($this->component, 'disabled');
-    }
-
     public function is_uninstall_allowed() {
         if ($this->name !== get_config('moodlecourse', 'format') && $this->name !== 'site') {
             return true;
diff --git a/lib/tests/available_update_checker_test.php b/lib/tests/available_update_checker_test.php
new file mode 100644 (file)
index 0000000..b1d9f27
--- /dev/null
@@ -0,0 +1,290 @@
+<?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 checker.
+ *
+ * @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');
+require_once(__DIR__.'/available_update_deployer_test.php');
+
+
+/**
+ * Tests of the basic API of the available update checker.
+ */
+class core_available_update_checker_testcase extends advanced_testcase {
+
+    public function test_core_available_update() {
+        $provider = testable_available_update_checker::instance();
+        $this->assertInstanceOf('available_update_checker', $provider);
+
+        $provider->fake_current_environment(2012060102.00, '2.3.2 (Build: 20121012)', '2.3', array());
+        $updates = $provider->get_update_info('core');
+        $this->assertCount(2, $updates);
+
+        $provider->fake_current_environment(2012060103.00, '2.3.3 (Build: 20121212)', '2.3', array());
+        $updates = $provider->get_update_info('core');
+        $this->assertCount(1, $updates);
+
+        $provider->fake_current_environment(2012060103.00, '2.3.3 (Build: 20121212)', '2.3', array());
+        $updates = $provider->get_update_info('core', array('minmaturity' => MATURITY_STABLE));
+        $this->assertNull($updates);
+    }
+
+    /**
+     * If there are no fetched data yet, the first cron should fetch them.
+     */
+    public function test_cron_initial_fetch() {
+        $provider = testable_available_update_checker::instance();
+        $provider->fakerecentfetch = null;
+        $provider->fakecurrenttimestamp = -1;
+        $this->setExpectedException('testable_available_update_checker_cron_executed');
+        $provider->cron();
+    }
+
+    /**
+     * If there is a fresh fetch available, no cron execution is expected.
+     */
+    public function test_cron_has_fresh_fetch() {
+        $provider = testable_available_update_checker::instance();
+        $provider->fakerecentfetch = time() - 23 * HOURSECS; // Fetched 23 hours ago.
+        $provider->fakecurrenttimestamp = -1;
+        $provider->cron();
+        $this->assertTrue(true); // We should get here with no exception thrown.
+    }
+
+    /**
+     * If there is an outdated fetch, the cron execution is expected.
+     */
+    public function test_cron_has_outdated_fetch() {
+        $provider = testable_available_update_checker::instance();
+        $provider->fakerecentfetch = time() - 49 * HOURSECS; // Fetched 49 hours ago.
+        $provider->fakecurrenttimestamp = -1;
+        $this->setExpectedException('testable_available_update_checker_cron_executed');
+        $provider->cron();
+    }
+
+    /**
+     * The first cron after 01:42 AM today should fetch the data.
+     *
+     * @see testable_available_update_checker::cron_execution_offset()
+     */
+    public function test_cron_offset_execution_not_yet() {
+        $provider = testable_available_update_checker::instance();
+        $provider->fakecurrenttimestamp = mktime(1, 40, 02); // 01:40:02 AM today
+        $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS;
+        $provider->cron();
+        $this->assertTrue(true); // We should get here with no exception thrown.
+    }
+
+    /**
+     * The first cron after 01:42 AM today should fetch the data and then
+     * it is supposed to wait next 24 hours.
+     *
+     * @see testable_available_update_checker::cron_execution_offset()
+     */
+    public function test_cron_offset_execution() {
+        $provider = testable_available_update_checker::instance();
+
+        // The cron at 01:45 should fetch the data.
+        $provider->fakecurrenttimestamp = mktime(1, 45, 02); // 01:45:02 AM today
+        $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS - 1;
+        $executed = false;
+        try {
+            $provider->cron();
+        } catch (testable_available_update_checker_cron_executed $e) {
+            $executed = true;
+        }
+        $this->assertTrue($executed, 'Cron should be executed at 01:45:02 but it was not.');
+
+        // Another cron at 06:45 should still consider data as fresh enough.
+        $provider->fakerecentfetch = $provider->fakecurrenttimestamp;
+        $provider->fakecurrenttimestamp = mktime(6, 45, 03); // 06:45:03 AM
+        $executed = false;
+        try {
+            $provider->cron();
+        } catch (testable_available_update_checker_cron_executed $e) {
+            $executed = true;
+        }
+        $this->assertFalse($executed, 'Cron should not be executed at 06:45:03 but it was.');
+
+        // The next scheduled execution should happen the next day.
+        $provider->fakecurrenttimestamp = $provider->fakerecentfetch + 24 * HOURSECS + 1;
+        $executed = false;
+        try {
+            $provider->cron();
+        } catch (testable_available_update_checker_cron_executed $e) {
+            $executed = true;
+        }
+        $this->assertTrue($executed, 'Cron should be executed the next night but it was not.');
+    }
+
+    public function test_compare_responses_both_empty() {
+        $provider = testable_available_update_checker::instance();
+        $old = array();
+        $new = array();
+        $cmp = $provider->compare_responses($old, $new);
+        $this->assertInternalType('array', $cmp);
+        $this->assertEmpty($cmp);
+    }
+
+    public function test_compare_responses_old_empty() {
+        $provider = testable_available_update_checker::instance();
+        $old = array();
+        $new = array(
+            'updates' => array(
+                'core' => array(
+                    array(
+                        'version' => 2012060103
+                    )
+                )
+            )
+        );
+        $cmp = $provider->compare_responses($old, $new);
+        $this->assertInternalType('array', $cmp);
+        $this->assertNotEmpty($cmp);
+        $this->assertTrue(isset($cmp['core'][0]['version']));
+        $this->assertEquals(2012060103, $cmp['core'][0]['version']);
+    }
+
+    public function test_compare_responses_no_change() {
+        $provider = testable_available_update_checker::instance();
+        $old = $new = array(
+            'updates' => array(
+                'core' => array(
+                    array(
+                        'version' => 2012060104
+                    ),
+                    array(
+                        'version' => 2012120100
+                    )
+                ),
+                'mod_foo' => array(
+                    array(
+                        'version' => 2011010101
+                    )
+                )
+            )
+        );
+        $cmp = $provider->compare_responses($old, $new);
+        $this->assertInternalType('array', $cmp);
+        $this->assertEmpty($cmp);
+    }
+
+    public function test_compare_responses_new_and_missing_update() {
+        $provider = testable_available_update_checker::instance();
+        $old = array(
+            'updates' => array(
+                'core' => array(
+                    array(
+                        'version' => 2012060104
+                    )
+                ),
+                'mod_foo' => array(
+                    array(
+                        'version' => 2011010101
+                    )
+                )
+            )
+        );
+        $new = array(
+            'updates' => array(
+                'core' => array(
+                    array(
+                        'version' => 2012060104
+                    ),
+                    array(
+                        'version' => 2012120100
+                    )
+                )
+            )
+        );
+        $cmp = $provider->compare_responses($old, $new);
+        $this->assertInternalType('array', $cmp);
+        $this->assertNotEmpty($cmp);
+        $this->assertCount(1, $cmp);
+        $this->assertCount(1, $cmp['core']);
+        $this->assertEquals(2012120100, $cmp['core'][0]['version']);
+    }
+
+    public function test_compare_responses_modified_update() {
+        $provider = testable_available_update_checker::instance();
+        $old = array(
+            'updates' => array(
+                'mod_foo' => array(
+                    array(
+                        'version' => 2011010101
+                    )
+                )
+            )
+        );
+        $new = array(
+            'updates' => array(
+                'mod_foo' => array(
+                    array(
+                        'version' => 2011010102
+                    )
+                )
+            )
+        );
+        $cmp = $provider->compare_responses($old, $new);
+        $this->assertInternalType('array', $cmp);
+        $this->assertNotEmpty($cmp);
+        $this->assertCount(1, $cmp);
+        $this->assertCount(1, $cmp['mod_foo']);
+        $this->assertEquals(2011010102, $cmp['mod_foo'][0]['version']);
+    }
+
+    public function test_compare_responses_invalid_format() {
+        $provider = testable_available_update_checker::instance();
+        $broken = array(
+            'status' => 'ERROR' // No 'updates' key here.
+        );
+        $this->setExpectedException('available_update_checker_exception');
+        $cmp = $provider->compare_responses($broken, $broken);
+    }
+
+    public function test_is_same_release_explicit() {
+        $provider = testable_available_update_checker::instance();
+        $this->assertTrue($provider->is_same_release('2.3dev (Build: 20120323)', '2.3dev (Build: 20120323)'));
+        $this->assertTrue($provider->is_same_release('2.3dev (Build: 20120323)', '2.3dev (Build: 20120330)'));
+        $this->assertFalse($provider->is_same_release('2.3dev (Build: 20120529)', '2.3 (Build: 20120601)'));
+        $this->assertFalse($provider->is_same_release('2.3dev', '2.3 dev'));
+        $this->assertFalse($provider->is_same_release('2.3.1', '2.3'));
+        $this->assertFalse($provider->is_same_release('2.3.1', '2.3.2'));
+        $this->assertTrue($provider->is_same_release('2.3.2+', '2.3.2')); // Yes, really!
+        $this->assertTrue($provider->is_same_release('2.3.2 (Build: 123456)', '2.3.2+ (Build: 123457)'));
+        $this->assertFalse($provider->is_same_release('3.0 Community Edition', '3.0 Enterprise Edition'));
+        $this->assertTrue($provider->is_same_release('3.0 Community Edition', '3.0 Community Edition (Build: 20290101)'));
+    }
+
+    public function test_is_same_release_implicit() {
+        $provider = testable_available_update_checker::instance();
+        $provider->fake_current_environment(2012060102.00, '2.3.2 (Build: 20121012)', '2.3', array());
+        $this->assertTrue($provider->is_same_release('2.3.2'));
+        $this->assertTrue($provider->is_same_release('2.3.2+'));
+        $this->assertTrue($provider->is_same_release('2.3.2+ (Build: 20121013)'));
+        $this->assertFalse($provider->is_same_release('2.4dev (Build: 20121012)'));
+    }
+}
diff --git a/lib/tests/available_update_deployer_test.php b/lib/tests/available_update_deployer_test.php
new file mode 100644 (file)
index 0000000..23e75c5
--- /dev/null
@@ -0,0 +1,209 @@
+<?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 {
+}
diff --git a/lib/tests/fixtures/mockplugins/mod/bar/version.php b/lib/tests/fixtures/mockplugins/mod/bar/version.php
deleted file mode 100644 (file)
index 47613f6..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?php
-
-$module->version = 2012030500;
-$module->requires = 2012010100;
diff --git a/lib/tests/fixtures/mockplugins/mod/baz/meg/one/version.php b/lib/tests/fixtures/mockplugins/mod/baz/meg/one/version.php
deleted file mode 100644 (file)
index b099a77..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-
-$plugin->version = 2013041103;
-$plugin->requires = 2013010100;
-$plugin->component = 'bazmeg_one';
diff --git a/lib/tests/fixtures/mockplugins/mod/baz/version.php b/lib/tests/fixtures/mockplugins/mod/baz/version.php
deleted file mode 100644 (file)
index 47613f6..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?php
-
-$module->version = 2012030500;
-$module->requires = 2012010100;
diff --git a/lib/tests/fixtures/mockplugins/mod/foo/lish/frog/version.php b/lib/tests/fixtures/mockplugins/mod/foo/lish/frog/version.php
deleted file mode 100644 (file)
index b5dd0ee..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-
-$plugin->version = 2013041103;
-$plugin->requires = 2013010100;
-$plugin->component = 'foolish_frog';
-$plugin->dependencies = array('mod_foo' => 2012030500);
diff --git a/lib/tests/fixtures/mockplugins/mod/foo/lish/hippo/version.php b/lib/tests/fixtures/mockplugins/mod/foo/lish/hippo/version.php
deleted file mode 100644 (file)
index f64b02d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-
-$plugin->version = 2013041103;
-$plugin->requires = 2012010100;
-$plugin->component = 'foolish_hippo';
-$plugin->dependencies = array('foolish_frog' => ANY_VERSION);
diff --git a/lib/tests/fixtures/mockplugins/mod/foo/version.php b/lib/tests/fixtures/mockplugins/mod/foo/version.php
deleted file mode 100644 (file)
index a12a76a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-$module->version = 2012030500;
-$module->requires = 2012010100;
-$module->component = 'mod_foo';
-$module->dependencies = array(
-    'mod_bar' => 2012030500,
-    'mod_missing' => ANY_VERSION,
-    'foolish_frog' => ANY_VERSION,
-);
diff --git a/lib/tests/fixtures/mockplugins/mod/new/version.php b/lib/tests/fixtures/mockplugins/mod/new/version.php
deleted file mode 100644 (file)
index 976ddf1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-
-$module->version = 2013041900;
-$module->requires = 2012010100;
-$module->component = 'mod_new';
diff --git a/lib/tests/fixtures/mockplugins/mod/qux/cat/one/version.php b/lib/tests/fixtures/mockplugins/mod/qux/cat/one/version.php
deleted file mode 100644 (file)
index 4c9e978..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-
-$plugin->version = 2013041103;
-$plugin->requires = 2013010100;
-$plugin->component = 'quxcat_one';
-$plugin->dependencies = array('bazmeg_one' => 2013010100);
diff --git a/lib/tests/fixtures/mockplugins/mod/qux/version.php b/lib/tests/fixtures/mockplugins/mod/qux/version.php
deleted file mode 100644 (file)
index 9c1c5f0..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-
-$plugin->version = 2013041103;
-$plugin->requires = 2013010100;
-$plugin->component = 'mod_qux';
diff --git a/lib/tests/plugin_manager_test.php b/lib/tests/plugin_manager_test.php
new file mode 100644 (file)
index 0000000..4a8f378
--- /dev/null
@@ -0,0 +1,232 @@
+<?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 plugin manager class.
+ *
+ * @package   core
+ * @category  phpunit
+ * @copyright 2013 Petr Skoda {@link http://skodak.org}
+ * @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');
+
+
+/**
+ * Tests of the basic API of the plugin manager.
+ */
+class core_plugin_manager_testcase extends advanced_testcase {
+
+    public function test_instance() {
+        $pluginman = plugin_manager::instance();
+        $this->assertInstanceOf('plugin_manager', $pluginman);
+        $pluginman2 = plugin_manager::instance();
+        $this->assertSame($pluginman, $pluginman2);
+    }
+
+    public function test_reset_caches() {
+        // Make sure there are no warnings or errors.
+        plugin_manager::reset_caches();
+    }
+
+    public function test_get_plugin_types() {
+        // Make sure there are no warnings or errors.
+        $types = plugin_manager::instance()->get_plugin_types();
+        $this->assertInternalType('array', $types);
+        foreach ($types as $type => $fulldir) {
+            $this->assertFileExists($fulldir);
+        }
+    }
+
+    public function test_get_installed_plugins() {
+        $types = plugin_manager::instance()->get_plugin_types();
+        foreach ($types as $type => $fulldir) {
+            $installed = plugin_manager::instance()->get_installed_plugins($type);
+            foreach ($installed as $plugin => $version) {
+                $this->assertRegExp('/^[a-z]+[a-z0-9_]*$/', $plugin);
+                $this->assertTrue(is_numeric($version), 'All plugins should have a version, plugin '.$type.'_'.$plugin.' does not have version info.');
+            }
+        }
+    }
+
+    public function test_get_enabled_plugins() {
+        $types = plugin_manager::instance()->get_plugin_types();
+        foreach ($types as $type => $fulldir) {
+            $enabled = plugin_manager::instance()->get_enabled_plugins($type);
+            if (is_array($enabled)) {
+                foreach ($enabled as $key => $val) {
+                    $this->assertRegExp('/^[a-z]+[a-z0-9_]*$/', $key);
+                    $this->assertSame($key, $val);
+                }
+            } else {
+                $this->assertNull($enabled);
+            }
+        }
+    }
+
+    public function test_get_present_plugins() {
+        $types = plugin_manager::instance()->get_plugin_types();
+        foreach ($types as $type => $fulldir) {
+            $present = plugin_manager::instance()->get_present_plugins($type);
+            if (is_array($present)) {
+                foreach ($present as $plugin => $version) {
+                    $this->assertRegExp('/^[a-z]+[a-z0-9_]*$/', $plugin, 'All plugins are supposed to have version.php file.');
+                    $this->assertInternalType('object', $version);
+                    $this->assertTrue(is_numeric($version->version), 'All plugins should have a version, plugin '.$type.'_'.$plugin.' does not have version info.');
+                }
+            } else {
+                // No plugins of this type exist.
+                $this->assertNull($present);
+            }
+        }
+    }
+
+    public function test_get_plugins() {
+        $plugininfos = plugin_manager::instance()->get_plugins();
+        foreach ($plugininfos as $type => $infos) {
+            foreach ($infos as $name => $info) {
+                $this->assertInstanceOf('plugininfo_base', $info);
+            }
+        }
+    }
+
+    public function test_get_plugins_of_type() {
+        $plugininfos = plugin_manager::instance()->get_plugins();
+        foreach ($plugininfos as $type => $infos) {
+            $this->assertSame($infos, plugin_manager::instance()->get_plugins_of_type($type));
+        }
+    }
+
+    public function test_get_subplugins_of_plugin() {
+        global $CFG;
+
+        // Any standard plugin with subplugins is suitable.
+        $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.');
+
+        $subplugins = plugin_manager::instance()->get_subplugins_of_plugin('editor_tinymce');
+        foreach ($subplugins as $component => $info) {
+            $this->assertInstanceOf('plugininfo_base', $info);
+        }
+    }
+
+    public function test_get_subplugins() {
+        // Tested already indirectly from test_get_subplugins_of_plugin().
+        $subplugins = plugin_manager::instance()->get_subplugins();
+        $this->assertInternalType('array', $subplugins);
+    }
+
+    public function test_get_parent_of_subplugin() {
+        global $CFG;
+
+        // Any standard plugin with subplugins is suitable.
+        $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.');
+
+        $parent = plugin_manager::instance()->get_parent_of_subplugin('tinymce');
+        $this->assertSame('editor_tinymce', $parent);
+    }
+
+    public function test_plugin_name() {
+        global $CFG;
+
+        // Any standard plugin is suitable.
+        $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.');
+
+        $name = plugin_manager::instance()->plugin_name('editor_tinymce');
+        $this->assertSame(get_string('pluginname', 'editor_tinymce'), $name);
+    }
+
+    public function test_plugintype_name() {
+        $name = plugin_manager::instance()->plugintype_name('editor');
+        $this->assertSame(get_string('type_editor', 'core_plugin'), $name);
+    }
+
+    public function test_plugintype_name_plural() {
+        $name = plugin_manager::instance()->plugintype_name_plural('editor');
+        $this->assertSame(get_string('type_editor_plural', 'core_plugin'), $name);
+    }
+
+    public function test_get_plugin_info() {
+        global $CFG;
+
+        // Any standard plugin is suitable.
+        $this->assertFileExists("$CFG->dirroot/lib/editor/tinymce", 'TinyMCE is not present.');
+
+        $info = plugin_manager::instance()->get_plugin_info('editor_tinymce');
+        $this->assertInstanceOf('plugininfo_editor', $info);
+    }
+
+    public function test_can_uninstall_plugin() {
+        global $CFG;
+
+        // Any standard plugin that is required by some other standard plugin is ok.
+        $this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/assignmentupgrade", 'assign upgrade tool is not present');
+        $this->assertFileExists("$CFG->dirroot/mod/assign", 'assign module is not present');
+
+        $this->assertFalse(plugin_manager::instance()->can_uninstall_plugin('mod_assign'));
+        $this->assertTrue(plugin_manager::instance()->can_uninstall_plugin('tool_assignmentupgrade'));
+    }
+
+    public function test_plugin_states() {
+        global $CFG;
+        $this->resetAfterTest();
+
+        // Any standard plugin that is ok.
+        $this->assertFileExists("$CFG->dirroot/mod/assign", 'assign module is not present');
+        $this->assertFileExists("$CFG->dirroot/mod/forum", 'forum module is not present');
+        $this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/phpunit", 'phpunit tool is not present');
+        $this->assertFileNotExists("$CFG->dirroot/mod/xxxxxxx");
+        $this->assertFileNotExists("$CFG->dirroot/enrol/autorize");
+
+        // Ready for upgrade.
+        $assignversion = get_config('mod_assign', 'version');
+        set_config('version', $assignversion - 1, 'mod_assign');
+        // Downgrade problem.
+        $forumversion = get_config('mod_forum', 'version');
+        set_config('version', $forumversion + 1, 'mod_forum');
+        // Not installed yet.
+        unset_config('version', 'tool_phpunit');
+        // Missing already installed.
+        set_config('version', 2013091300, 'mod_xxxxxxx');
+        // Deleted present.
+        set_config('version', 2013091300, 'enrol_authorize');
+
+        plugin_manager::reset_caches();
+
+        $plugininfos = plugin_manager::instance()->get_plugins();
+        foreach ($plugininfos as $type => $infos) {
+            foreach ($infos as $name => $info) {
+                /** @var plugininfo_base $info */
+                if ($info->component === 'mod_assign') {
+                    $this->assertSame(plugin_manager::PLUGIN_STATUS_UPGRADE, $info->get_status(), 'Invalid '.$info->component.' state');
+                } else if ($info->component === 'mod_forum') {
+                    $this->assertSame(plugin_manager::PLUGIN_STATUS_DOWNGRADE, $info->get_status(), 'Invalid '.$info->component.' state');
+                } else if ($info->component === 'tool_phpunit') {
+                    $this->assertSame(plugin_manager::PLUGIN_STATUS_NEW, $info->get_status(), 'Invalid '.$info->component.' state');
+                } else if ($info->component === 'mod_xxxxxxx') {
+                    $this->assertSame(plugin_manager::PLUGIN_STATUS_MISSING, $info->get_status(), 'Invalid '.$info->component.' state');
+                } else if ($info->component === 'enrol_authorize') {
+                    $this->assertSame(plugin_manager::PLUGIN_STATUS_DELETE, $info->get_status(), 'Invalid '.$info->component.' state');
+                } else {
+                    $this->assertSame(plugin_manager::PLUGIN_STATUS_UPTODATE, $info->get_status(), 'Invalid '.$info->component.' state');
+                }
+            }
+        }
+    }
+}
diff --git a/lib/tests/pluginlib_test.php b/lib/tests/pluginlib_test.php
deleted file mode 100644 (file)
index d6f47d1..0000000
+++ /dev/null
@@ -1,950 +0,0 @@
-<?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 lib/pluginlib.php library
- *
- * Execute the core_plugin group to run all tests in this file:
- *
- *  $ phpunit --group core_plugin
- *
- * @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');
-
-
-/**
- * Tests of the basic API of the plugin manager.
- *
- * @group core_plugin
- */
-class core_plugin_manager_testcase extends advanced_testcase {
-
-    public function setUp() {
-        $this->resetAfterTest();
-    }
-
-    public function test_plugin_manager_instance() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertInstanceOf('testable_plugin_manager', $pluginman);
-    }
-
-    public function test_get_plugins_of_type() {
-        $pluginman = testable_plugin_manager::instance();
-        $mods = $pluginman->get_plugins_of_type('mod');
-        $this->assertInternalType('array', $mods);
-        $this->assertCount(5, $mods);
-        $this->assertInstanceOf('testable_plugininfo_mod', $mods['foo']);
-        $this->assertInstanceOf('testable_plugininfo_mod', $mods['bar']);
-        $this->assertInstanceOf('testable_plugininfo_mod', $mods['baz']);
-        $this->assertInstanceOf('testable_plugininfo_mod', $mods['qux']);
-        $this->assertInstanceOf('testable_plugininfo_mod', $mods['new']);
-        $foolishes = $pluginman->get_plugins_of_type('foolish');
-        $this->assertCount(2, $foolishes);
-        $this->assertInstanceOf('testable_pluginfo_foolish', $foolishes['frog']);
-        $this->assertInstanceOf('testable_pluginfo_foolish', $foolishes['hippo']);
-        $bazmegs = $pluginman->get_plugins_of_type('bazmeg');
-        $this->assertCount(1, $bazmegs);
-        $this->assertInstanceOf('testable_pluginfo_bazmeg', $bazmegs['one']);
-        $quxcats = $pluginman->get_plugins_of_type('quxcat');
-        $this->assertCount(1, $quxcats);
-        $this->assertInstanceOf('testable_pluginfo_quxcat', $quxcats['one']);
-        $unknown = $pluginman->get_plugins_of_type('muhehe');
-        $this->assertSame(array(), $unknown);
-    }
-
-    public function test_get_plugins() {
-        $pluginman = testable_plugin_manager::instance();
-        $plugins = $pluginman->get_plugins();
-        $this->assertInternalType('array', $plugins);
-        $this->assertTrue(isset($plugins['mod']['foo']));
-        $this->assertTrue(isset($plugins['mod']['bar']));
-        $this->assertTrue(isset($plugins['mod']['baz']));
-        $this->assertTrue(isset($plugins['mod']['new']));
-        $this->assertTrue(isset($plugins['foolish']['frog']));
-        $this->assertTrue(isset($plugins['foolish']['hippo']));
-        $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['foo']);
-        $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['bar']);
-        $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['baz']);
-        $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['new']);
-        $this->assertInstanceOf('testable_pluginfo_foolish', $plugins['foolish']['frog']);
-        $this->assertInstanceOf('testable_pluginfo_foolish', $plugins['foolish']['hippo']);
-        $this->assertInstanceOf('testable_pluginfo_bazmeg', $plugins['bazmeg']['one']);
-        $this->assertInstanceOf('testable_pluginfo_quxcat', $plugins['quxcat']['one']);
-    }
-
-    public function test_get_subplugins_of_plugin() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertSame(array(), $pluginman->get_subplugins_of_plugin('mod_missing'));
-        $this->assertSame(array(), $pluginman->get_subplugins_of_plugin('mod_bar'));
-        $foosubs = $pluginman->get_subplugins_of_plugin('mod_foo');
-        $this->assertInternalType('array', $foosubs);
-        $this->assertCount(2, $foosubs);
-        $this->assertInstanceOf('testable_pluginfo_foolish', $foosubs['foolish_frog']);
-        $this->assertInstanceOf('testable_pluginfo_foolish', $foosubs['foolish_hippo']);
-        $bazsubs = $pluginman->get_subplugins_of_plugin('mod_baz');
-        $this->assertInternalType('array', $bazsubs);
-        $this->assertCount(1, $bazsubs);
-        $this->assertInstanceOf('testable_pluginfo_bazmeg', $bazsubs['bazmeg_one']);
-        $quxsubs = $pluginman->get_subplugins_of_plugin('mod_qux');
-        $this->assertInternalType('array', $quxsubs);
-        $this->assertCount(1, $quxsubs);
-        $this->assertInstanceOf('testable_pluginfo_quxcat', $quxsubs['quxcat_one']);
-    }
-
-    public function test_get_subplugins() {
-        $pluginman = testable_plugin_manager::instance();
-        $subplugins = $pluginman->get_subplugins();
-        $this->assertTrue(isset($subplugins['mod_foo']['foolish']));
-        $this->assertTrue(isset($subplugins['mod_baz']['bazmeg']));
-        $this->assertTrue(isset($subplugins['mod_qux']['quxcat']));
-    }
-
-    public function test_get_parent_of_subplugin() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertSame('mod_foo', $pluginman->get_parent_of_subplugin('foolish'));
-        $this->assertSame('mod_baz', $pluginman->get_parent_of_subplugin('bazmeg'));
-        $this->assertSame('mod_qux', $pluginman->get_parent_of_subplugin('quxcat'));
-        $this->assertFalse($pluginman->get_parent_of_subplugin('mod'));
-        $this->assertFalse($pluginman->get_parent_of_subplugin('unknown'));
-        $plugins = $pluginman->get_plugins();
-        $this->assertFalse($plugins['mod']['foo']->is_subplugin());
-        $this->assertFalse($plugins['mod']['foo']->get_parent_plugin());
-        $this->assertTrue($plugins['foolish']['frog']->is_subplugin());
-        $this->assertSame('mod_foo', $plugins['foolish']['frog']->get_parent_plugin());
-    }
-
-    public function test_plugin_name() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertSame('Foo', $pluginman->plugin_name('mod_foo'));
-        $this->assertSame('Bar', $pluginman->plugin_name('mod_bar'));
-        $this->assertSame('Frog', $pluginman->plugin_name('foolish_frog'));
-        $this->assertSame('Hippo', $pluginman->plugin_name('foolish_hippo'));
-        $this->assertSame('One', $pluginman->plugin_name('bazmeg_one'));
-        $this->assertSame('One', $pluginman->plugin_name('quxcat_one'));
-    }
-
-    public function test_get_plugin_info() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertInstanceOf('testable_plugininfo_mod', $pluginman->get_plugin_info('mod_foo'));
-        $this->assertInstanceOf('testable_pluginfo_foolish', $pluginman->get_plugin_info('foolish_frog'));
-    }
-
-    public function test_other_plugins_that_require() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertEquals(array('foolish_frog'), $pluginman->other_plugins_that_require('mod_foo'));
-        $this->assertCount(2, $pluginman->other_plugins_that_require('foolish_frog'));
-        $this->assertContains('foolish_hippo', $pluginman->other_plugins_that_require('foolish_frog'));
-        $this->assertContains('mod_foo', $pluginman->other_plugins_that_require('foolish_frog'));
-        $this->assertEquals(array(), $pluginman->other_plugins_that_require('foolish_hippo'));
-        $this->assertEquals(array('mod_foo'), $pluginman->other_plugins_that_require('mod_bar'));
-        $this->assertEquals(array('mod_foo'), $pluginman->other_plugins_that_require('mod_missing'));
-        $this->assertEquals(array('quxcat_one'), $pluginman->other_plugins_that_require('bazmeg_one'));
-    }
-
-    public function test_are_dependencies_satisfied() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertTrue($pluginman->are_dependencies_satisfied(array()));
-        $this->assertTrue($pluginman->are_dependencies_satisfied(array(
-            'mod_bar' => 2012030500,
-        )));
-        $this->assertTrue($pluginman->are_dependencies_satisfied(array(
-            'mod_bar' => ANY_VERSION,
-        )));
-        $this->assertFalse($pluginman->are_dependencies_satisfied(array(
-            'mod_bar' => 2099010000,
-        )));
-        $this->assertFalse($pluginman->are_dependencies_satisfied(array(
-            'mod_bar' => 2012030500,
-            'mod_missing' => ANY_VERSION,
-        )));
-    }
-
-    public function test_all_plugins_ok() {
-        $pluginman = testable_plugin_manager::instance();
-        $failedplugins = array();
-        $this->assertFalse($pluginman->all_plugins_ok(2013010100, $failedplugins));
-        $this->assertContains('mod_foo', $failedplugins); // Requires mod_missing.
-        $this->assertNotContains('mod_bar', $failedplugins);
-        $this->assertNotContains('foolish_frog', $failedplugins);
-        $this->assertNotContains('foolish_hippo', $failedplugins);
-
-        $failedplugins = array();
-        $this->assertFalse($pluginman->all_plugins_ok(2012010100, $failedplugins));
-        $this->assertContains('mod_foo', $failedplugins); // Requires mod_missing.
-        $this->assertNotContains('mod_bar', $failedplugins);
-        $this->assertContains('foolish_frog', $failedplugins); // Requires Moodle 2013010100.
-        $this->assertNotContains('foolish_hippo', $failedplugins);
-
-        $failedplugins = array();
-        $this->assertFalse($pluginman->all_plugins_ok(2011010100, $failedplugins));
-        $this->assertContains('mod_foo', $failedplugins); // Requires mod_missing and Moodle 2012010100.
-        $this->assertContains('mod_bar', $failedplugins); // Requires Moodle 2012010100.
-        $this->assertContains('foolish_frog', $failedplugins); // Requires Moodle 2013010100.
-        $this->assertContains('foolish_hippo', $failedplugins); // Requires Moodle 2012010100.
-    }
-
-    public function test_some_plugins_updatable() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertTrue($pluginman->some_plugins_updatable()); // We have available update for mod_foo.
-    }
-
-    public function test_is_standard() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertTrue($pluginman->get_plugin_info('mod_bar')->is_standard());
-        $this->assertFalse($pluginman->get_plugin_info('mod_foo')->is_standard());
-        $this->assertFalse($pluginman->get_plugin_info('foolish_frog')->is_standard());
-    }
-
-    public function test_get_status() {
-        $pluginman = testable_plugin_manager::instance();
-        $plugins = $pluginman->get_plugins();
-        $this->assertEquals(plugin_manager::PLUGIN_STATUS_UPGRADE, $plugins['mod']['foo']->get_status());
-        $this->assertEquals(plugin_manager::PLUGIN_STATUS_NEW, $plugins['mod']['new']->get_status());
-        $this->assertEquals(plugin_manager::PLUGIN_STATUS_NEW, $plugins['bazmeg']['one']->get_status());
-        $this->assertEquals(plugin_manager::PLUGIN_STATUS_UPTODATE, $plugins['quxcat']['one']->get_status());
-    }
-
-    public function test_available_update() {
-        $pluginman = testable_plugin_manager::instance();
-        $plugins = $pluginman->get_plugins();
-        $this->assertNull($plugins['mod']['bar']->available_updates());
-        $this->assertInternalType('array', $plugins['mod']['foo']->available_updates());
-        foreach ($plugins['mod']['foo']->available_updates() as $availableupdate) {
-            $this->assertInstanceOf('available_update_info', $availableupdate);
-        }
-    }
-
-    public function test_can_uninstall_plugin() {
-        $pluginman = testable_plugin_manager::instance();
-        $this->assertFalse($pluginman->can_uninstall_plugin('mod_missing'));
-        $this->assertTrue($pluginman->can_uninstall_plugin('mod_foo')); // Because mod_foo is required by foolish_frog only
-                                                                        // and foolish_frog is required by mod_foo and foolish_hippo only.
-        $this->assertFalse($pluginman->can_uninstall_plugin('mod_bar')); // Because mod_bar is required by mod_foo.
-        $this->assertFalse($pluginman->can_uninstall_plugin('mod_qux')); // Because even if no plugin (not even subplugins) declare
-                                                                         // dependency on it, but its subplugin can't be uninstalled.
-        $this->assertFalse($pluginman->can_uninstall_plugin('mod_baz')); // Because it's subplugin bazmeg_one is required by quxcat_one.
-        $this->assertFalse($pluginman->can_uninstall_plugin('mod_new')); // Because it is not installed.
-        $this->assertFalse($pluginman->can_uninstall_plugin('quxcat_one')); // Because of testable_pluginfo_quxcat::is_uninstall_allowed().
-        $this->assertFalse($pluginman->can_uninstall_plugin('foolish_frog')); // Because foolish_hippo requires it.
-    }
-
-    public function test_get_uninstall_url() {
-        $pluginman = testable_plugin_manager::instance();
-        foreach ($pluginman->get_plugins() as $plugintype => $plugininfos) {
-            foreach ($plugininfos as $plugininfo) {
-                $this->assertInstanceOf('moodle_url', $plugininfo->get_uninstall_url());
-            }
-        }
-    }
-}
-
-
-/**
- * Tests of the basic API of the available update checker.
- *
- * @group core_plugin
- */
-class core_available_update_checker_testcase extends advanced_testcase {
-
-    public function test_core_available_update() {
-        $provider = testable_available_update_checker::instance();
-        $this->assertInstanceOf('available_update_checker', $provider);
-
-        $provider->fake_current_environment(2012060102.00, '2.3.2 (Build: 20121012)', '2.3', array());
-        $updates = $provider->get_update_info('core');
-        $this->assertCount(2, $updates);
-
-        $provider->fake_current_environment(2012060103.00, '2.3.3 (Build: 20121212)', '2.3', array());
-        $updates = $provider->get_update_info('core');
-        $this->assertCount(1, $updates);
-
-        $provider->fake_current_environment(2012060103.00, '2.3.3 (Build: 20121212)', '2.3', array());
-        $updates = $provider->get_update_info('core', array('minmaturity' => MATURITY_STABLE));
-        $this->assertNull($updates);
-    }
-
-    /**
-     * If there are no fetched data yet, the first cron should fetch them.
-     */
-    public function test_cron_initial_fetch() {
-        $provider = testable_available_update_checker::instance();
-        $provider->fakerecentfetch = null;
-        $provider->fakecurrenttimestamp = -1;
-        $this->setExpectedException('testable_available_update_checker_cron_executed');
-        $provider->cron();
-    }
-
-    /**
-     * If there is a fresh fetch available, no cron execution is expected.
-     */
-    public function test_cron_has_fresh_fetch() {
-        $provider = testable_available_update_checker::instance();
-        $provider->fakerecentfetch = time() - 23 * HOURSECS; // Fetched 23 hours ago.
-        $provider->fakecurrenttimestamp = -1;
-        $provider->cron();
-        $this->assertTrue(true); // We should get here with no exception thrown.
-    }
-
-    /**
-     * If there is an outdated fetch, the cron execution is expected.
-     */
-    public function test_cron_has_outdated_fetch() {
-        $provider = testable_available_update_checker::instance();
-        $provider->fakerecentfetch = time() - 49 * HOURSECS; // Fetched 49 hours ago.
-        $provider->fakecurrenttimestamp = -1;
-        $this->setExpectedException('testable_available_update_checker_cron_executed');
-        $provider->cron();
-    }
-
-    /**
-     * The first cron after 01:42 AM today should fetch the data.
-     *
-     * @see testable_available_update_checker::cron_execution_offset()
-     */
-    public function test_cron_offset_execution_not_yet() {
-        $provider = testable_available_update_checker::instance();
-        $provider->fakecurrenttimestamp = mktime(1, 40, 02); // 01:40:02 AM today
-        $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS;
-        $provider->cron();
-        $this->assertTrue(true); // We should get here with no exception thrown.
-    }
-
-    /**
-     * The first cron after 01:42 AM today should fetch the data and then
-     * it is supposed to wait next 24 hours.
-     *
-     * @see testable_available_update_checker::cron_execution_offset()
-     */
-    public function test_cron_offset_execution() {
-        $provider = testable_available_update_checker::instance();
-
-        // The cron at 01:45 should fetch the data.
-        $provider->fakecurrenttimestamp = mktime(1, 45, 02); // 01:45:02 AM today
-        $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS - 1;
-        $executed = false;
-        try {
-            $provider->cron();
-        } catch (testable_available_update_checker_cron_executed $e) {
-            $executed = true;
-        }
-        $this->assertTrue($executed, 'Cron should be executed at 01:45:02 but it was not.');
-
-        // Another cron at 06:45 should still consider data as fresh enough.
-        $provider->fakerecentfetch = $provider->fakecurrenttimestamp;
-        $provider->fakecurrenttimestamp = mktime(6, 45, 03); // 06:45:03 AM
-        $executed = false;
-        try {
-            $provider->cron();
-        } catch (testable_available_update_checker_cron_executed $e) {
-            $executed = true;
-        }
-        $this->assertFalse($executed, 'Cron should not be executed at 06:45:03 but it was.');
-
-        // The next scheduled execution should happen the next day.
-        $provider->fakecurrenttimestamp = $provider->fakerecentfetch + 24 * HOURSECS + 1;
-        $executed = false;
-        try {
-            $provider->cron();
-        } catch (testable_available_update_checker_cron_executed $e) {
-            $executed = true;
-        }
-        $this->assertTrue($executed, 'Cron should be executed the next night but it was not.');
-    }
-
-    public function test_compare_responses_both_empty() {
-        $provider = testable_available_update_checker::instance();
-        $old = array();
-        $new = array();
-        $cmp = $provider->compare_responses($old, $new);
-        $this->assertInternalType('array', $cmp);
-        $this->assertEmpty($cmp);
-    }
-
-    public function test_compare_responses_old_empty() {
-        $provider = testable_available_update_checker::instance();
-        $old = array();
-        $new = array(
-            'updates' => array(
-                'core' => array(
-                    array(
-                        'version' => 2012060103
-                    )
-                )
-            )
-        );
-        $cmp = $provider->compare_responses($old, $new);
-        $this->assertInternalType('array', $cmp);
-        $this->assertNotEmpty($cmp);
-        $this->assertTrue(isset($cmp['core'][0]['version']));
-        $this->assertEquals(2012060103, $cmp['core'][0]['version']);
-    }
-
-    public function test_compare_responses_no_change() {
-        $provider = testable_available_update_checker::instance();
-        $old = $new = array(
-            'updates' => array(
-                'core' => array(
-                    array(
-                        'version' => 2012060104
-                    ),
-                    array(
-                        'version' => 2012120100
-                    )
-                ),
-                'mod_foo' => array(
-                    array(
-                        'version' => 2011010101
-                    )
-                )
-            )
-        );
-        $cmp = $provider->compare_responses($old, $new);
-        $this->assertInternalType('array', $cmp);
-        $this->assertEmpty($cmp);
-    }
-
-    public function test_compare_responses_new_and_missing_update() {
-        $provider = testable_available_update_checker::instance();
-        $old = array(
-            'updates' => array(
-                'core' => array(
-                    array(
-                        'version' => 2012060104
-                    )
-                ),
-                'mod_foo' => array(
-                    array(
-                        'version' => 2011010101
-                    )
-                )
-            )
-        );
-        $new = array(
-            'updates' => array(
-                'core' => array(
-                    array(
-                        'version' => 2012060104
-                    ),
-                    array(
-                        'version' => 2012120100
-                    )
-                )
-            )
-        );
-        $cmp = $provider->compare_responses($old, $new);
-        $this->assertInternalType('array', $cmp);
-        $this->assertNotEmpty($cmp);
-        $this->assertCount(1, $cmp);
-        $this->assertCount(1, $cmp['core']);
-        $this->assertEquals(2012120100, $cmp['core'][0]['version']);
-    }
-
-    public function test_compare_responses_modified_update() {
-        $provider = testable_available_update_checker::instance();
-        $old = array(
-            'updates' => array(
-                'mod_foo' => array(
-                    array(
-                        'version' => 2011010101
-                    )
-                )
-            )
-        );
-        $new = array(
-            'updates' => array(
-                'mod_foo' => array(
-                    array(
-                        'version' => 2011010102
-                    )
-                )
-            )
-        );
-        $cmp = $provider->compare_responses($old, $new);
-        $this->assertInternalType('array', $cmp);
-        $this->assertNotEmpty($cmp);
-        $this->assertCount(1, $cmp);
-        $this->assertCount(1, $cmp['mod_foo']);
-        $this->assertEquals(2011010102, $cmp['mod_foo'][0]['version']);
-    }
-
-    public function test_compare_responses_invalid_format() {
-        $provider = testable_available_update_checker::instance();
-        $broken = array(
-            'status' => 'ERROR' // No 'updates' key here.
-        );
-        $this->setExpectedException('available_update_checker_exception');
-        $cmp = $provider->compare_responses($broken, $broken);
-    }
-
-    public function test_is_same_release_explicit() {
-        $provider = testable_available_update_checker::instance();
-        $this->assertTrue($provider->is_same_release('2.3dev (Build: 20120323)', '2.3dev (Build: 20120323)'));
-        $this->assertTrue($provider->is_same_release('2.3dev (Build: 20120323)', '2.3dev (Build: 20120330)'));
-        $this->assertFalse($provider->is_same_release('2.3dev (Build: 20120529)', '2.3 (Build: 20120601)'));
-        $this->assertFalse($provider->is_same_release('2.3dev', '2.3 dev'));
-        $this->assertFalse($provider->is_same_release('2.3.1', '2.3'));
-        $this->assertFalse($provider->is_same_release('2.3.1', '2.3.2'));
-        $this->assertTrue($provider->is_same_release('2.3.2+', '2.3.2')); // Yes, really!
-        $this->assertTrue($provider->is_same_release('2.3.2 (Build: 123456)', '2.3.2+ (Build: 123457)'));
-        $this->assertFalse($provider->is_same_release('3.0 Community Edition', '3.0 Enterprise Edition'));
-        $this->assertTrue($provider->is_same_release('3.0 Community Edition', '3.0 Community Edition (Build: 20290101)'));
-    }
-
-    public function test_is_same_release_implicit() {
-        $provider = testable_available_update_checker::instance();
-        $provider->fake_current_environment(2012060102.00, '2.3.2 (Build: 20121012)', '2.3', array());
-        $this->assertTrue($provider->is_same_release('2.3.2'));
-        $this->assertTrue($provider->is_same_release('2.3.2+'));
-        $this->assertTrue($provider->is_same_release('2.3.2+ (Build: 20121013)'));
-        $this->assertFalse($provider->is_same_release('2.4dev (Build: 20121012)'));
-    }
-}
-
-
-/**
- * Base class for testable plugininfo classes.
- */
-class testable_plugininfo_base extends plugininfo_base {
-
-    protected function get_plugin_manager() {
-        return testable_plugin_manager::instance();
-    }
-}
-
-
-/**
- * Modified {@link plugininfo_mod} suitable for testing purposes.
- */
-class testable_plugininfo_mod extends plugininfo_mod {
-
-    public function init_display_name() {
-        $this->displayname = ucfirst($this->name);
-    }
-
-    public function is_standard() {
-        if ($this->component === 'mod_foo') {
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    public function load_db_version() {
-        if ($this->component !== 'mod_new') {
-            $this->versiondb = 2012022900;
-        }
-    }
-
-    public function is_uninstall_allowed() {
-        return true; // Allow uninstall for standard plugins too.
-    }
-
-    protected function get_plugin_manager() {
-        return testable_plugin_manager::instance();
-    }
-}
-
-
-/**
- * Testable class representing subplugins of testable mod_foo.
- */
-class testable_pluginfo_foolish extends testable_plugininfo_base {
-
-    public function init_display_name() {
-        $this->displayname = ucfirst($this->name);
-    }
-
-    public function is_standard() {
-        return false;
-    }
-
-    public function load_db_version() {
-        $this->versiondb = 2012022900;
-    }
-}
-
-
-/**
- * Testable class representing subplugins of testable mod_baz.
- */
-class testable_pluginfo_bazmeg extends testable_plugininfo_base {
-
-    public function init_display_name() {
-        $this->displayname = ucfirst($this->name);
-    }
-
-    public function is_standard() {
-        return false;
-    }
-
-    public function load_db_version() {
-        $this->versiondb = null;
-    }
-}
-
-
-/**
- * Testable class representing subplugins of testable mod_qux.
- */
-class testable_pluginfo_quxcat extends testable_plugininfo_base {
-
-    public function init_display_name() {
-        $this->displayname = ucfirst($this->name);
-    }
-
-    public function is_standard() {
-        return false;
-    }
-
-    public function load_db_version() {
-        $this->versiondb = 2013041103;
-    }
-
-    public function is_uninstall_allowed() {
-        return false;
-    }
-}
-
-
-/**
- * Modified {@link plugin_manager} suitable for testing purposes
- */
-class testable_plugin_manager extends plugin_manager {
-
-    /**
-     * Factory method for this class
-     *
-     * @return plugin_manager the singleton instance
-     */
-    public static function instance() {
-        global $CFG;
-
-        if (is_null(self::$singletoninstance)) {
-            self::$singletoninstance = new self();
-        }
-        return self::$singletoninstance;
-    }
-
-    /**
-     * A version of {@link plugin_manager::get_plugins()} that prepares some faked
-     * testable instances.
-     *
-     * @param bool $disablecache ignored in this class
-     * @return array
-     */
-    public function get_plugins($disablecache = false) {
-
-        $dirroot = dirname(__FILE__).'/fixtures/mockplugins';
-
-        $this->pluginsinfo = array(
-            'mod' => array(
-                'foo' => plugininfo_default_factory::make('mod', $dirroot.'/mod', 'foo',
-                    $dirroot.'/mod/foo', 'testable_plugininfo_mod'),
-                'bar' => plugininfo_default_factory::make('mod', $dirroot.'/bar', 'bar',
-                    $dirroot.'/mod/bar', 'testable_plugininfo_mod'),
-                'baz' => plugininfo_default_factory::make('mod', $dirroot.'/baz', 'baz',
-                    $dirroot.'/mod/baz', 'testable_plugininfo_mod'),
-                'qux' => plugininfo_default_factory::make('mod', $dirroot.'/qux', 'qux',
-                    $dirroot.'/mod/qux', 'testable_plugininfo_mod'),
-                'new' => plugininfo_default_factory::make('mod', $dirroot.'/new', 'new',
-                    $dirroot.'/mod/new', 'testable_plugininfo_mod'),
-            ),
-            'foolish' => array(
-                'frog' => plugininfo_default_factory::make('foolish', $dirroot.'/mod/foo/lish', 'frog',
-                    $dirroot.'/mod/foo/lish/frog', 'testable_pluginfo_foolish'),
-                'hippo' => plugininfo_default_factory::make('foolish', $dirroot.'/mod/foo/lish', 'hippo',
-                    $dirroot.'/mod/foo/lish/hippo', 'testable_pluginfo_foolish'),
-            ),
-            'bazmeg' => array(
-                'one' => plugininfo_default_factory::make('bazmeg', $dirroot.'/mod/baz/meg', 'one',
-                    $dirroot.'/mod/baz/meg/one', 'testable_pluginfo_bazmeg'),
-            ),
-            'quxcat' => array(
-                'one' => plugininfo_default_factory::make('quxcat', $dirroot.'/mod/qux/cat', 'one',
-                    $dirroot.'/mod/qux/cat/one', 'testable_pluginfo_quxcat'),
-            ),
-        );
-
-        $checker = testable_available_update_checker::instance();
-        $this->pluginsinfo['mod']['foo']->check_available_updates($checker);
-        $this->pluginsinfo['mod']['bar']->check_available_updates($checker);
-        $this->pluginsinfo['mod']['baz']->check_available_updates($checker);
-        $this->pluginsinfo['mod']['new']->check_available_updates($checker);
-        $this->pluginsinfo['bazmeg']['one']->check_available_updates($checker);
-        $this->pluginsinfo['quxcat']['one']->check_available_updates($checker);
-
-        return $this->pluginsinfo;
-    }
-
-    /**
-     * Testable version of {@link plugin_manager::get_subplugins()} that works with
-     * the simulated environment.
-     *
-     * In this case, the mod_foo fake module provides subplugins of type 'foolish',
-     * mod_baz provides subplugins of type 'bazmeg' and mod_qux has 'quxcat'.
-     *
-     * @param bool $disablecache ignored in this class
-     * @return array
-     */
-    public function get_subplugins($disablecache = false) {
-
-        $this->subpluginsinfo = array(
-            'mod_foo' => array(
-                'foolish' => (object)array(
-                    'type' => 'foolish',
-                    'typerootdir' => 'mod/foo/lish',
-                ),
-            ),
-            'mod_baz' => array(
-                'bazmeg' => (object)array(
-                    'type' => 'bazmeg',
-                    'typerootdir' => 'mod/baz/meg',
-                ),
-            ),
-            'mod_qux' => array(
-                'quxcat' => (object)array(
-                    'type' => 'quxcat',
-                    'typerootdir' => 'mod/qux/cat',
-                ),
-            ),
-        );
-
-        return $this->subpluginsinfo;
-    }
-
-    /**
-     * Adds support for mock plugin types.
-     */
-    protected function normalize_component($component) {
-
-        // List of mock plugin types used in these unit tests.
-        $faketypes = array('foolish', 'bazmeg', 'quxcat');
-
-        foreach ($faketypes as $faketype) {
-            if (strpos($component, $faketype.'_') === 0) {
-                return explode('_', $component, 2);
-            }
-        }
-
-        return parent::normalize_component($component);
-    }
-
-    public function plugintype_name($type) {
-        return ucfirst($type);
-    }
-
-    public function plugintype_name_plural($type) {
-        return ucfirst($type).'s'; // Simple, isn't it? ;-).
-    }
-
-    public function plugin_external_source($component) {
-        if ($component === 'foolish_frog') {
-            return true;
-        }
-        return false;
-    }
-}
-
-
-/**
- * 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 {
-}
-
-
-/**
- * Test cases for {@link available_update_deployer} class.
- *
- * @group core_plugin
- */
-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]);
-    }
-}
index ae66726..2473c30 100644 (file)
@@ -36,6 +36,9 @@ information provided here is intended especially for developers.
   Use core_user::get_noreply_user() and core_user::get_support_user() to get noreply and support user's respectively.
   Real users can be used as noreply/support users by setting $CFG->noreplyuserid and $CFG->supportuserid
 * New function readfile_allow_large() in filelib.php for use when very large files may need sending to user.
+* Use plugin_manager::reset_caches() when changing visibility of plugins.
+* Implement new method get_enabled_plugins() method in subplugin info classes.
+* Each plugin should include version information in version.php.
 
 DEPRECATIONS:
 Various previously deprecated functions have now been altered to throw DEBUG_DEVELOPER debugging notices
index 0ca4493..12ed9b9 100644 (file)
@@ -222,21 +222,25 @@ function upgrade_main_savepoint($result, $version, $allowabort=true) {
 function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
     global $DB;
 
+    $component = 'mod_'.$modname;
+
     if (!$result) {
-        throw new upgrade_exception("mod_$modname", $version);
+        throw new upgrade_exception($component, $version);
     }
 
+    $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
+
     if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
         print_error('modulenotexist', 'debug', '', $modname);
     }
 
-    if ($module->version >= $version) {
+    if ($dbversion >= $version) {
         // something really wrong is going on in upgrade script
-        throw new downgrade_exception("mod_$modname", $module->version, $version);
+        throw new downgrade_exception($component, $dbversion, $version);
     }
-    $module->version = $version;
-    $DB->update_record('modules', $module);
-    upgrade_log(UPGRADE_LOG_NORMAL, "mod_$modname", 'Upgrade savepoint reached');
+    set_config('version', $version, $component);
+
+    upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 
     // reset upgrade timeout to default
     upgrade_set_timeout();
@@ -262,21 +266,25 @@ function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
 function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) {
     global $DB;
 
+    $component = 'block_'.$blockname;
+
     if (!$result) {
-        throw new upgrade_exception("block_$blockname", $version);
+        throw new upgrade_exception($component, $version);
     }
 
+    $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
+
     if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
         print_error('blocknotexist', 'debug', '', $blockname);
     }
 
-    if ($block->version >= $version) {
+    if ($dbversion >= $version) {
         // something really wrong is going on in upgrade script
-        throw new downgrade_exception("block_$blockname", $block->version, $version);
+        throw new downgrade_exception($component, $dbversion, $version);
     }
-    $block->version = $version;
-    $DB->update_record('block', $block);
-    upgrade_log(UPGRADE_LOG_NORMAL, "block_$blockname", 'Upgrade savepoint reached');
+    set_config('version', $version, $component);
+
+    upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 
     // reset upgrade timeout to default
     upgrade_set_timeout();
@@ -301,16 +309,19 @@ function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true
  * @return void
  */
 function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) {
+    global $DB;
+
     $component = $type.'_'.$plugin;
 
     if (!$result) {
         throw new upgrade_exception($component, $version);
     }
 
-    $installedversion = get_config($component, 'version');
-    if ($installedversion >= $version) {
+    $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
+
+    if ($dbversion >= $version) {
         // Something really wrong is going on in the upgrade script
-        throw new downgrade_exception($component, $installedversion, $version);
+        throw new downgrade_exception($component, $dbversion, $version);
     }
     set_config('version', $version, $component);
     upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
@@ -338,6 +349,7 @@ function upgrade_stale_php_files_present() {
 
     $someexamplesofremovedfiles = array(
         // removed in 2.6dev
+        '/admin/block.php',
         '/admin/oacleanup.php',
         // removed in 2.5dev
         '/backup/lib.php',
@@ -402,12 +414,10 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
         }
 
         $plugin = new stdClass();
-        $module = new stdClass(); // Prevent some notices when plugin placed in wrong directory.
+        $plugin->version = null;
+        $module = $plugin; // Prevent some notices when plugin placed in wrong directory.
         require($fullplug.'/version.php');  // defines $plugin with version etc
-
-        if (!isset($plugin->version) and isset($module->version)) {
-            $plugin = $module;
-        }
+        unset($module);
 
         // if plugin tells us it's full name we may check the location
         if (isset($plugin->component)) {
@@ -425,7 +435,6 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
         $plugin->name     = $plug;
         $plugin->fullname = $component;
 
-
         if (!empty($plugin->requires)) {
             if ($plugin->requires > $CFG->version) {
                 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
@@ -457,7 +466,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
             }
         }
 
-        $installedversion = get_config($plugin->fullname, 'version');
+        $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
         if (empty($installedversion)) { // new installation
             $startcallback($component, true, $verbose);
 
@@ -503,7 +512,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
                 $result = true;
             }
 
-            $installedversion = get_config($plugin->fullname, 'version');
+            $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
             if ($installedversion < $plugin->version) {
                 // store version if not already there
                 upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
@@ -516,6 +525,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
             events_update_definition($component);
             message_update_providers($component);
             if ($type === 'message') {
+                // Ugly hack!
                 message_update_processors($plug);
             }
             upgrade_plugin_mnet_functions($component);
@@ -555,36 +565,34 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
             throw new plugin_defective_exception($component, 'Missing version.php');
         }
 
-        $module = new stdClass();
-        $plugin = new stdClass(); // Prevent some notices when plugin placed in wrong directory.
-        require($fullmod .'/version.php');  // defines $module with version etc
-
-        if (!isset($module->version) and isset($plugin->version)) {
-            $module = $plugin;
-        }
+        $plugin = new stdClass();
+        $plugin->version = null;
+        $module = $plugin;
+        require($fullmod .'/version.php');  // Defines $module/$plugin with version etc.
+        $plugin = clone($module);
+        unset($module->version);
+        unset($module->component);
+        unset($module->dependencies);
+        unset($module->release);
 
         // if plugin tells us it's full name we may check the location
-        if (isset($module->component)) {
-            if ($module->component !== $component) {
+        if (isset($plugin->component)) {
+            if ($plugin->component !== $component) {
                 $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullmod);
-                $expected = str_replace($CFG->dirroot, '$CFG->dirroot', core_component::get_component_directory($module->component));
+                $expected = str_replace($CFG->dirroot, '$CFG->dirroot', core_component::get_component_directory($plugin->component));
                 throw new plugin_misplaced_exception($component, $expected, $current);
             }
         }
 
-        if (empty($module->version)) {
-            if (isset($module->version)) {
-                // Version is empty but is set - it means its value is 0 or ''. Let us skip such module.
-                // This is intended for developers so they can work on the early stages of the module.
-                continue;
-            }
+        if (empty($plugin->version)) {
+            // Version must be always set now!
             throw new plugin_defective_exception($component, 'Missing version value in version.php');
         }
 
-        if (!empty($module->requires)) {
-            if ($module->requires > $CFG->version) {
-                throw new upgrade_requires_exception($component, $module->version, $CFG->version, $module->requires);
-            } else if ($module->requires < 2010000000) {
+        if (!empty($plugin->requires)) {
+            if ($plugin->requires > $CFG->version) {
+                throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
+            } else if ($plugin->requires < 2010000000) {
                 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
             }
         }
@@ -600,7 +608,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
 
         $module->name = $mod;   // The name MUST match the directory
 
-        $currmodule = $DB->get_record('modules', array('name'=>$module->name));
+        $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 
         if (file_exists($fullmod.'/db/install.php')) {
             if (get_config($module->name, 'installrunning')) {
@@ -622,7 +630,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
             }
         }
 
-        if (empty($currmodule->version)) {
+        if (empty($installedversion)) {
             $startcallback($component, true, $verbose);
 
         /// Execute install.xml (XMLDB) - must be present in all modules
@@ -630,6 +638,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
 
         /// Add record into modules table - may be needed in install.php already
             $module->id = $DB->insert_record('modules', $module);
+            upgrade_mod_savepoint(true, $plugin->version, $module->name, false);
 
         /// Post installation hook - optional
             if (file_exists("$fullmod/db/install.php")) {
@@ -651,22 +660,23 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
 
             $endcallback($component, true, $verbose);
 
-        } else if ($currmodule->version < $module->version) {
+        } else if ($installedversion < $plugin->version) {
         /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
             $startcallback($component, false, $verbose);
 
             if (is_readable($fullmod.'/db/upgrade.php')) {
                 require_once($fullmod.'/db/upgrade.php');  // defines new upgrading function
                 $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
-                $result = $newupgrade_function($currmodule->version, $module);
+                $result = $newupgrade_function($installedversion, $module);
             } else {
                 $result = true;
             }
 
+            $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
             $currmodule = $DB->get_record('modules', array('name'=>$module->name));
-            if ($currmodule->version < $module->version) {
+            if ($installedversion < $plugin->version) {
                 // store version if not already there
-                upgrade_mod_savepoint($result, $module->version, $mod, false);
+                upgrade_mod_savepoint($result, $plugin->version, $mod, false);
             }
 
             // update cron flag if needed
@@ -684,8 +694,8 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
 
             $endcallback($component, false, $verbose);
 
-        } else if ($currmodule->version > $module->version) {
-            throw new downgrade_exception($component, $currmodule->version, $module->version);
+        } else if ($installedversion > $plugin->version) {
+            throw new downgrade_exception($component, $installedversion, $plugin->version);
         }
     }
 }
@@ -731,24 +741,30 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
             throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
         }
         $plugin = new stdClass();
-        $module = new stdClass(); // Prevent some notices when module placed in wrong directory.
-        $plugin->version = NULL;
+        $plugin->version = null;
         $plugin->cron    = 0;
+        $module = $plugin; // Prevent some notices when module placed in wrong directory.
         include($fullblock.'/version.php');
-        if (!isset($plugin->version) and isset($module->version)) {
-            $plugin = $module;
-        }
-        $block = $plugin;
+        unset($module);
+        $block = clone($plugin);
+        unset($block->version);
+        unset($block->component);
+        unset($block->dependencies);
+        unset($block->release);
 
         // if plugin tells us it's full name we may check the location
-        if (isset($block->component)) {
-            if ($block->component !== $component) {
+        if (isset($plugin->component)) {
+            if ($plugin->component !== $component) {
                 $current = str_replace($CFG->dirroot, '$CFG->dirroot', $fullblock);
-                $expected = str_replace($CFG->dirroot, '$CFG->dirroot', core_component::get_component_directory($block->component));
+                $expected = str_replace($CFG->dirroot, '$CFG->dirroot', core_component::get_component_directory($plugin->component));
                 throw new plugin_misplaced_exception($component, $expected, $current);
             }
         }
 
+        if (empty($plugin->version)) {
+            throw new plugin_defective_exception($component, 'Missing block version.');
+        }
+
         if (!empty($plugin->requires)) {
             if ($plugin->requires > $CFG->version) {
                 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
@@ -778,11 +794,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
 
         $block->name     = $blockname;   // The name MUST match the directory
 
-        if (empty($block->version)) {
-            throw new plugin_defective_exception($component, 'Missing block version.');
-        }
-
-        $currblock = $DB->get_record('block', array('name'=>$block->name));
+        $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 
         if (file_exists($fullblock.'/db/install.php')) {
             if (get_config('block_'.$blockname, 'installrunning')) {
@@ -804,7 +816,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
             }
         }
 
-        if (empty($currblock->version)) { // block not installed yet, so install it
+        if (empty($installedversion)) { // block not installed yet, so install it
             $conflictblock = array_search($blocktitle, $blocktitles);
             if ($conflictblock !== false) {
                 // Duplicate block titles are not allowed, they confuse people
@@ -817,6 +829,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
                 $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
             }
             $block->id = $DB->insert_record('block', $block);
+            upgrade_block_savepoint(true, $plugin->version, $block->name, false);
 
             if (file_exists($fullblock.'/db/install.php')) {
                 require_once($fullblock.'/db/install.php');
@@ -839,21 +852,22 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
 
             $endcallback($component, true, $verbose);
 
-        } else if ($currblock->version < $block->version) {
+        } else if ($installedversion < $plugin->version) {
             $startcallback($component, false, $verbose);
 
             if (is_readable($fullblock.'/db/upgrade.php')) {
                 require_once($fullblock.'/db/upgrade.php');  // defines new upgrading function
                 $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
-                $result = $newupgrade_function($currblock->version, $block);
+                $result = $newupgrade_function($installedversion, $block);
             } else {
                 $result = true;
             }
 
+            $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
             $currblock = $DB->get_record('block', array('name'=>$block->name));
-            if ($currblock->version < $block->version) {
+            if ($installedversion < $plugin->version) {
                 // store version if not already there
-                upgrade_block_savepoint($result, $block->version, $block->name, false);
+                upgrade_block_savepoint($result, $plugin->version, $block->name, false);
             }
 
             if ($currblock->cron != $block->cron) {
@@ -871,8 +885,8 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
 
             $endcallback($component, false, $verbose);
 
-        } else if ($currblock->version > $block->version) {
-            throw new downgrade_exception($component, $currblock->version, $block->version);
+        } else if ($installedversion > $plugin->version) {
+            throw new downgrade_exception($component, $installedversion, $plugin->version);
         }
     }
 
@@ -1178,35 +1192,6 @@ function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
         include("$CFG->dirroot/version.php");
         $targetversion = $version;
 
-    } else if ($plugintype === 'mod') {
-        try {
-            $currentversion = $DB->get_field('modules', 'version', array('name'=>$pluginname));
-            $currentversion = ($currentversion === false) ? null : $currentversion;
-        } catch (Exception $ignored) {
-        }
-        $cd = core_component::get_component_directory($component);
-        if (file_exists("$cd/version.php")) {
-            $module = new stdClass();
-            $module->version = null;
-            include("$cd/version.php");
-            $targetversion = $module->version;
-        }
-
-    } else if ($plugintype === 'block') {
-        try {
-            if ($block = $DB->get_record('block', array('name'=>$pluginname))) {
-                $currentversion = $block->version;
-            }
-        } catch (Exception $ignored) {
-        }
-        $cd = core_component::get_component_directory($component);
-        if (file_exists("$cd/version.php")) {
-            $plugin = new stdClass();
-            $plugin->version = null;
-            include("$cd/version.php");
-            $targetversion = $plugin->version;
-        }
-
     } else {
         $pluginversion = get_config($component, 'version');
         if (!empty($pluginversion)) {
@@ -1216,6 +1201,7 @@ function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
         if (file_exists("$cd/version.php")) {
             $plugin = new stdClass();
             $plugin->version = null;
+            $module = $plugin;
             include("$cd/version.php");
             $targetversion = $plugin->version;
         }
index 8fb5af3..30eaba7 100644 (file)
@@ -113,9 +113,11 @@ class moodle1_mod_resource_handler extends moodle1_mod_handler {
             // use the version of the successor instead of the current mod/resource
             // beware - the version.php declares info via $module object, do not use
             // a variable of such name here
-            $module = new stdClass();
+            $plugin = new stdClass();
+            $plugin->version = null;
+            $module = $plugin;
             include $CFG->dirroot.'/mod/'.$successor->get_modname().'/version.php';
-            $cminfo['version'] = $module->version;
+            $cminfo['version'] = $plugin->version;
 
             // stash the new course module information for this successor
             $cminfo['modulename'] = $successor->get_modname();
index a84935c..e8b0b39 100644 (file)
@@ -29,6 +29,7 @@ if (empty($CFG->enableportfolios)) {
     print_error('disabled', 'portfolio');
 }
 
+require_once($CFG->libdir . '/pluginlib.php');
 require_once($CFG->libdir . '/portfoliolib.php');
 require_once($CFG->libdir . '/portfolio/forms.php');
 
@@ -57,9 +58,6 @@ $display = true; // set this to false in the conditions to stop processing
 
 require_login($course, false);
 
-// Purge all caches related to portfolio administration.
-cache::make('core', 'plugininfo_portfolio')->purge();
-
 $PAGE->set_url($url);
 $PAGE->set_context(context_user::instance($user->id));
 $PAGE->set_title("$course->fullname: $fullname: $strportfolios");
@@ -84,6 +82,7 @@ if (!empty($config)) {
         $success = $instance->set_user_config($fromform, $USER->id);
             //$success = $success && $instance->save();
         if ($success) {
+            plugin_manager::reset_caches();
             redirect($baseurl, get_string('instancesaved', 'portfolio'), 3);
         } else {
             print_error('instancenotsaved', 'portfolio', $baseurl);
@@ -100,6 +99,7 @@ if (!empty($config)) {
 } else if (!empty($hide)) {
     $instance = portfolio_instance($hide);
     $instance->set_user_config(array('visible' => !$instance->get_user_config('visible', $USER->id)), $USER->id);
+    plugin_manager::reset_caches();
 }
 
 if ($display) {
index 9474abd..8d77f28 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2013092000.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2013092001.02;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.