Merge branch 'MDL-35959-master' of git://git.luns.net.uk/moodle
authorSam Hemelryk <sam@moodle.com>
Tue, 30 Oct 2012 00:31:30 +0000 (13:31 +1300)
committerSam Hemelryk <sam@moodle.com>
Tue, 30 Oct 2012 00:31:30 +0000 (13:31 +1300)
364 files changed:
admin/cli/install.php
admin/cli/upgrade.php
admin/handlevirus.php
admin/renderer.php
admin/settings/courses.php
admin/settings/plugins.php
admin/settings/server.php
admin/tool/customlang/locallib.php
admin/tool/customlang/styles.css
admin/tool/generator/locallib.php
admin/tool/xmldb/actions/check_bigints/check_bigints.class.php
admin/tool/xmldb/lang/en/tool_xmldb.php
admin/webservice/service_users.php
auth/cas/auth.php
auth/cas/config.html
auth/cas/lang/en/auth_cas.php
auth/db/auth.php
auth/ldap/auth.php
auth/ldap/config.html
auth/ldap/lang/en/auth_ldap.php
auth/shibboleth/index.php
auth/shibboleth/lang/en/auth_shibboleth.php
backup/util/checks/tests/checks_test.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/helper/backup_general_helper.class.php
backup/util/includes/backup_includes.php
blocks/community/forms.php
blocks/course_overview/renderer.php
blocks/dock.js
blocks/navigation/block_navigation.php
blocks/online_users/block_online_users.php
blocks/site_main_menu/block_site_main_menu.php
blocks/social_activities/block_social_activities.php
blog/locallib.php
cache/README.md [new file with mode: 0644]
cache/admin.php [new file with mode: 0644]
cache/classes/config.php [new file with mode: 0644]
cache/classes/definition.php [new file with mode: 0644]
cache/classes/dummystore.php [new file with mode: 0644]
cache/classes/factory.php [new file with mode: 0644]
cache/classes/helper.php [new file with mode: 0644]
cache/classes/interfaces.php [new file with mode: 0644]
cache/classes/loaders.php [new file with mode: 0644]
cache/forms.php [new file with mode: 0644]
cache/lib.php [new file with mode: 0644]
cache/locallib.php [new file with mode: 0644]
cache/locks/file/lang/en/cachelock_file.php [new file with mode: 0644]
cache/locks/file/lib.php [new file with mode: 0644]
cache/renderer.php [new file with mode: 0644]
cache/stores/file/addinstanceform.php [new file with mode: 0644]
cache/stores/file/lang/en/cachestore_file.php [new file with mode: 0644]
cache/stores/file/lib.php [new file with mode: 0644]
cache/stores/file/version.php [new file with mode: 0644]
cache/stores/memcache/addinstanceform.php [new file with mode: 0644]
cache/stores/memcache/lang/en/cachestore_memcache.php [new file with mode: 0644]
cache/stores/memcache/lib.php [new file with mode: 0644]
cache/stores/memcache/settings.php [new file with mode: 0644]
cache/stores/memcache/version.php [new file with mode: 0644]
cache/stores/memcached/addinstanceform.php [new file with mode: 0644]
cache/stores/memcached/lang/en/cachestore_memcached.php [new file with mode: 0644]
cache/stores/memcached/lib.php [new file with mode: 0644]
cache/stores/memcached/settings.php [new file with mode: 0644]
cache/stores/memcached/version.php [new file with mode: 0644]
cache/stores/mongodb/addinstanceform.php [new file with mode: 0644]
cache/stores/mongodb/lang/en/cachestore_mongodb.php [new file with mode: 0644]
cache/stores/mongodb/lib.php [new file with mode: 0644]
cache/stores/mongodb/settings.php [new file with mode: 0644]
cache/stores/mongodb/version.php [new file with mode: 0644]
cache/stores/session/lang/en/cachestore_session.php [new file with mode: 0644]
cache/stores/session/lib.php [new file with mode: 0644]
cache/stores/session/version.php [new file with mode: 0644]
cache/stores/static/lang/en/cachestore_static.php [new file with mode: 0644]
cache/stores/static/lib.php [new file with mode: 0644]
cache/stores/static/version.php [new file with mode: 0644]
cache/testperformance.php [new file with mode: 0644]
cache/tests/cache_test.php [new file with mode: 0644]
cache/tests/fixtures/lib.php [new file with mode: 0644]
cache/tests/locallib_test.php [new file with mode: 0644]
cohort/lib.php
comment/locallib.php
config-dist.php
course/dndupload.js
course/dnduploadlib.php
course/edit.php
course/externallib.php
course/format/formatlegacy.php
course/format/lib.php
course/format/renderer.php
course/format/topics/format.php
course/format/upgrade.txt
course/format/weeks/format.php
course/lib.php
course/mod.php
course/modedit.php
course/recent.php
course/recent_form.php
course/resources.php
course/rest.php
course/tests/courselib_test.php
course/tests/externallib_test.php
course/view.php
course/yui/dragdrop/dragdrop.js
course/yui/modchooser/modchooser.js
course/yui/toolboxes/toolboxes.js
enrol/ajax.php
enrol/cohort/tests/sync_test.php
enrol/externallib.php
enrol/imsenterprise/lib.php
enrol/imsenterprise/settings.php
enrol/instances.php
enrol/locallib.php
enrol/manual/ajax.php
enrol/manual/cli/sync.php
enrol/manual/db/messages.php [new file with mode: 0644]
enrol/manual/db/upgrade.php [new file with mode: 0644]
enrol/manual/edit.php
enrol/manual/edit_form.php
enrol/manual/externallib.php
enrol/manual/lang/en/enrol_manual.php
enrol/manual/lib.php
enrol/manual/manage.php
enrol/manual/settings.php
enrol/manual/tests/lib_test.php
enrol/manual/version.php
enrol/manual/yui/quickenrolment/assets/skins/sam/quickenrolment.css
enrol/self/cli/sync.php
enrol/self/db/messages.php [new file with mode: 0644]
enrol/self/db/upgrade.php [new file with mode: 0644]
enrol/self/edit.php
enrol/self/edit_form.php
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/settings.php
enrol/self/tests/self_test.php
enrol/self/version.php
enrol/upgrade.txt
enrol/yui/otherusersmanager/assets/skins/sam/otherusersmanager.css
enrol/yui/otherusersmanager/otherusersmanager.js
grade/lib.php
grade/report/grader/lib.php
index.php
install/lang/bg/install.php
install/lang/he/install.php
install/lang/it/langconfig.php
install/lang/ms/admin.php [new file with mode: 0644]
install/lang/ms/install.php [new file with mode: 0644]
install/lang/ms/moodle.php [new file with mode: 0644]
install/lang/ro/install.php
install/lang/sr_cr/install.php
lang/en/admin.php
lang/en/backup.php
lang/en/cache.php [new file with mode: 0644]
lang/en/enrol.php
lang/en/mimetypes.php
lang/en/moodle.php
lang/en/plugin.php
lang/en/role.php
lib/adminlib.php
lib/blocklib.php
lib/completionlib.php
lib/csvlib.class.php
lib/datalib.php
lib/db/access.php
lib/db/caches.php [new file with mode: 0644]
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/editor/tinymce/adminlib.php
lib/editor/tinymce/lib.php
lib/editor/tinymce/plugins/dragmath/lang/en/tinymce_dragmath.php
lib/editor/tinymce/plugins/dragmath/lib.php
lib/editor/tinymce/plugins/dragmath/settings.php [new file with mode: 0644]
lib/editor/tinymce/plugins/dragmath/version.php
lib/editor/tinymce/plugins/moodleemoticon/lang/en/tinymce_moodleemoticon.php
lib/editor/tinymce/plugins/moodleemoticon/lib.php
lib/editor/tinymce/plugins/moodleemoticon/settings.php [new file with mode: 0644]
lib/editor/tinymce/plugins/moodleemoticon/version.php
lib/editor/tinymce/plugins/spellchecker/lib.php
lib/editor/tinymce/settings.php
lib/enrollib.php
lib/filebrowser/file_browser.php
lib/filebrowser/file_info.php
lib/filebrowser/file_info_context_course.php
lib/filebrowser/file_info_context_coursecat.php
lib/filebrowser/file_info_context_module.php
lib/filebrowser/file_info_stored.php
lib/filelib.php
lib/filestorage/file_packer.php
lib/filestorage/tests/zip_packer_test.php
lib/filestorage/zip_archive.php
lib/filestorage/zip_packer.php
lib/form/text.php
lib/form/yui/dateselector/dateselector.js
lib/formslib.php
lib/grade/grade_category.php
lib/grade/tests/grade_category_test.php
lib/installlib.php
lib/javascript-static.js
lib/messagelib.php
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/data_generator.php
lib/phpunit/classes/message_sink.php [new file with mode: 0644]
lib/phpunit/classes/module_generator.php
lib/phpunit/classes/util.php
lib/phpunit/lib.php
lib/phpunit/tests/advanced_test.php
lib/phpunit/tests/generator_test.php
lib/pluginlib.php
lib/portfoliolib.php
lib/questionlib.php
lib/sessionlib.php
lib/setup.php
lib/tablelib.php
lib/tests/conditionlib_test.php
lib/tests/datalib_test.php
lib/tests/moodlelib_test.php
lib/tests/outputcomponents_test.php
lib/tests/outputlib_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/yui/chooserdialogue/chooserdialogue.js
message/edit.php
message/lib.php
message/renderer.php
mod/assign/adminlib.php
mod/assign/db/events.php
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/offline/importgradesform.php
mod/assign/gradeform.php
mod/assign/gradingoptionsform.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/renderer.php
mod/assign/settings.php
mod/assign/tests/externallib_test.php
mod/assign/upgradelib.php
mod/assign/version.php
mod/assignment/index.php
mod/assignment/type/online/all.php
mod/book/db/upgrade.php
mod/book/db/upgradelib.php [new file with mode: 0644]
mod/book/index.php
mod/book/lang/en/book.php
mod/book/locallib.php
mod/book/tool/importhtml/index.php
mod/book/version.php
mod/book/view.php
mod/chat/index.php
mod/choice/index.php
mod/data/field/date/field.class.php
mod/data/index.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/locallib.php
mod/data/tests/fixtures/test_data_records.csv
mod/data/tests/search_test.php
mod/data/view.php
mod/feedback/index.php
mod/folder/index.php
mod/forum/discuss.php
mod/forum/index.php
mod/forum/lib.php
mod/forum/locallib.php
mod/glossary/import.php
mod/glossary/index.php
mod/glossary/locallib.php
mod/imscp/index.php
mod/imscp/locallib.php
mod/lesson/importppt.php [deleted file]
mod/lesson/importpptlib.php [deleted file]
mod/lesson/index.php
mod/lesson/lang/en/lesson.php
mod/lesson/renderer.php
mod/lesson/version.php
mod/lti/index.php
mod/page/index.php
mod/quiz/attemptlib.php
mod/quiz/index.php
mod/quiz/lib.php
mod/quiz/settings.php
mod/quiz/styles.css
mod/resource/index.php
mod/scorm/datamodel.php
mod/scorm/datamodels/aicc.js.php
mod/scorm/datamodels/callback.js.php
mod/scorm/datamodels/scorm_12.js.php
mod/scorm/datamodels/scorm_13.js.php
mod/scorm/datamodels/scorm_13lib.php
mod/scorm/datamodels/scormlib.php
mod/scorm/datamodels/sequencinghandler.php [new file with mode: 0644]
mod/scorm/datamodels/sequencinglib.php
mod/scorm/index.php
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/loaddatamodel.php
mod/scorm/locallib.php
mod/scorm/mod_form.php
mod/scorm/module.js
mod/scorm/player.php
mod/scorm/view.php
mod/survey/index.php
mod/upgrade.txt
mod/url/index.php
mod/wiki/index.php
mod/workshop/fileinfolib.php
mod/workshop/index.php
mod/workshop/locallib.php
phpunit.xml.dist
pix/f/epub.png [new file with mode: 0644]
pix/i/test.png [new file with mode: 0644]
pix/i/test.svg [new file with mode: 0644]
portfolio/googledocs/db/upgrade.php
portfolio/picasa/db/upgrade.php
question/behaviour/behaviourbase.php
question/behaviour/immediatecbm/behaviour.php
question/editlib.php
question/engine/bank.php
question/question.php
question/type/calculated/styles.css
question/type/multianswer/module.js
question/type/multianswer/renderer.php
question/type/multichoice/edit_multichoice_form.php
question/type/numerical/edit_numerical_form.php
question/type/numerical/styles.css
question/type/shortanswer/question.php
report/log/locallib.php
report/outline/index.php
report/outline/user.php
repository/dropbox/locallib.php
repository/filepicker.js
repository/googledocs/db/upgrade.php
repository/lib.php
repository/local/lib.php
repository/picasa/db/upgrade.php
repository/upload/lang/en/repository_upload.php
theme/base/style/admin.css
theme/base/style/core.css
theme/canvas/style/blocks.css
theme/canvas/style/core.css
theme/canvas/style/mods.css
theme/formal_white/lang/en/theme_formal_white.php
theme/image.php
theme/magazine/style/core.css
theme/serenity/style/core.css
theme/standard/style/core.css
user/edit.php
user/profile/field/textarea/field.class.php
user/selector/module.js
user/selector/search.php
version.php
webservice/lib.php

index 79ff0b7..38aa06e 100644 (file)
@@ -240,18 +240,8 @@ echo get_string('cliinstallheader', 'install', $CFG->target_release)."\n";
 if ($interactive) {
     cli_separator();
     $languages = get_string_manager()->get_list_of_translations();
-    // format the langs nicely - 3 per line
-    $c = 0;
-    $langlist = '';
-    foreach ($languages as $key=>$lang) {
-        $c++;
-        $length = iconv_strlen($lang, 'UTF-8');
-        $padded = $lang.str_repeat(' ', 38-$length);
-        $langlist .= $padded;
-        if ($c % 3 == 0) {
-            $langlist .= "\n";
-        }
-    }
+    // Do not put the langs into columns because it is not compatible with RTL.
+    $langlist = implode("\n", $languages);
     $default = $CFG->lang;
     cli_heading(get_string('availablelangs', 'install'));
     echo $langlist."\n";
index 1fae897..72a03f0 100644 (file)
@@ -159,9 +159,7 @@ set_config('branch', $branch);
 upgrade_noncore(true);
 
 // log in as admin - we need doanything permission when applying defaults
-$admins = get_admins();
-$admin = reset($admins);
-session_set_user($admin);
+session_set_user(get_admin());
 
 // apply all default settings, just in case do it twice to fill all defaults
 admin_apply_default_settings(NULL, false);
index d7cf3cd..bfd76c1 100644 (file)
@@ -86,7 +86,7 @@ function notify_admins($user,$subject,$a) {
     foreach ($admins as $admin) {
         $eventdata = new stdClass();
         $eventdata->modulename        = 'moodle';
-        $eventdata->userfrom          = $admin;
+        $eventdata->userfrom          = get_admin();
         $eventdata->userto            = $admin;
         $eventdata->subject           = $subject;
         $eventdata->fullmessage       = $body;
@@ -107,7 +107,7 @@ function notify_admins_unknown($file,$a) {
     foreach ($admins as $admin) {
         $eventdata = new stdClass();
         $eventdata->modulename        = 'moodle';
-        $eventdata->userfrom          = $admin;
+        $eventdata->userfrom          = get_admin();
         $eventdata->userto            = $admin;
         $eventdata->subject           = $subject;
         $eventdata->fullmessage       = $body;
index 3a3c2bd..293bf52 100644 (file)
@@ -470,7 +470,7 @@ class core_admin_renderer extends plugin_renderer_base {
         $copyrighttext = '<a href="http://moodle.org/">Moodle</a> '.
                          '<a href="http://docs.moodle.org/dev/Releases" title="'.$CFG->version.'">'.$CFG->release.'</a><br />'.
                          'Copyright &copy; 1999 onwards, Martin Dougiamas<br />'.
-                         'and <a href="http://docs.moodle.org/dev/Credits">many other contributors</a>.<br />'.
+                         'and <a href="http://moodle.org/dev">many other contributors</a>.<br />'.
                          '<a href="http://docs.moodle.org/dev/License">GNU Public License</a>';
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -683,7 +683,7 @@ class core_admin_renderer extends plugin_renderer_base {
                 $row = new html_table_row();
                 $row->attributes['class'] = 'type-' . $plugin->type . ' name-' . $plugin->type . '_' . $plugin->name;
 
-                if ($this->page->theme->resolve_image_location('icon', $plugin->type . '_' . $plugin->name)) {
+                if ($this->page->theme->resolve_image_location('icon', $plugin->type . '_' . $plugin->name, null)) {
                     $icon = $this->output->pix_icon('icon', '', $plugin->type . '_' . $plugin->name, array('class' => 'smallicon pluginicon'));
                 } else {
                     $icon = $this->output->pix_icon('spacer', '', 'moodle', array('class' => 'smallicon pluginicon noicon'));
index 25614ab..363bfb1 100644 (file)
@@ -169,6 +169,25 @@ if ($hassiteconfig
         500 => '500');
     $temp->add(new admin_setting_configselect('backup/backup_auto_keep', new lang_string('keep'), new lang_string('backupkeephelp'), 1, $keepoptoins));
     $temp->add(new admin_setting_configcheckbox('backup/backup_shortname', new lang_string('backup_shortname', 'admin'), new lang_string('backup_shortnamehelp', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_auto_skip_hidden', new lang_string('skiphidden', 'backup'), new lang_string('skiphiddenhelp', 'backup'), 1));
+    $temp->add(new admin_setting_configselect('backup/backup_auto_skip_modif_days', new lang_string('skipmodifdays', 'backup'), new lang_string('skipmodifdayshelp', 'backup'), 30, array(
+        0 => new lang_string('never'),
+        1 => new lang_string('numdays', '', 1),
+        2 => new lang_string('numdays', '', 2),
+        3 => new lang_string('numdays', '', 3),
+        5 => new lang_string('numdays', '', 5),
+        7 => new lang_string('numdays', '', 7),
+        10 => new lang_string('numdays', '', 10),
+        14 => new lang_string('numdays', '', 14),
+        20 => new lang_string('numdays', '', 20),
+        30 => new lang_string('numdays', '', 30),
+        60 => new lang_string('numdays', '', 60),
+        90 => new lang_string('numdays', '', 90),
+        120 => new lang_string('numdays', '', 120),
+        180 => new lang_string('numdays', '', 180),
+        365 => new lang_string('numdays', '', 365)
+    )));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_auto_skip_modif_prev', new lang_string('skipmodifprev', 'backup'), new lang_string('skipmodifprevhelp', 'backup'), 0));
 
     // Automated defaults section.
     $temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), ''));
index 4f22f08..29d0665 100644 (file)
@@ -5,66 +5,34 @@
  */
 
 if ($hassiteconfig) {
+    require_once("$CFG->libdir/pluginlib.php");
+    $allplugins = plugin_manager::instance()->get_plugins();
+
     $ADMIN->add('modules', new admin_page_pluginsoverview());
+
+    // activity modules
     $ADMIN->add('modules', new admin_category('modsettings', new lang_string('activitymodules')));
     $ADMIN->add('modsettings', new admin_page_managemods());
-    $modules = $DB->get_records('modules', array(), "name ASC");
-    foreach ($modules as $module) {
-        $modulename = $module->name;
-        if (!file_exists("$CFG->dirroot/mod/$modulename/lib.php")) {
-            continue;
-        }
-        $strmodulename = new lang_string('modulename', 'mod_'.$modulename);
-        if (file_exists($CFG->dirroot.'/mod/'.$modulename.'/settings.php')) {
-            // do not show disabled modules in tree, keep only settings link on manage page
-            $settings = new admin_settingpage('modsetting'.$modulename, $strmodulename, 'moodle/site:config', !$module->visible);
-            include($CFG->dirroot.'/mod/'.$modulename.'/settings.php');
-            if ($settings) {
-                $ADMIN->add('modsettings', $settings);
-            }
-        }
+    foreach ($allplugins['mod'] as $module) {
+        $module->load_settings($ADMIN, 'modsettings', $hassiteconfig);
     }
 
     // hidden script for converting journals to online assignments (or something like that) linked from elsewhere
     $ADMIN->add('modsettings', new admin_externalpage('oacleanup', 'Online Assignment Cleanup', $CFG->wwwroot.'/'.$CFG->admin.'/oacleanup.php', 'moodle/site:config', true));
 
+    // blocks
     $ADMIN->add('modules', new admin_category('blocksettings', new lang_string('blocks')));
     $ADMIN->add('blocksettings', new admin_page_manageblocks());
-    $blocks = $DB->get_records('block', array(), "name ASC");
-    foreach ($blocks as $block) {
-        $blockname = $block->name;
-        if (!file_exists("$CFG->dirroot/blocks/$blockname/block_$blockname.php")) {
-            continue;
-        }
-        $strblockname = new lang_string('pluginname', 'block_'.$blockname);
-        if (file_exists($CFG->dirroot.'/blocks/'.$blockname.'/settings.php')) {
-            $settings = new admin_settingpage('blocksetting'.$blockname, $strblockname, 'moodle/site:config', !$block->visible);
-            include($CFG->dirroot.'/blocks/'.$blockname.'/settings.php');
-            if ($settings) {
-                $ADMIN->add('blocksettings', $settings);
-            }
-        }
+    foreach ($allplugins['block'] as $block) {
+        $block->load_settings($ADMIN, 'blocksettings', $hassiteconfig);
     }
 
     // message outputs
     $ADMIN->add('modules', new admin_category('messageoutputs', new lang_string('messageoutputs', 'message')));
     $ADMIN->add('messageoutputs', new admin_page_managemessageoutputs());
     $ADMIN->add('messageoutputs', new admin_page_defaultmessageoutputs());
-    require_once($CFG->dirroot.'/message/lib.php');
-    $processors = get_message_processors();
-    foreach ($processors as $processor) {
-        $processorname = $processor->name;
-        if (!$processor->available) {
-            continue;
-        }
-        if ($processor->hassettings) {
-            $strprocessorname = new lang_string('pluginname', 'message_'.$processorname);
-            $settings = new admin_settingpage('messagesetting'.$processorname, $strprocessorname, 'moodle/site:config', !$processor->enabled);
-            include($CFG->dirroot.'/message/output/'.$processor->name.'/settings.php');
-            if ($settings) {
-                $ADMIN->add('messageoutputs', $settings);
-            }
-        }
+    foreach ($allplugins['message'] as $processor) {
+        $processor->load_settings($ADMIN, 'messageoutputs', $hassiteconfig);
     }
 
     // authentication plugins
@@ -92,72 +60,27 @@ if ($hassiteconfig) {
     $temp->add(new admin_setting_configtext('recaptchaprivatekey', new lang_string('recaptchaprivatekey', 'admin'), new lang_string('configrecaptchaprivatekey', 'admin'), '', PARAM_NOTAGS));
     $ADMIN->add('authsettings', $temp);
 
-
-    $auths = get_plugin_list('auth');
-    $authsenabled = get_enabled_auth_plugins();
-    foreach ($auths as $authname => $authdir) {
-        $strauthname = new lang_string('pluginname', "auth_{$authname}");
-        // do not show disabled auths in tree, keep only settings link on manage page
-        $enabled = in_array($authname, $authsenabled);
-        if (file_exists($authdir.'/settings.php')) {
-            // TODO: finish implementation of common settings - locking, etc.
-            $settings = new admin_settingpage('authsetting'.$authname, $strauthname, 'moodle/site:config', !$enabled);
-            include($authdir.'/settings.php');
-            if ($settings) {
-                $ADMIN->add('authsettings', $settings);
-            }
-
-        } else {
-            $ADMIN->add('authsettings', new admin_externalpage('authsetting'.$authname, $strauthname, "$CFG->wwwroot/$CFG->admin/auth_config.php?auth=$authname", 'moodle/site:config', !$enabled));
-        }
+    foreach ($allplugins['auth'] as $auth) {
+        $auth->load_settings($ADMIN, 'authsettings', $hassiteconfig);
     }
 
-
     // Enrolment plugins
     $ADMIN->add('modules', new admin_category('enrolments', new lang_string('enrolments', 'enrol')));
     $temp = new admin_settingpage('manageenrols', new lang_string('manageenrols', 'enrol'));
     $temp->add(new admin_setting_manageenrols());
-    if (empty($CFG->enrol_plugins_enabled)) {
-        $enabled = array();
-    } else {
-        $enabled = explode(',', $CFG->enrol_plugins_enabled);
-    }
-    $enrols = get_plugin_list('enrol');
     $ADMIN->add('enrolments', $temp);
-    foreach($enrols as $enrol=>$enrolpath) {
-        if (!file_exists("$enrolpath/settings.php")) {
-            continue;
-        }
-
-        $settings = new admin_settingpage('enrolsettings'.$enrol, new lang_string('pluginname', 'enrol_'.$enrol), 'moodle/site:config', !in_array($enrol, $enabled));
-        // settings.php may create a subcategory or unset the settings completely
-        include("$enrolpath/settings.php");
-        if ($settings) {
-            $ADMIN->add('enrolments', $settings);
-        }
-
+    foreach($allplugins['enrol'] as $enrol) {
+        $enrol->load_settings($ADMIN, 'enrolments', $hassiteconfig);
     }
-    unset($enabled);
-    unset($enrols);
 
 
 /// Editor plugins
     $ADMIN->add('modules', new admin_category('editorsettings', new lang_string('editors', 'editor')));
     $temp = new admin_settingpage('manageeditors', new lang_string('editorsettings', 'editor'));
     $temp->add(new admin_setting_manageeditors());
-    $htmleditors = editors_get_available();
     $ADMIN->add('editorsettings', $temp);
-
-    $editors_available = editors_get_available();
-    foreach ($editors_available as $editor=>$editorstr) {
-        if (file_exists($CFG->dirroot . '/lib/editor/'.$editor.'/settings.php')) {
-            $settings = new admin_settingpage('editorsettings'.$editor, new lang_string('pluginname', 'editor_'.$editor), 'moodle/site:config');
-            // settings.php may create a subcategory or unset the settings completely
-            include($CFG->dirroot . '/lib/editor/'.$editor.'/settings.php');
-            if ($settings) {
-                $ADMIN->add('editorsettings', $settings);
-            }
-        }
+    foreach ($allplugins['editor'] as $editor) {
+        $editor->load_settings($ADMIN, 'editorsettings', $hassiteconfig);
     }
 
 /// License types
@@ -218,17 +141,8 @@ if ($hassiteconfig) {
     }
     $ADMIN->add('filtersettings', $temp);
 
-    $activefilters = filter_get_globally_enabled();
-    $filternames = filter_get_all_installed();
-    foreach ($filternames as $filterpath => $strfiltername) {
-        if (file_exists("$CFG->dirroot/$filterpath/filtersettings.php")) {
-            $settings = new admin_settingpage('filtersetting'.str_replace('/', '', $filterpath),
-                    $strfiltername, 'moodle/site:config', !isset($activefilters[$filterpath]));
-            include("$CFG->dirroot/$filterpath/filtersettings.php");
-            if ($settings) {
-                $ADMIN->add('filtersettings', $settings);
-            }
-        }
+    foreach ($allplugins['filter'] as $filter) {
+        $filter->load_settings($ADMIN, 'filtersettings', $hassiteconfig);
     }
 
 
@@ -325,19 +239,9 @@ if ($hassiteconfig) {
     $ADMIN->add('repositorysettings', new admin_externalpage('repositoryinstanceedit',
         new lang_string('editrepositoryinstance', 'repository'), $url, 'moodle/site:config', true),
         '', $url);
-    foreach (repository::get_types() as $repositorytype) {
-      //display setup page for plugins with: general options or multiple instances (e.g. has instance config)
-      $typeoptionnames = repository::static_function($repositorytype->get_typename(), 'get_type_option_names');
-      $instanceoptionnames = repository::static_function($repositorytype->get_typename(), 'get_instance_option_names');
-      if (!empty($typeoptionnames) || !empty($instanceoptionnames)) {
-
-          $params = array('action'=>'edit', 'sesskey'=>sesskey(), 'repos'=>$repositorytype->get_typename());
-          $settingsurl = new moodle_url("/$CFG->admin/repository.php", $params);
-          $repositoryexternalpage = new admin_externalpage('repositorysettings'.$repositorytype->get_typename(), $repositorytype->get_readablename(), $settingsurl);
-          $ADMIN->add('repositorysettings', $repositoryexternalpage);
-      }
+    foreach ($allplugins['repository'] as $repositorytype) {
+        $repositorytype->load_settings($ADMIN, 'repositorysettings', $hassiteconfig);
     }
-}
 
 /// Web services
     $ADMIN->add('modules', new admin_category('webservicesettings', new lang_string('webservices', 'webservice')));
@@ -375,17 +279,8 @@ if ($hassiteconfig) {
                         'admin'), new lang_string('configenablewsdocumentation', 'admin', $wsdoclink), false));
     $ADMIN->add('webservicesettings', $temp);
     /// links to protocol pages
-    $webservices_available = get_plugin_list('webservice');
-    $active_webservices = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
-    foreach ($webservices_available as $webservice => $location) {
-        if (file_exists("$location/settings.php")) {
-            $name = new lang_string('pluginname', 'webservice_'.$webservice);
-            $settings = new admin_settingpage('webservicesetting'.$webservice, $name, 'moodle/site:config', !in_array($webservice, $active_webservices) or empty($CFG->enablewebservices));
-            include("$location/settings.php");
-            if ($settings) {
-                $ADMIN->add('webservicesettings', $settings);
-            }
-        }
+    foreach ($allplugins['webservice'] as $webservice) {
+        $webservice->load_settings($ADMIN, 'webservicesettings', $hassiteconfig);
     }
     /// manage token page link
     $ADMIN->add('webservicesettings', new admin_externalpage('addwebservicetoken', new lang_string('managetokens', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/tokens.php", 'moodle/site:config', true));
@@ -395,6 +290,7 @@ if ($hassiteconfig) {
         $temp->add(new admin_setting_heading('webservicesaredisabled', '', new lang_string('disabledwarning', 'webservice')));
     }
     $ADMIN->add('webservicesettings', $temp);
+}
 
 // Question type settings
 if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) {
@@ -405,17 +301,8 @@ if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext))
     // Question type settings.
     $ADMIN->add('modules', new admin_category('qtypesettings', new lang_string('questiontypes', 'admin')));
     $ADMIN->add('qtypesettings', new admin_page_manageqtypes());
-    $qtypes = get_plugin_list('qtype');
-    foreach ($qtypes as $qtype => $path) {
-        $settingsfile = $path . '/settings.php';
-        if (file_exists($settingsfile)) {
-            $settings = new admin_settingpage('qtypesetting' . $qtype,
-                    new lang_string('pluginname', 'qtype_' . $qtype), 'moodle/question:config');
-            include($settingsfile);
-            if ($settings) {
-                $ADMIN->add('qtypesettings', $settings);
-            }
-        }
+    foreach ($allplugins['qtype'] as $qtype) {
+        $qtype->load_settings($ADMIN, 'qtypesettings', $hassiteconfig);
     }
 }
 
@@ -425,10 +312,8 @@ if ($hassiteconfig && !empty($CFG->enableplagiarism)) {
     $ADMIN->add('plagiarism', new admin_externalpage('manageplagiarismplugins', new lang_string('manageplagiarism', 'plagiarism'),
         $CFG->wwwroot . '/' . $CFG->admin . '/plagiarism.php'));
 
-    foreach (get_plugin_list('plagiarism') as $plugin => $plugindir) {
-        if (file_exists($plugindir.'/settings.php')) {
-            $ADMIN->add('plagiarism', new admin_externalpage('plagiarism'.$plugin, new lang_string($plugin, 'plagiarism_'.$plugin), "$CFG->wwwroot/plagiarism/$plugin/settings.php", 'moodle/site:config'));
-        }
+    foreach ($allplugins['plagiarism'] as $plugin) {
+        $plugin->load_settings($ADMIN, 'plagiarism', $hassiteconfig);
     }
 }
 $ADMIN->add('reports', new admin_externalpage('comments', new lang_string('comments'), $CFG->wwwroot.'/comment/', 'moodle/site:viewreports'));
@@ -492,6 +377,22 @@ foreach (get_plugin_list('tool') as $plugin => $plugindir) {
     }
 }
 
+// Now add the Cache plugins
+if ($hassiteconfig) {
+    $ADMIN->add('modules', new admin_category('cache', new lang_string('caching', 'cache')));
+    $ADMIN->add('cache', new admin_externalpage('cacheconfig', new lang_string('cacheconfig', 'cache'), $CFG->wwwroot .'/cache/admin.php'));
+    $ADMIN->add('cache', new admin_externalpage('cachetestperformance', new lang_string('testperformance', 'cache'), $CFG->wwwroot . '/cache/testperformance.php'));
+    $ADMIN->add('cache', new admin_category('cachestores', new lang_string('cachestores', 'cache')));
+    foreach (get_plugin_list('cachestore') as $plugin => $path) {
+        $settingspath = $path.'/settings.php';
+        if (file_exists($settingspath)) {
+            $settings = new admin_settingpage('cachestore_'.$plugin.'_settings', new lang_string('pluginname', 'cachestore_'.$plugin), 'moodle/site:config');
+            include($settingspath);
+            $ADMIN->add('cachestores', $settings);
+        }
+    }
+}
+
 /// Add all local plugins - must be always last!
 if ($hassiteconfig) {
     $ADMIN->add('modules', new admin_category('localplugins', new lang_string('localplugins')));
@@ -499,6 +400,8 @@ if ($hassiteconfig) {
                                                         $CFG->wwwroot . '/' . $CFG->admin . '/localplugins.php'));
 }
 
+// extend settings for each local plugin. Note that their settings may be in any part of the
+// settings tree and may be visible not only for administrators. We can not use $allplugins here
 foreach (get_plugin_list('local') as $plugin => $plugindir) {
     $settings_path = "$plugindir/settings.php";
     if (file_exists($settings_path)) {
index 461f06c..0c8746e 100644 (file)
@@ -216,6 +216,7 @@ $temp->add(new admin_setting_configselect('memcachedpconn', new lang_string('mem
                                           array( '0' => new lang_string('no'),
                                                  '1' => new lang_string('yes'))));
 */
+
 $ADMIN->add('server', $temp);
 
 
index 47bbdfa..878eb4c 100644 (file)
@@ -488,7 +488,7 @@ class tool_customlang_translator implements renderable {
         list($insql, $inparams) = $DB->get_in_or_equal($filter->component, SQL_PARAMS_NAMED);
 
         $csql = "SELECT COUNT(*)";
-        $fsql = "SELECT s.id, s.*, c.name AS component";
+        $fsql = "SELECT s.*, c.name AS component";
         $sql  = "  FROM {tool_customlang_components} c
                    JOIN {tool_customlang} s ON s.componentid = c.id
                   WHERE s.lang = :lang
index c743bcf..963b289 100644 (file)
@@ -67,3 +67,7 @@
 #page-admin-tool-customlang-index .continuebutton {
     margin-top: 1em;
 }
+
+.path-admin-tool-customlang #translator .standard.master.cell.c2 {
+    word-break: break-all;
+}
index 169f643..117e1ac 100644 (file)
@@ -556,7 +556,7 @@ class generator {
                         $module->name = ucfirst($moduledata->name) . ' ' . $moduledata->count++;
 
                         $module->course = $courseid;
-                        $module->section = $i;
+                        $module->section = 0;
                         $module->module = $moduledata->id;
                         $module->modulename = $moduledata->name;
                         $module->add = $moduledata->name;
@@ -564,10 +564,7 @@ class generator {
                         $module->coursemodule = '';
                         $add_instance_function = $moduledata->name . '_add_instance';
 
-                        $section = get_course_section($i, $courseid);
-                        $module->section = $section->id;
                         $module->coursemodule = add_course_module($module);
-                        $module->section = $i;
 
                         if (function_exists($add_instance_function)) {
                             $this->verbose("Calling module function $add_instance_function");
@@ -580,13 +577,12 @@ class generator {
                             }
                         }
 
-                        add_mod_to_section($module);
+                        $module->section = course_add_cm_to_section($courseid, $module->coursemodule, $i);
 
                         $module->cmidnumber = set_coursemodule_idnumber($module->coursemodule, '');
 
                         $this->verbose("A $moduledata->name module was added to section $i (id $module->section) "
                             ."of course $courseid.");
-                        rebuild_course_cache($courseid);
 
                         $module_instance = $DB->get_field('course_modules', 'instance', array('id' => $module->coursemodule));
                         $module_record = $DB->get_record($moduledata->name, array('id' => $module_instance));
index ef7b622..69bfb84 100644 (file)
@@ -29,9 +29,6 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class check_bigints extends XMLDBCheckAction {
-    private $correct_type;
-    private $dbfamily;
-
     /**
      * Init method, every subclass will have its own
      */
@@ -51,19 +48,6 @@ class check_bigints extends XMLDBCheckAction {
             'nowrongintsfound' => 'tool_xmldb',
             'yeswrongintsfound' => 'tool_xmldb',
         ));
-
-        // Correct fields must be type bigint for MySQL and int8 for PostgreSQL
-        $this->dbfamily = $DB->get_dbfamily();
-        switch ($this->dbfamily) {
-            case 'mysql':
-                $this->correct_type = 'bigint';
-                break;
-            case 'postgres':
-                $this->correct_type = 'int8';
-                break;
-            default:
-                $this->correct_type = NULL;
-        }
     }
 
     protected function check_table(xmldb_table $xmldb_table, array $metacolumns) {
@@ -75,19 +59,24 @@ class check_bigints extends XMLDBCheckAction {
             $o.='        <ul>';
             foreach ($xmldb_fields as $xmldb_field) {
                 // If the field isn't integer(10), skip
-                if ($xmldb_field->getType() != XMLDB_TYPE_INTEGER || $xmldb_field->getLength() != 10) {
+                if ($xmldb_field->getType() != XMLDB_TYPE_INTEGER) {
                     continue;
                 }
                 // If the metadata for that column doesn't exist, skip
                 if (!isset($metacolumns[$xmldb_field->getName()])) {
                     continue;
                 }
+                $minlength = $xmldb_field->getLength();
+                if ($minlength > 18) {
+                    // Anything above 18 is borked, just ignore it here.
+                    $minlength = 18;
+                }
                 // To variable for better handling
                 $metacolumn = $metacolumns[$xmldb_field->getName()];
                 // Going to check this field in DB
                 $o.='            <li>' . $this->str['field'] . ': ' . $xmldb_field->getName() . ' ';
                 // Detect if the physical field is wrong
-                if ($metacolumn->type != $this->correct_type) {
+                if (($metacolumn->meta_type != 'I' and $metacolumn->meta_type != 'R') or $metacolumn->max_length < $minlength) {
                     $o.='<font color="red">' . $this->str['wrong'] . '</font>';
                     // Add the wrong field to the list
                     $obj = new stdClass();
@@ -124,17 +113,7 @@ class check_bigints extends XMLDBCheckAction {
             foreach ($wrong_fields as $obj) {
                 $xmldb_table = $obj->table;
                 $xmldb_field = $obj->field;
-                // MySQL directly supports this
-
-// TODO: move this hack to generators!!
-
-                if ($this->dbfamily == 'mysql') {
-                    $sqlarr = $dbman->generator->getAlterFieldSQL($xmldb_table, $xmldb_field);
-                // PostgreSQL (XMLDB implementation) is a bit, er... imperfect.
-                } else if ($this->dbfamily == 'postgres') {
-                    $sqlarr = array('ALTER TABLE ' . $DB->get_prefix() . $xmldb_table->getName() .
-                              ' ALTER COLUMN ' . $xmldb_field->getName() . ' TYPE BIGINT;');
-                }
+                $sqlarr = $dbman->generator->getAlterFieldSQL($xmldb_table, $xmldb_field);
                 $r.= '            <li>' . $this->str['table'] . ': ' . $xmldb_table->getName() . '. ' .
                                           $this->str['field'] . ': ' . $xmldb_field->getName() . '</li>';
                 // Add to output if we have sentences
index 7b384b2..4dfc221 100644 (file)
@@ -101,7 +101,7 @@ $string['generate_documentation'] = 'Documentation';
 $string['gotolastused'] = 'Go to last used file';
 $string['change'] = 'Change';
 $string['charincorrectlength'] = 'Incorrect length for char field';
-$string['checkbigints'] = 'Check bigints';
+$string['checkbigints'] = 'Check integers';
 $string['check_bigints'] = 'Look for incorrect DB integers';
 $string['checkdefaults'] = 'Check defaults';
 $string['check_defaults'] = 'Look for inconsistent default values';
@@ -111,10 +111,13 @@ $string['checkindexes'] = 'Check indexes';
 $string['check_indexes'] = 'Look for missing DB indexes';
 $string['checkoraclesemantics'] = 'Check semantics';
 $string['check_oracle_semantics'] = 'Look for incorrect length semantics';
+$string['duplicateindexname'] = 'Duplicate index name';
 $string['incorrectfieldname'] = 'Incorrect name';
 $string['index'] = 'Index';
 $string['indexes'] = 'Indexes';
+$string['indexnameempty'] = 'Index name is empty';
 $string['integerincorrectlength'] = 'Incorrect length for integer field';
+$string['incorrectindexname'] = 'Incorrect index name';
 $string['incorrectkeyname'] = 'Incorrect key name';
 $string['incorrecttablename'] = 'Incorrect table name';
 $string['key'] = 'Key';
index 12db2b0..ad67c66 100644 (file)
@@ -97,7 +97,7 @@ $usersmissingcaps = $webservicemanager->get_missing_capabilities_by_users($allow
 
 //add the missing capabilities to the allowed users object to be displayed by renderer
 foreach ($allowedusers as &$alloweduser) {
-    if (!is_siteadmin($alloweduser->id) and key_exists($alloweduser->id, $usersmissingcaps)) {
+    if (!is_siteadmin($alloweduser->id) and array_key_exists($alloweduser->id, $usersmissingcaps)) {
         $alloweduser->missingcapabilities = implode(', ', $usersmissingcaps[$alloweduser->id]);
     }
 }
index 94ea981..ce84377 100644 (file)
@@ -268,6 +268,9 @@ class auth_plugin_cas extends auth_plugin_ldap {
         if (!isset($config->certificate_path)) {
             $config->certificate_path = '';
         }
+        if (!isset($config->logout_return_url)) {
+            $config->logout_return_url = '';
+        }
 
         // LDAP settings
         if (!isset($config->host_url)) {
@@ -331,6 +334,7 @@ class auth_plugin_cas extends auth_plugin_ldap {
         set_config('multiauth', $config->multiauth, $this->pluginconfig);
         set_config('certificate_check', $config->certificate_check, $this->pluginconfig);
         set_config('certificate_path', $config->certificate_path, $this->pluginconfig);
+        set_config('logout_return_url', $config->logout_return_url, $this->pluginconfig);
 
         // save LDAP settings
         set_config('host_url', trim($config->host_url), $this->pluginconfig);
@@ -439,4 +443,19 @@ class auth_plugin_cas extends auth_plugin_ldap {
         }
         parent::sync_users($do_updates);
     }
+
+    /**
+    * Hook for logout page
+    */
+    function logoutpage_hook() {
+        global $USER, $redirect;
+        // Only do this if the user is actually logged in via CAS
+        if ($USER->auth === $this->authtype) {
+            // Check if there is an alternative logout return url defined
+            if (isset($this->config->logout_return_url) && !empty($this->config->logout_return_url)) {
+                // Set redirect to alternative return url
+                $redirect = $this->config->logout_return_url;
+            }
+        }
+    }
 }
index 28557dd..031df6e 100644 (file)
@@ -33,6 +33,9 @@ if (!isset ($config->certificate_check)) {
 if (!isset ($config->certificate_path)) {
     $config->certificate_path = '';
 }
+if (!isset($config->logout_return_url)) {
+    $config->logout_return_url = '';
+}
 
 // set to defaults if undefined (LDAP)
 if (!isset($config->host_url)) {
@@ -201,6 +204,16 @@ $yesno = array( get_string('no'), get_string('yes') );
         <?php print_string('auth_cas_certificate_path', 'auth_cas') ?>
     </td>
 </tr>
+<tr valign="top" class="required">
+    <td align="right"><?php print_string('auth_cas_logout_return_url_key', 'auth_cas') ?>:</td>
+    <td>
+       <input name="logout_return_url" type="text" size="30" value="<?php echo $config->logout_return_url ?>" />
+       <?php if (isset($err['logout_return_url'])) { echo $OUTPUT->error_text($err['logout_return_url']); } ?>
+    </td>
+    <td>
+        <?php print_string('auth_cas_logout_return_url', 'auth_cas') ?>
+    </td>
+</tr>
 <tr>
    <td colspan="2">
         <h4><?php print_string('auth_ldap_server_settings', 'auth_ldap') ?></h4>
index 15cb616..70a82a0 100644 (file)
@@ -47,6 +47,8 @@ $string['auth_cas_invalidcaslogin'] = 'Sorry, your login has failed - you could
 $string['auth_cas_language'] = 'Select language for authentication pages';
 $string['auth_cas_language_key'] = 'Language';
 $string['auth_cas_logincas'] = 'Secure connection access';
+$string['auth_cas_logout_return_url_key'] = 'Alternative logout return URL';
+$string['auth_cas_logout_return_url'] = 'Provide the URL that CAS users shall be redirected to after logging out.<br />If left empty, users will be redirected to the location that moodle will redirect users to';
 $string['auth_cas_logoutcas'] = 'Select \'yes\' if you want to logout from CAS when you disconnect from Moodle';
 $string['auth_cas_logoutcas_key'] = 'CAS logout option';
 $string['auth_cas_multiauth'] = 'Select \'yes\' if you want to have multi-authentication (CAS + other authentication)';
index 60babc8..4c95249 100644 (file)
@@ -46,12 +46,22 @@ class auth_plugin_db extends auth_plugin_base {
         $extusername = textlib::convert($username, 'utf-8', $this->config->extencoding);
         $extpassword = textlib::convert($password, 'utf-8', $this->config->extencoding);
 
-        $authdb = $this->db_init();
-
         if ($this->is_internal()) {
             // lookup username externally, but resolve
             // password locally -- to support backend that
             // don't track passwords
+
+            if (isset($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_KEEP) {
+                // No need to connect to external database in this case because users are never removed and we verify password locally.
+                if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
+                    return validate_internal_user_password($user, $password);
+                } else {
+                    return false;
+                }
+            }
+
+            $authdb = $this->db_init();
+
             $rs = $authdb->Execute("SELECT * FROM {$this->config->table}
                                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
             if (!$rs) {
@@ -78,6 +88,8 @@ class auth_plugin_db extends auth_plugin_base {
         } else {
             // normal case: use external db for both usernames and passwords
 
+            $authdb = $this->db_init();
+
             if ($this->config->passtype === 'md5') {   // Re-format password accordingly
                 $extpassword = md5($extpassword);
             } else if ($this->config->passtype === 'sha1') {
index 049fd97..a90a41b 100644 (file)
@@ -41,6 +41,18 @@ if (!defined('AUTH_GID_NOGROUP')) {
     define('AUTH_GID_NOGROUP', -2);
 }
 
+// Regular expressions for a valid NTLM username and domain name.
+if (!defined('AUTH_NTLM_VALID_USERNAME')) {
+    define('AUTH_NTLM_VALID_USERNAME', '[^/\\\\\\\\\[\]:;|=,+*?<>@"]+');
+}
+if (!defined('AUTH_NTLM_VALID_DOMAINNAME')) {
+    define('AUTH_NTLM_VALID_DOMAINNAME', '[^\\\\\\\\\/:*?"<>|]+');
+}
+// Default format for remote users if using NTLM SSO
+if (!defined('AUTH_NTLM_DEFAULT_FORMAT')) {
+    define('AUTH_NTLM_DEFAULT_FORMAT', '%domain%\\%username%');
+}
+
 require_once($CFG->libdir.'/authlib.php');
 require_once($CFG->libdir.'/ldaplib.php');
 
@@ -1570,8 +1582,11 @@ class auth_plugin_ldap extends auth_plugin_base {
 
             switch ($this->config->ntlmsso_type) {
                 case 'ntlm':
-                    // Format is DOMAIN\username
-                    $username = substr(strrchr($username, '\\'), 1);
+                    // The format is now configurable, so try to extract the username
+                    $username = $this->get_ntlm_remote_user($username);
+                    if (empty($username)) {
+                        return false;
+                    }
                     break;
                 case 'kerberos':
                     // Format is username@DOMAIN
@@ -1779,6 +1794,9 @@ class auth_plugin_ldap extends auth_plugin_base {
         if (!isset($config->ntlmsso_type)) {
             $config->ntlmsso_type = 'ntlm';
         }
+        if (!isset($config->ntlmsso_remoteuserformat)) {
+            $config->ntlmsso_remoteuserformat = '';
+        }
 
         // Try to remove duplicates before storing the contexts (to avoid problems in sync_users()).
         $config->contexts = explode(';', $config->contexts);
@@ -1818,6 +1836,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         set_config('ntlmsso_subnet', trim($config->ntlmsso_subnet), $this->pluginconfig);
         set_config('ntlmsso_ie_fastpath', (int)$config->ntlmsso_ie_fastpath, $this->pluginconfig);
         set_config('ntlmsso_type', $config->ntlmsso_type, 'auth/ldap');
+        set_config('ntlmsso_remoteuserformat', trim($config->ntlmsso_remoteuserformat), 'auth/ldap');
 
         return true;
     }
@@ -2018,4 +2037,60 @@ class auth_plugin_ldap extends auth_plugin_base {
                                 $this->config->user_attribute, $this->config->search_sub);
     }
 
+
+    /**
+     * A chance to validate form data, and last chance to do stuff
+     * before it is inserted in config_plugin
+     *
+     * @param object object with submitted configuration settings (without system magic quotes)
+     * @param array $err array of error messages (passed by reference)
+     */
+    function validate_form($form, &$err) {
+        if ($form->ntlmsso_type == 'ntlm') {
+            $format = trim($form->ntlmsso_remoteuserformat);
+            if (!empty($format) && !preg_match('/%username%/i', $format)) {
+                $err['ntlmsso_remoteuserformat'] = get_string('auth_ntlmsso_missing_username', 'auth_ldap');
+            }
+        }
+    }
+
+
+    /**
+     * When using NTLM SSO, the format of the remote username we get in
+     * $_SERVER['REMOTE_USER'] may vary, depending on where from and how the web
+     * server gets the data. So we let the admin configure the format using two
+     * place holders (%domain% and %username%). This function tries to extract
+     * the username (stripping the domain part and any separators if they are
+     * present) from the value present in $_SERVER['REMOTE_USER'], using the
+     * configured format.
+     *
+     * @param string $remoteuser The value from $_SERVER['REMOTE_USER'] (converted to UTF-8)
+     *
+     * @return string The remote username (without domain part or
+     *                separators). Empty string if we can't extract the username.
+     */
+    protected function get_ntlm_remote_user($remoteuser) {
+        if (empty($this->config->ntlmsso_remoteuserformat)) {
+            $format = AUTH_NTLM_DEFAULT_FORMAT;
+        } else {
+            $format = $this->config->ntlmsso_remoteuserformat;
+        }
+
+        $format = preg_quote($format);
+        $formatregex = preg_replace(array('#%domain%#', '#%username%#'),
+                                    array('('.AUTH_NTLM_VALID_DOMAINNAME.')', '('.AUTH_NTLM_VALID_USERNAME.')'),
+                                    $format);
+        if (preg_match('#^'.$formatregex.'$#', $remoteuser, $matches)) {
+            $user = end($matches);
+            return $user;
+        }
+
+        /* We are unable to extract the username with the configured format. Probably
+         * the format specified is wrong, so log a warning for the admin and return
+         * an empty username.
+         */
+        error_log($this->errorlogtag.get_string ('auth_ntlmsso_maybeinvalidformat', 'auth_ldap'));
+        return '';
+    }
+
 } // End of the class
index 8ad27ef..3efe253 100644 (file)
@@ -94,6 +94,9 @@ if (!isset($config->ntlmsso_ie_fastpath)) {
 if (!isset($config->ntlmsso_type)) {
     $config->ntlmsso_type = 'ntlm';
 }
+if (!isset($config->ntlmsso_remoteuserformat)) {
+    $config->ntlmsso_remoteuserformat = '';
+}
 
 $yesno = array(get_string('no'), get_string('yes'));
 
@@ -539,6 +542,18 @@ $yesno = array(get_string('no'), get_string('yes'));
         <?php print_string('auth_ntlmsso_type','auth_ldap') ?>
     </td>
 </tr>
+<tr valign="top">
+    <td align="right">
+        <label for="ntlmsso_remoteuserformat"><?php print_string('auth_ntlmsso_remoteuserformat_key', 'auth_ldap') ?></label>
+    </td>
+    <td>
+        <input name="ntlmsso_remoteuserformat" id="ntlmsso_remoteuserformat" type="text" size="30" value="<?php echo $config->ntlmsso_remoteuserformat?>" />
+        <?php if (isset($err['ntlmsso_remoteuserformat'])) { echo $OUTPUT->error_text($err['ntlmsso_remoteuserformat']); } ?>
+    </td>
+    <td>
+        <?php print_string('auth_ntlmsso_remoteuserformat', 'auth_ldap') ?>
+    </td>
+</tr>
 <?php
 $help  = get_string('auth_ldapextrafields', 'auth_ldap');
 $help .= get_string('auth_updatelocal_expl', 'auth');
index bbb0ddb..848169a 100644 (file)
@@ -101,6 +101,10 @@ $string['auth_ntlmsso_enabled'] = 'Set to yes to attempt Single Sign On with the
 $string['auth_ntlmsso_enabled_key'] = 'Enable';
 $string['auth_ntlmsso_ie_fastpath'] = 'Set to yes to enable the NTLM SSO fast path (bypasses certain steps and only works if the client\'s browser is MS Internet Explorer).';
 $string['auth_ntlmsso_ie_fastpath_key'] = 'MS IE fast path?';
+$string['auth_ntlmsso_maybeinvalidformat'] = 'Unable to extract the username from the REMOTE_USER header. Is the configured format right?';
+$string['auth_ntlmsso_missing_username'] = 'You need to specify at least %username% in the remote username format';
+$string['auth_ntlmsso_remoteuserformat_key'] = 'Remote username format';
+$string['auth_ntlmsso_remoteuserformat'] = 'If you have chosen \'NTLM\' in \'Authentication type\', you can specify the remote username format here. If you leave this empty, the default DOMAIN\\username format will be used. You can use the optional <b>%domain%</b> placeholder to specify where the domain name appears, and the mandatory <b>%username%</b> placeholder to specify where the username appears. <br /><br />Some widely used formats are <tt>%domain%\\%username%</tt> (MS Windows default), <tt>%domain%/%username%</tt>, <tt>%domain%+%username%</tt> and just <tt>%username%</tt> (if there is no domain part).';
 $string['auth_ntlmsso_subnet'] = 'If set, it will only attempt SSO with clients in this subnet. Format: xxx.xxx.xxx.xxx/bitmask. Separate multiple subnets with \',\' (comma).';
 $string['auth_ntlmsso_subnet_key'] = 'Subnet';
 $string['auth_ntlmsso_type_key'] = 'Authentication type';
index 051ac4f..6a760ec 100644 (file)
@@ -41,9 +41,9 @@
 
     /// Check if the user has actually submitted login data to us
 
-        if ($shibbolethauth->user_login($frm->username, $frm->password)) {
+        if ($shibbolethauth->user_login($frm->username, $frm->password)
+                && $user = authenticate_user_login($frm->username, $frm->password)) {
 
-            $user = authenticate_user_login($frm->username, $frm->password);
             enrol_check_plugins($user);
             session_set_user($user);
 
@@ -87,7 +87,8 @@
         }
 
         else {
-            // For some weird reason the Shibboleth user couldn't be authenticated
+            // The Shibboleth user couldn't be mapped to a valid Moodle user
+            print_error('shib_invalid_account_error', 'auth_shibboleth');
         }
     }
 
index ca0f251..aace9a1 100644 (file)
@@ -51,6 +51,7 @@ $string['auth_shib_no_organizations_warning'] = 'If you want to use the integrat
 $string['auth_shib_only'] = 'Shibboleth only';
 $string['auth_shib_only_description'] = 'Check this option if a Shibboleth authentication shall be enforced';
 $string['auth_shib_username_description'] = 'Name of the webserver Shibboleth environment variable that shall be used as Moodle username';
+$string['shib_invalid_account_error'] = 'You seem to be Shibboleth authenticated but Moodle has no valid account for your username. Your account may not exist or it may have been suspended.';
 $string['shib_no_attributes_error'] = 'You seem to be Shibboleth authenticated but Moodle didn\'t receive any user attributes. Please check that your Identity Provider releases the necessary attributes ({$a}) to the Service Provider Moodle is running on or inform the webmaster of this server.';
 $string['shib_not_all_attributes_error'] = 'Moodle needs certain Shibboleth attributes which are not present in your case. The attributes are: {$a}<br />Please contact the webmaster of this server or your Identity Provider.';
 $string['shib_not_set_up_error'] = 'Shibboleth authentication doesn\'t seem to be set up correctly because no Shibboleth environment variables are present for this page. Please consult the <a href="README.txt">README</a> for further instructions on how to set up Shibboleth authentication or contact the webmaster of this Moodle installation.';
index ccee3ba..e9905c4 100644 (file)
@@ -45,7 +45,7 @@ class backup_check_testcase extends advanced_testcase {
 
         $this->resetAfterTest(true);
 
-        $course = $this->getDataGenerator()->create_course();
+        $course = $this->getDataGenerator()->create_course(array(), array('createsections' => true));
         $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
         $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
 
index 4b6c63c..a906801 100644 (file)
@@ -1504,7 +1504,8 @@ abstract class restore_dbops {
             }
             $currentfullname = $fullname.$suffixfull;
             $currentshortname = substr($shortname, 0, 100 - strlen($suffixshort)).$suffixshort; // < 100cc
-            $coursefull  = $DB->get_record_select('course', 'fullname = ? AND id != ?', array($currentfullname, $courseid));
+            $coursefull  = $DB->get_record_select('course', 'fullname = ? AND id != ?',
+                    array($currentfullname, $courseid), '*', IGNORE_MULTIPLE);
             $courseshort = $DB->get_record_select('course', 'shortname = ? AND id != ?', array($currentshortname, $courseid));
             $counter++;
         } while ($coursefull || $courseshort);
index e3ada4f..62874f8 100644 (file)
@@ -15,7 +15,6 @@
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * Utility helper for automated backups run through cron.
  *
@@ -25,6 +24,8 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * This class is an abstract class with methods that can be called to aid the
  * running of automated backups over cron.
@@ -69,6 +70,7 @@ abstract class backup_cron_automated_helper {
         $status = true;
         $emailpending = false;
         $now = time();
+        $config = get_config('backup');
 
         mtrace("Checking automated backup status",'...');
         $state = backup_cron_automated_helper::get_automated_backup_state($rundirective);
@@ -125,40 +127,69 @@ abstract class backup_cron_automated_helper {
                     $backupcourse = $DB->get_record('backup_courses', array('courseid'=>$course->id));
                 }
 
-                // Skip courses that do not yet need backup
+                // The last backup is considered as successful when OK or SKIPPED.
+                $lastbackupwassuccessful =  ($backupcourse->laststatus == self::BACKUP_STATUS_SKIPPED ||
+                                            $backupcourse->laststatus == self::BACKUP_STATUS_OK) && (
+                                            $backupcourse->laststarttime > 0 && $backupcourse->lastendtime > 0);
+
+                // Skip courses that do not yet need backup.
                 $skipped = !(($backupcourse->nextstarttime > 0 && $backupcourse->nextstarttime < $now) || $rundirective == self::RUN_IMMEDIATELY);
-                if ($skipped && $backupcourse->nextstarttime != $nextstarttime) {
-                    $backupcourse->nextstarttime = $nextstarttime;
-                    $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
-                    $DB->update_record('backup_courses', $backupcourse);
-                    mtrace('Backup of \'' . $course->fullname . '\' is scheduled on ' . $showtime);
+                $skippedmessage = 'Does not require backup';
+
+                // If config backup_auto_skip_hidden is set to true, skip courses that are not visible.
+                if (!$skipped && $config->backup_auto_skip_hidden) {
+                    $skipped = ($config->backup_auto_skip_hidden && !$course->visible);
+                    $skippedmessage = 'Not visible';
                 }
 
-                // Skip backup of unavailable courses that have remained unmodified in a month
-                if (!$skipped && empty($course->visible) && ($now - $course->timemodified) > 31*24*60*60) {  //Hidden + settings were unmodified last month
-                    //Check log if there were any modifications to the course content
-                    $sqlwhere = "course=:courseid AND time>:time AND ". $DB->sql_like('action', ':action', false, true, true);
-                    $params = array('courseid' => $course->id, 'time' => $now-31*24*60*60, 'action' => '%view%');
+                // If config backup_auto_skip_modif_days is set to true, skip courses
+                // that have not been modified since the number of days defined.
+                if (!$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_days) {
+                    $sqlwhere = "course=:courseid AND time>:time AND ".$DB->sql_like('action', ':action', false, true, true);
+                    $timenotmodifsincedays = $now - ($config->backup_auto_skip_modif_days * DAYSECS);
+                    // Check log if there were any modifications to the course content.
+                    $params = array('courseid' => $course->id,
+                                    'time' => $timenotmodifsincedays,
+                                    'action' => '%view%');
                     $logexists = $DB->record_exists_select('log', $sqlwhere, $params);
-                    if (!$logexists) {
-                        $backupcourse->laststatus = self::BACKUP_STATUS_SKIPPED;
-                        $backupcourse->nextstarttime = $nextstarttime;
-                        $DB->update_record('backup_courses', $backupcourse);
-                        mtrace('Skipping unchanged course '.$course->fullname);
-                        $skipped = true;
-                    }
+
+                    $skipped = ($course->timemodified <= $timenotmodifsincedays && !$logexists);
+                    $skippedmessage = 'Not modified in the past '.$config->backup_auto_skip_modif_days.' days';
                 }
 
-                //Now we backup every non-skipped course
-                if (!$skipped) {
+                // If config backup_auto_skip_modif_prev is set to true, skip courses
+                // that have not been modified since previous backup.
+                if (!$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_prev) {
+                    // Check log if there were any modifications to the course content.
+                    $params = array('courseid' => $course->id,
+                                    'time' => $backupcourse->laststarttime,
+                                    'action' => '%view%');
+                    $logexists = $DB->record_exists_select('log', $sqlwhere, $params);
+
+                    $skipped = ($course->timemodified <= $backupcourse->laststarttime && !$logexists);
+                    $skippedmessage = 'Not modified since previous backup';
+                }
+
+                // Skip courses not needed for backup.
+                if ($skipped) {
+                    // Output the next execution time when it has been updated.
+                    if ($backupcourse->nextstarttime != $nextstarttime) {
+                        mtrace('Backup of \'' . $course->fullname . '\' is scheduled on ' . $showtime);
+                    }
+                    $backupcourse->laststatus = self::BACKUP_STATUS_SKIPPED;
+                    $backupcourse->nextstarttime = $nextstarttime;
+                    $DB->update_record('backup_courses', $backupcourse);
+                    mtrace('Skipping '.$course->fullname.' ('.$skippedmessage.')');
+                } else {
+                    // Backup every non-skipped courses.
                     mtrace('Backing up '.$course->fullname.'...');
 
-                    //We have to send a email because we have included at least one backup
+                    // We have to send an email because we have included at least one backup.
                     $emailpending = true;
 
-                    //Only make the backup if laststatus isn't 2-UNFINISHED (uncontrolled error)
-                    if ($backupcourse->laststatus != 2) {
-                        //Set laststarttime
+                    // Only make the backup if laststatus isn't 2-UNFINISHED (uncontrolled error).
+                    if ($backupcourse->laststatus != self::BACKUP_STATUS_UNFINISHED) {
+                        // Set laststarttime.
                         $starttime = time();
 
                         $backupcourse->laststarttime = time();
@@ -505,9 +536,9 @@ abstract class backup_cron_automated_helper {
     /**
      * Removes excess backups from the external system and the local file system.
      *
-     * The number of backups keep comes from $config->backup_auto_keep
+     * The number of backups keep comes from $config->backup_auto_keep.
      *
-     * @param stdClass $course
+     * @param stdClass $course object
      * @return bool
      */
     public static function remove_excess_backups($course) {
@@ -517,7 +548,7 @@ abstract class backup_cron_automated_helper {
         $dir =      $config->backup_auto_destination;
 
         if ($keep == 0) {
-            // means keep all backup files
+            // Means keep all backup files.
             return true;
         }
 
@@ -528,7 +559,7 @@ abstract class backup_cron_automated_helper {
             $dir = null;
         }
 
-        // Clean up excess backups in the course backup filearea
+        // Clean up excess backups in the course backup filearea.
         if ($storage == 0 || $storage == 2) {
             $fs = get_file_storage();
             $context = context_course::instance($course->id);
@@ -536,7 +567,7 @@ abstract class backup_cron_automated_helper {
             $filearea = 'automated';
             $itemid = 0;
             $files = array();
-            // Store all the matching files into timemodified => stored_file array
+            // Store all the matching files into timemodified => stored_file array.
             foreach ($fs->get_area_files($context->id, $component, $filearea, $itemid) as $file) {
                 if (strpos($file->get_filename(), $backupword) !== 0) {
                     continue;
@@ -544,11 +575,10 @@ abstract class backup_cron_automated_helper {
                 $files[$file->get_timemodified()] = $file;
             }
             if (count($files) <= $keep) {
-                // There are less matching files than the desired number to keep
-                // do there is nothing to clean up.
+                // There are less matching files than the desired number to keep there is nothing to clean up.
                 return 0;
             }
-            // Sort by keys descending (newer to older filemodified)
+            // Sort by keys descending (newer to older filemodified).
             krsort($files);
             $remove = array_splice($files, $keep);
             foreach ($remove as $file) {
@@ -557,26 +587,42 @@ abstract class backup_cron_automated_helper {
             //mtrace('Removed '.count($remove).' old backup file(s) from the automated filearea');
         }
 
-        // Clean up excess backups in the specified external directory
+        // Clean up excess backups in the specified external directory.
         if (!empty($dir) && ($storage == 1 || $storage == 2)) {
             // Calculate backup filename regex, ignoring the date/time/info parts that can be
-            // variable, depending of languages, formats and automated backup settings
-            $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$course->id . '-';
+            // variable, depending of languages, formats and automated backup settings.
+            $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-';
             $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
 
-            // Store all the matching files into fullpath => timemodified array
+            // Store all the matching files into filename => timemodified array.
             $files = array();
             foreach (scandir($dir) as $file) {
-                if (preg_match($regex, $file, $matches)) {
-                    $files[$file] = filemtime($dir . '/' . $file);
+                // Skip files not matching the naming convention.
+                if (!preg_match($regex, $file, $matches)) {
+                    continue;
+                }
+
+                // Read the information contained in the backup itself.
+                try {
+                    $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
+                } catch (backup_helper_exception $e) {
+                    mtrace('Error: ' . $file . ' does not appear to be a valid backup (' . $e->errorcode . ')');
+                    continue;
+                }
+
+                // Make sure this backup concerns the course and site we are looking for.
+                if ($bcinfo->format === backup::FORMAT_MOODLE &&
+                        $bcinfo->type === backup::TYPE_1COURSE &&
+                        $bcinfo->original_course_id == $course->id &&
+                        backup_general_helper::backup_is_samesite($bcinfo)) {
+                    $files[$file] = $bcinfo->backup_date;
                 }
             }
             if (count($files) <= $keep) {
-                // There are less matching files than the desired number to keep
-                // do there is nothing to clean up.
+                // There are less matching files than the desired number to keep there is nothing to clean up.
                 return 0;
             }
-            // Sort by values descending (newer to older filemodified)
+            // Sort by values descending (newer to older filemodified).
             arsort($files);
             $remove = array_splice($files, $keep);
             foreach (array_keys($remove) as $file) {
index df3c8d9..798c6b6 100644 (file)
@@ -22,6 +22,8 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Non instantiable helper class providing general helper methods for backup/restore
  *
@@ -216,6 +218,38 @@ abstract class backup_general_helper extends backup_helper {
         return $info;
     }
 
+    /**
+     * Load and format all the needed information from a backup file.
+     *
+     * This will only extract the moodle_backup.xml file from an MBZ
+     * file and then call {@link self::get_backup_information()}.
+     *
+     * @param string $filepath absolute path to the MBZ file.
+     * @return stdClass containing information.
+     * @since 2.4
+     */
+    public static function get_backup_information_from_mbz($filepath) {
+        global $CFG;
+        if (!is_readable($filepath)) {
+            throw new backup_helper_exception('missing_moodle_backup_file', $filepath);
+        }
+
+        // Extract moodle_backup.xml.
+        $tmpname = 'info_from_mbz_' . time() . '_' . random_string(4);
+        $tmpdir = $CFG->tempdir . '/backup/' . $tmpname;
+        $fp = get_file_packer('application/vnd.moodle.backup');
+        $extracted = $fp->extract_to_pathname($filepath, $tmpdir, array('moodle_backup.xml'));
+        $moodlefile =  $tmpdir . '/' . 'moodle_backup.xml';
+        if (!$extracted || !is_readable($moodlefile)) {
+            throw new backup_helper_exception('missing_moodle_backup_xml_file', $moodlefile);
+        }
+
+        // Read the information and delete the temporary directory.
+        $info = self::get_backup_information($tmpname);
+        remove_dir($tmpdir);
+        return $info;
+    }
+
     /**
      * Given the information fetched from moodle_backup.xml file
      * decide if we are restoring in the same site the backup was
index b70e6c7..12a3259 100644 (file)
@@ -60,10 +60,12 @@ require_once($CFG->dirroot . '/backup/util/helper/backup_null_iterator.class.php
 require_once($CFG->dirroot . '/backup/util/helper/backup_array_iterator.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_anonymizer_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_file_manager.class.php');
+require_once($CFG->dirroot . '/backup/util/helper/restore_moodlexml_parser_processor.class.php'); // Required by backup_general_helper::get_backup_information().
 require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
 require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
 require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php');
 require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
+require_once($CFG->dirroot . '/backup/util/xml/parser/progressive_parser.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/base_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
index 075ab2a..641ef17 100644 (file)
@@ -140,7 +140,7 @@ class community_hub_search_form extends moodleform {
             $options = array();
             $firsthub = false;
             foreach ($hubs as $hub) {
-                if (key_exists('id', $hub)) {
+                if (array_key_exists('id', $hub)) {
                     $params = array('hubid' => $hub['id'],
                         'filetype' => HUB_HUBSCREENSHOT_FILE_TYPE);
                     $imgurl = new moodle_url(HUB_HUBDIRECTORYURL .
index 7c19c9e..a83e51f 100644 (file)
@@ -111,7 +111,7 @@ class block_course_overview_renderer extends plugin_renderer_base {
             } else {
                 $html .= $this->output->heading(html_writer::link(
                     new moodle_url('/auth/mnet/jump.php', array('hostid' => $course->hostid, 'wantsurl' => '/course/view.php?id='.$course->remoteid)),
-                    format_string($course->shortname, true, $course->id), $attributes) . ' (' . format_string($course->hostname) . ')', 2, 'title');
+                    format_string($course->shortname, true), $attributes) . ' (' . format_string($course->hostname) . ')', 2, 'title');
             }
             $html .= $this->output->box('', 'flush');
             $html .= html_writer::end_tag('div');
index 4eff68c..3c8a31b 100644 (file)
@@ -958,7 +958,7 @@ M.core_dock.genericblock.prototype = {
         placeholder.replace(this.Y.Node.getDOMNode(this.cachedcontentnode));
         this.cachedcontentnode = this.Y.one('#'+this.cachedcontentnode.get('id'));
 
-        var commands = this.cachedcontentnode.one('.title .commands');
+        var commands = dockitem.commands;
         if (commands) {
             commands.all('.hidepanelicon').remove();
             commands.all('.moveto').remove();
index d5610c0..7f37c48 100644 (file)
@@ -177,9 +177,10 @@ class block_navigation extends block_base {
             $trimlength = (int)$this->config->trimlength;
         }
 
-        // Initialise (only actually happens if it hasn't already been done yet
-        $this->page->navigation->initialise();
-        $navigation = clone($this->page->navigation);
+        // Get the navigation object or don't display the block if none provided.
+        if (!$navigation = $this->get_navigation()) {
+            return null;
+        }
         $expansionlimit = null;
         if (!empty($this->config->expansionlimit)) {
             $expansionlimit = $this->config->expansionlimit;
@@ -204,7 +205,7 @@ class block_navigation extends block_base {
         $options['linkcategories'] = (!empty($this->config->linkcategories) && $this->config->linkcategories == 'yes');
 
         // Grab the items to display
-        $renderer = $this->page->get_renderer('block_navigation');
+        $renderer = $this->page->get_renderer($this->blockname);
         $this->content = new stdClass();
         $this->content->text = $renderer->navigation_tree($navigation, $expansionlimit, $options);
 
@@ -214,6 +215,17 @@ class block_navigation extends block_base {
         return $this->content;
     }
 
+    /**
+     * Returns the navigation
+     *
+     * @return navigation_node The navigation object to display
+     */
+    protected function get_navigation() {
+        // Initialise (only actually happens if it hasn't already been done yet)
+        $this->page->navigation->initialise();
+        return clone($this->page->navigation);
+    }
+
     /**
      * Returns the attributes to set for this block
      *
index cbf1dac..2efe9a2 100644 (file)
@@ -31,7 +31,8 @@ class block_online_users extends block_base {
         if (isset($CFG->block_online_users_timetosee)) {
             $timetoshowusers = $CFG->block_online_users_timetosee * 60;
         }
-        $timefrom = 100 * floor((time()-$timetoshowusers) / 100); // Round to nearest 100 seconds for better query cache
+        $now = time();
+        $timefrom = 100 * floor(($now - $timetoshowusers) / 100); // Round to nearest 100 seconds for better query cache
 
         //Calculate if we are in separate groups
         $isseparategroups = ($this->page->course->groupmode == SEPARATEGROUPS
@@ -53,18 +54,23 @@ class block_online_users extends block_base {
         }
 
         $userfields = user_picture::fields('u', array('username'));
-
+        $params['now'] = $now;
+        $params['timefrom'] = $timefrom;
         if ($this->page->course->id == SITEID or $this->page->context->contextlevel < CONTEXT_COURSE) {  // Site-level
             $sql = "SELECT $userfields, MAX(u.lastaccess) AS lastaccess
                       FROM {user} u $groupmembers
-                     WHERE u.lastaccess > $timefrom
+                     WHERE u.lastaccess > :timefrom
+                           AND u.lastaccess <= :now
+                           AND u.deleted = 0
                            $groupselect
                   GROUP BY $userfields
                   ORDER BY lastaccess DESC ";
 
            $csql = "SELECT COUNT(u.id)
                       FROM {user} u $groupmembers
-                     WHERE u.lastaccess > $timefrom
+                     WHERE u.lastaccess > :timefrom
+                           AND u.lastaccess <= :now
+                           AND u.deleted = 0
                            $groupselect";
 
         } else {
@@ -77,9 +83,11 @@ class block_online_users extends block_base {
             $sql = "SELECT $userfields, MAX(ul.timeaccess) AS lastaccess
                       FROM {user_lastaccess} ul $groupmembers, {user} u
                       JOIN ($esqljoin) euj ON euj.id = u.id
-                     WHERE ul.timeaccess > $timefrom
+                     WHERE ul.timeaccess > :timefrom
                            AND u.id = ul.userid
                            AND ul.courseid = :courseid
+                           AND ul.timeaccess <= :now
+                           AND u.deleted = 0
                            $groupselect
                   GROUP BY $userfields
                   ORDER BY lastaccess DESC";
@@ -87,9 +95,11 @@ class block_online_users extends block_base {
            $csql = "SELECT COUNT(u.id)
                       FROM {user_lastaccess} ul $groupmembers, {user} u
                       JOIN ($esqljoin) euj ON euj.id = u.id
-                     WHERE ul.timeaccess > $timefrom
+                     WHERE ul.timeaccess > :timefrom
                            AND u.id = ul.userid
                            AND ul.courseid = :courseid
+                           AND ul.timeaccess <= :now
+                           AND u.deleted = 0
                            $groupselect";
 
             $params['courseid'] = $this->page->course->id;
@@ -138,7 +148,7 @@ class block_online_users extends block_base {
             }
             foreach ($users as $user) {
                 $this->content->text .= '<li class="listentry">';
-                $timeago = format_time(time() - $user->lastaccess); //bruno to calculate correctly on frontpage
+                $timeago = format_time($now - $user->lastaccess); //bruno to calculate correctly on frontpage
 
                 if (isguestuser($user)) {
                     $this->content->text .= '<div class="user">'.$OUTPUT->user_picture($user, array('size'=>16));
index 5d2c515..39a550e 100644 (file)
@@ -29,10 +29,10 @@ class block_site_main_menu extends block_list {
         require_once($CFG->dirroot.'/course/lib.php');
         $context = context_course::instance($course->id);
         $isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context);
-        $modinfo = get_fast_modinfo($course);
 
 /// extra fast view mode
         if (!$isediting) {
+            $modinfo = get_fast_modinfo($course);
             if (!empty($modinfo->sections[0])) {
                 $options = array('overflowdiv'=>true);
                 foreach($modinfo->sections[0] as $cmid) {
@@ -61,9 +61,9 @@ class block_site_main_menu extends block_list {
 
 /// slow & hacky editing mode
         $ismoving = ismoving($course->id);
-        $section  = get_course_section(0, $course->id);
-
-        get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
+        course_create_sections_if_missing($course, 0);
+        $modinfo = get_fast_modinfo($course);
+        $section = $modinfo->get_section_info(0);
 
         $groupbuttons = $course->groupmode;
         $groupbuttonslink = (!$course->groupmodeforce);
@@ -82,14 +82,13 @@ class block_site_main_menu extends block_list {
             $this->content->items[] = $USER->activitycopyname.'&nbsp;(<a href="'.$CFG->wwwroot.'/course/mod.php?cancelcopy=true&amp;sesskey='.sesskey().'">'.$strcancel.'</a>)';
         }
 
-        if (!empty($section->sequence)) {
-            $sectionmods = explode(',', $section->sequence);
+        if (!empty($modinfo->sections[0])) {
             $options = array('overflowdiv'=>true);
-            foreach ($sectionmods as $modnumber) {
-                if (empty($mods[$modnumber])) {
+            foreach ($modinfo->sections[0] as $modnumber) {
+                $mod = $modinfo->cms[$modnumber];
+                if (!$mod->uservisible) {
                     continue;
                 }
-                $mod = $mods[$modnumber];
                 if (!$ismoving) {
                     if ($groupbuttons) {
                         if (! $mod->groupmodelink = $groupbuttonslink) {
@@ -135,11 +134,7 @@ class block_site_main_menu extends block_list {
             $this->content->icons[] = '';
         }
 
-        if (!empty($modnames)) {
-            $this->content->footer = print_section_add_menus($course, 0, $modnames, true, true);
-        } else {
-            $this->content->footer = '';
-        }
+        $this->content->footer = print_section_add_menus($course, 0, null, true, true);
 
         return $this->content;
     }
index d9f98b0..b98f44f 100644 (file)
@@ -64,15 +64,8 @@ class block_social_activities extends block_list {
 
 /// slow & hacky editing mode
         $ismoving = ismoving($course->id);
-        $sections = get_all_sections($course->id);
-
-        if(!empty($sections) && isset($sections[0])) {
-            $section = $sections[0];
-        }
-
-        if (!empty($section)) {
-            get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
-        }
+        $modinfo = get_fast_modinfo($course);
+        $section = $modinfo->get_section_info(0);
 
         $groupbuttons = $course->groupmode;
         $groupbuttonslink = (!$course->groupmodeforce);
@@ -91,14 +84,13 @@ class block_social_activities extends block_list {
             $this->content->items[] = $USER->activitycopyname.'&nbsp;(<a href="'.$CFG->wwwroot.'/course/mod.php?cancelcopy=true&amp;sesskey='.sesskey().'">'.$strcancel.'</a>)';
         }
 
-        if (!empty($section) && !empty($section->sequence)) {
-            $sectionmods = explode(',', $section->sequence);
+        if (!empty($modinfo->sections[0])) {
             $options = array('overflowdiv'=>true);
-            foreach ($sectionmods as $modnumber) {
-                if (empty($mods[$modnumber])) {
+            foreach ($modinfo->sections[0] as $modnumber) {
+                $mod = $modinfo->cms[$modnumber];
+                if (!$mod->uservisible) {
                     continue;
                 }
-                $mod = $mods[$modnumber];
                 if (!$ismoving) {
                     if ($groupbuttons) {
                         if (! $mod->groupmodelink = $groupbuttonslink) {
@@ -145,11 +137,7 @@ class block_social_activities extends block_list {
             $this->content->icons[] = '';
         }
 
-        if ($modnames) {
-            $this->content->footer = print_section_add_menus($course, 0, $modnames, true, true);
-        } else {
-            $this->content->footer = '';
-        }
+        $this->content->footer = print_section_add_menus($course, 0, null, true, true);
 
         return $this->content;
     }
index 9a07fba..168faf1 100644 (file)
@@ -663,27 +663,42 @@ class blog_listing {
             $userid = optional_param('userid', null, PARAM_INT);
 
             if (empty($userid) || (!empty($userid) && $userid == $USER->id)) {
-                $addurl = new moodle_url("$CFG->wwwroot/blog/edit.php");
-                $urlparams = array('action' => 'add',
-                                   'userid' => $userid,
-                                   'courseid' => optional_param('courseid', null, PARAM_INT),
-                                   'groupid' => optional_param('groupid', null, PARAM_INT),
-                                   'modid' => optional_param('modid', null, PARAM_INT),
-                                   'tagid' => optional_param('tagid', null, PARAM_INT),
-                                   'tag' => optional_param('tag', null, PARAM_INT),
-                                   'search' => optional_param('search', null, PARAM_INT));
-
-                foreach ($urlparams as $var => $val) {
-                    if (empty($val)) {
-                        unset($urlparams[$var]);
+
+                $canaddentries = true;
+                if ($modid = optional_param('modid', null, PARAM_INT)) {
+                    if (!has_capability('moodle/blog:associatemodule', context_module::instance($modid))) {
+                        $canaddentries = false;
+                    }
+                }
+                if ($courseid = optional_param('courseid', null, PARAM_INT)) {
+                    if (!has_capability('moodle/blog:associatecourse', context_course::instance($courseid))) {
+                        $canaddentries = false;
                     }
                 }
-                $addurl->params($urlparams);
 
-                $addlink = '<div class="addbloglink">';
-                $addlink .= '<a href="'.$addurl->out().'">'. $blogheaders['stradd'].'</a>';
-                $addlink .= '</div>';
-                echo $addlink;
+                if ($canaddentries) {
+                    $addurl = new moodle_url("$CFG->wwwroot/blog/edit.php");
+                    $urlparams = array('action' => 'add',
+                                       'userid' => $userid,
+                                       'courseid' => $courseid,
+                                       'groupid' => optional_param('groupid', null, PARAM_INT),
+                                       'modid' => $modid,
+                                       'tagid' => optional_param('tagid', null, PARAM_INT),
+                                       'tag' => optional_param('tag', null, PARAM_INT),
+                                       'search' => optional_param('search', null, PARAM_INT));
+
+                    foreach ($urlparams as $var => $val) {
+                        if (empty($val)) {
+                            unset($urlparams[$var]);
+                        }
+                    }
+                    $addurl->params($urlparams);
+
+                    $addlink = '<div class="addbloglink">';
+                    $addlink .= '<a href="'.$addurl->out().'">'. $blogheaders['stradd'].'</a>';
+                    $addlink .= '</div>';
+                    echo $addlink;
+                }
             }
         }
 
diff --git a/cache/README.md b/cache/README.md
new file mode 100644 (file)
index 0000000..f5e5816
--- /dev/null
@@ -0,0 +1,202 @@
+Moodle Universal Cache / Cache API
+==================================
+
+Sample code snippets
+--------------------
+
+A definition:
+
+     $definitions = array(
+        'string' => array(                            // Required, unique to the component
+            'mode' => cache_store::MODE_APPLICATION,  // Required
+            'requireidentifiers' => array(            // Optional
+                'lang'
+            ),
+            'requiredataguarantee' => false,          // Optional
+            'requiremultipleidentifiers' => false,    // Optional
+            'requirelockingread' => false,            // Optional
+            'requirelockingwrite' => false,           // Optional
+            'maxsize' => null,                        // Optional
+            'overrideclass' => null,                  // Optional
+            'overrideclassfile' => null,              // Optional
+            'datasource' => null,                     // Optional
+            'datasourcefile' => null,                 // Optional
+            'persistent' => false,                    // Optional
+            'persistentmaxsize' => false,             // Optional
+            'ttl' => 0,                               // Optional
+            'mappingsonly' => false                   // Optional
+            'invalidationevents' => array(            // Optional
+                'contextmarkeddirty'
+            ),
+        )
+    );
+
+Getting something from a cache using the definition:
+
+    $cache = cache::make('core', 'string');
+    if (!$component = $cache->get('component')) {
+        // get returns false if its not there and can't be loaded.
+        $component = generate_data();
+        $cache->set($component);
+    }
+
+The same thing but using params:
+
+    $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'string');
+    if (!$component = $cache->get('component')) {
+        // get returns false if its not there and can't be loaded.
+        $component = generate_data();
+        $cache->set($component);
+    }
+
+If a data source had been specified in the definition, the following would be all that was needed.
+
+    $cache = cache::make('core', 'string');
+    $component = $cache->get('component');
+
+Cache API parts
+---------------
+
+There are several parts that make up the Cache API.
+
+### Loader
+The loader is central to the whole thing.
+It is used by the end developer to get an object that handles caching.
+90% of end developers will not need to know or use anything else in the cache API.
+In order to get a loader you must use one of two static methods, make or make_with_params.
+The loader has been kept as simple as possible, interaction is summarised by the cache_loader interface.
+Internally there is lots of magic going on. The important parts to know about are:
+* There are two ways to get a loader, the first with a definition (discussed below) the second with params. When params are used they are turned into an adhoc definition with default params.
+* A loader is passed three things when being constructed, a definition, a store, and another loader or datasource if there is either.
+* If a loader is the third arg then requests will be chained to provide redundancy.
+* If a data source is provided then requests for an item that is not cached will be passed to the data source and that will be expected to load the data. If it loads data, that data is stored in each store on its way back to the user.
+* There are three core loaders. One for each application, session and request.
+* A custom loader can be used. It will be provided by the definition (thus cannot be used with ad hoc definitions) and must override the appropriate core loader
+* The loader handles ttl (time to live) for stores that don't natively support ttl.
+* The application loader handles locking for stores that don't natively support locking.
+
+### Store
+The store is the bridge between the cache API and a cache solution.
+Cache store plugins exist within moodle/cache/store.
+The administrator of a site can configure multiple instances of each plugin, the configuration gets initialised as a store for the loader when required in code (during construction of the loader).
+The following points highlight things you should know about stores.
+* A cache_store interface is used to define the requirements of a store plugin.
+* The store plugin can inherit the cache_is_lockable interface to handle its own locking.
+* The store plugin can inherit the cache_is_key_aware interface to handle is own has checks.
+* Store plugins inform the cache API about the things they support. Features can be required by a definition.
+** Data guarantee - Data is guaranteed to exist in the cache once it is set there. It is never cleaned up to free space or because it has not been recently used.
+** Multiple identifiers - Rather than a single string key, the parts that make up the key are passed as an array.
+** Native TTL support - When required, the store supports native ttl and doesn't require the cache API to manage ttl of things given to the store.
+
+### Definition
+_Definitions were not a part of the previous proposal._
+Definitions are cache definitions. They will be located within a new file for each component/plugin at **db/caches.php**.
+They can be used to set all of the requirements of a cache instance and are used to ensure that a cache can only be interacted with in the same way no matter where it is being used.
+It also ensures that caches are easy to use, the config is stored in the definition and the developer using the cache does not need to know anything about its inner workings.
+When getting a loader you can either provide a definition name, or a set or params.
+* If you provide a definition name then the matching definition is found and used to construct a loader for you.
+* If you provide params then an ad hoc definition is created. It will have defaults and will not have any special requirements or options set.
+
+Definitions are designed to be used in situations where things are more than basic.
+
+The following settings are required for a definition:
+* name - Identifies the definition and must be unique.
+* mode - Application, session or request.
+
+The following optional settings can also be defined:
+* requireidentifiers - Any identifiers the definition requires. Must be provided when creating the loader.
+* requiredataguarantee - If set to true then only stores that support data guarantee will be used.
+* requiremultipleidentifiers - If set to true then only stores that support multiple identifiers will be used.
+* requirelockingread - If set to true a lock will be acquired for reading. Don't use this setting unless you have a REALLY good reason to.
+* requirelockingwrite - If set to true a lock will be acquired before writing to the cache. Avoid this unless necessary.
+* maxsize - This gives a cache an indication about the maximum items it should store. Cache stores don't have to use this, it is up to them to decide if its required.
+* overrideclass - If provided this class will be used for the loader. It must extend one of the core loader classes (based upon mode).
+* overrideclassfile - Included if required when using the overrideclass param.
+* datasource - If provided this class will be used as a data source for the definition. It must implement the cache_data_source interface.
+* datasourcefile - Included if required when using the datasource param.
+* persistent - If set to true the loader will be stored when first created and provided to subsequent requests. More on this later.
+* persistentmaxsize - If set to an int this will be the maximum number of items stored in the persistent cache.
+* ttl - Can be used to set a ttl value for data being set for this cache.
+* mappingsonly - This definition can only be used if there is a store mapping for it. More on this later.
+* invalidationevents - An array of events that should trigger this cache to invalidate.
+
+It's important to note that internally the definition is also aware of the component. This is picked up when the definition is read, based upon the location of the caches.php file.
+
+The persist option.
+As noted the persist option causes the loader generated for this definition to be stored when first created. Subsequent requests for this definition will be given the original loader instance.
+Data passed to or retrieved from the loader and its chained loaders gets cached by the instance.
+This option should be used when you know you will require the loader several times and perhaps in different areas of code.
+Because it caches key=>value data it avoids the need to re-fetch things from stores after the first request. Its good for performance, bad for memory.
+It should be used sparingly.
+
+The mappingsonly option.
+The administrator of a site can create mappings between stores and definitions. Allowing them to designate stores for specific definitions (caches).
+Setting this option to true means that the definition can only be used if a mapping has been made for it.
+Normally if no mappings exist then the default store for the definition mode is used.
+
+### Data source
+Data sources allow cache _misses_ (requests for a key that doesn't exist) to be handled and loaded internally.
+The loader gets used as the last resort if provided and means that code using the cache doesn't need to handle the situation that information isn't cached.
+They can be specified in a cache definition and must implement the cache_data_source interface.
+
+### How it all chains together.
+Consider the following:
+
+Basic request for information (no frills):
+
+    => Code calls get
+        => Loader handles get, passes the request to its store
+            <= Memcache doesn't have the data. sorry.
+        <= Loader returns the result.
+    |= Code couldn't get the data from the cache. It must generate it and then ask the loader to cache it.
+
+Advanced initial request for information not already cached (has chained stores and data source):
+
+    => Code calls get
+        => Loader handles get, passes the request to its store
+            => Memcache handles request, doesn't have it passes it to the chained store
+                => File (default store) doesn't have it requests it from the loader
+                    => Data source - makes required db calls, processes information
+                        ...database calls...
+                        ...processing and moulding...
+                    <= Data source returns the information
+                <= File caches the information on its way back through
+            <= Memcache caches the information on its way back through
+        <= Loader returns the data to the user.
+    |= Code the code now has the data.
+
+Subsequent request for information:
+
+    => Code calls get
+        => Loader handles get, passes the request to its store
+            <= Store returns the data
+        <= Loader returns the data
+    |= Code has the data
+
+Other internal magic you should be aware of
+-------------------------------------------
+The following should fill you in on a bit more of the behind-the-scenes stuff for the cache API.
+
+### Helper class
+There is a helper class called cache_helper which is abstract with static methods.
+This class handles much of the internal generation and initialisation requirements.
+In normal use this class will not be needed outside of the API (mostly internal use only)
+
+### Configuration
+There are two configuration classes cache_config and cache_config_writer.
+The reader class is used for every request, the writer is only used when modifying the configuration.
+Because the cache API is designed to cache database configuration and meta data it must be able to operate prior to database configuration being loaded.
+To get around this we store the configuration information in a file in the dataroot.
+The configuration file contains information on the configured store instances, definitions collected from definition files, and mappings.
+That information is stored and loaded in the same way we work with the lang string files.
+This means that we use the cache API as soon as it has been included.
+
+### Invalidation
+Cache information can be invalidated in two ways.
+1. pass a definition name and the keys to be invalidated (or none to invalidate the whole cache).
+2. pass an event and the keys to be invalidated.
+
+The first method is designed to be used when you have a single known definition you want to invalidate entries within.
+The second method is a lot more intensive for the system. There are defined invalidation events that definitions can "subscribe" to (through the definitions invalidationevents option).
+When you invalidate by event the cache API finds all of the definitions that subscribe to the event, it then loads the stores for each of those definitions and purges the keys from each store.
+This is obviously a recursive, and therefore, intense process.
\ No newline at end of file
diff --git a/cache/admin.php b/cache/admin.php
new file mode 100644 (file)
index 0000000..ce26856
--- /dev/null
@@ -0,0 +1,204 @@
+<?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/>.
+
+/**
+ * The administration and management interface for the cache setup and configuration.
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once($CFG->dirroot.'/lib/adminlib.php');
+require_once($CFG->dirroot.'/cache/locallib.php');
+require_once($CFG->dirroot.'/cache/forms.php');
+
+$action = optional_param('action', null, PARAM_ALPHA);
+
+admin_externalpage_setup('cacheconfig');
+$context = context_system::instance();
+
+$stores = cache_administration_helper::get_store_instance_summaries();
+$plugins = cache_administration_helper::get_store_plugin_summaries();
+$definitions = cache_administration_helper::get_definition_summaries();
+$defaultmodestores = cache_administration_helper::get_default_mode_stores();
+$locks = cache_administration_helper::get_lock_summaries();
+
+$title = new lang_string('cacheadmin', 'cache');
+$mform = null;
+$notification = null;
+$notifysuccess = true;
+
+if (!empty($action) && confirm_sesskey()) {
+    switch ($action) {
+        case 'rescandefinitions' : // Rescan definitions.
+            cache_config_writer::update_definitions();
+            redirect($PAGE->url);
+            break;
+        case 'addstore' : // Add the requested store.
+            $plugin = required_param('plugin', PARAM_PLUGIN);
+            if (!$plugins[$plugin]['canaddinstance']) {
+                print_error('ex_unmetstorerequirements', 'cache');
+            }
+            $mform = cache_administration_helper::get_add_store_form($plugin);
+            $title = get_string('addstore', 'cache', $plugins[$plugin]['name']);
+            if ($mform->is_cancelled()) {
+                rediect($PAGE->url);
+            } else if ($data = $mform->get_data()) {
+                $config = cache_administration_helper::get_store_configuration_from_data($data);
+                $writer = cache_config_writer::instance();
+                unset($config['lock']);
+                foreach ($writer->get_locks() as $lock => $lockconfig) {
+                    if ($lock == $data->lock) {
+                        $config['lock'] = $data->lock;
+                    }
+                }
+                $writer->add_store_instance($data->name, $data->plugin, $config);
+                redirect($PAGE->url, get_string('addstoresuccess', 'cache', $plugins[$plugin]['name']), 5);
+            }
+            break;
+        case 'editstore' : // Edit the requested store.
+            $plugin = required_param('plugin', PARAM_PLUGIN);
+            $store = required_param('store', PARAM_TEXT);
+            $mform = cache_administration_helper::get_edit_store_form($plugin, $store);
+            $title = get_string('addstore', 'cache', $plugins[$plugin]['name']);
+            if ($mform->is_cancelled()) {
+                rediect($PAGE->url);
+            } else if ($data = $mform->get_data()) {
+                $config = cache_administration_helper::get_store_configuration_from_data($data);
+                $writer = cache_config_writer::instance();
+                unset($config['lock']);
+                foreach ($writer->get_locks() as $lock => $lockconfig) {
+                    if ($lock == $data->lock) {
+                        $config['lock'] = $data->lock;
+                    }
+                }
+                $writer->edit_store_instance($data->name, $data->plugin, $config);
+                redirect($PAGE->url, get_string('editstoresuccess', 'cache', $plugins[$plugin]['name']), 5);
+            }
+            break;
+        case 'deletestore' : // Delete a given store.
+            $store = required_param('store', PARAM_TEXT);
+            $confirm = optional_param('confirm', false, PARAM_BOOL);
+
+            if (!array_key_exists($store, $stores)) {
+                $notifysuccess = false;
+                $notification = get_string('invalidstore');
+            } else if ($stores[$store]['mappings'] > 0) {
+                $notifysuccess = false;
+                $notification = get_string('deletestorehasmappings');
+            }
+
+            if ($notifysuccess) {
+                if (!$confirm) {
+                    $title = get_string('confirmstoredeletion', 'cache');
+                    $params = array('store' => $store, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey());
+                    $url = new moodle_url($PAGE->url, $params);
+                    $button = new single_button($url, get_string('deletestore', 'cache'));
+
+                    $PAGE->set_title($title);
+                    $PAGE->set_heading($SITE->fullname);
+                    echo $OUTPUT->header();
+                    echo $OUTPUT->heading($title);
+                    $confirmation = get_string('deletestoreconfirmation', 'cache', $stores[$store]['name']);
+                    echo $OUTPUT->confirm($confirmation, $button, $PAGE->url);
+                    echo $OUTPUT->footer();
+                    exit;
+                } else {
+                    $writer = cache_config_writer::instance();
+                    $writer->delete_store_instance($store);
+                    redirect($PAGE->url, get_string('deletestoresuccess', 'cache'), 5);
+                }
+            }
+            break;
+        case 'editdefinitionmapping' : // Edit definition mappings.
+            $definition = required_param('definition', PARAM_TEXT);
+            $title = get_string('editdefinitionmappings', 'cache', $definition);
+            $mform = new cache_definition_mappings_form($PAGE->url, array('definition' => $definition));
+            if ($mform->is_cancelled()) {
+                redirect($PAGE->url);
+            } else if ($data = $mform->get_data()) {
+                $writer = cache_config_writer::instance();
+                $mappings = array();
+                foreach ($data->mappings as $mapping) {
+                    if (!empty($mapping)) {
+                        $mappings[] = $mapping;
+                    }
+                }
+                $writer->set_definition_mappings($definition, $mappings);
+                redirect($PAGE->url);
+            }
+            break;
+        case 'editmodemappings': // Edit default mode mappings.
+            $mform = new cache_mode_mappings_form(null, $stores);
+            $mform->set_data(array(
+                'mode_'.cache_store::MODE_APPLICATION => key($defaultmodestores[cache_store::MODE_APPLICATION]),
+                'mode_'.cache_store::MODE_SESSION => key($defaultmodestores[cache_store::MODE_SESSION]),
+                'mode_'.cache_store::MODE_REQUEST => key($defaultmodestores[cache_store::MODE_REQUEST]),
+            ));
+            if ($mform->is_cancelled()) {
+                redirect($PAGE->url);
+            } else if ($data = $mform->get_data()) {
+                $mappings = array(
+                    cache_store::MODE_APPLICATION => array($data->{'mode_'.cache_store::MODE_APPLICATION}),
+                    cache_store::MODE_SESSION => array($data->{'mode_'.cache_store::MODE_SESSION}),
+                    cache_store::MODE_REQUEST => array($data->{'mode_'.cache_store::MODE_REQUEST}),
+                );
+                $writer = cache_config_writer::instance();
+                $writer->set_mode_mappings($mappings);
+                redirect($PAGE->url);
+            }
+            break;
+
+        case 'purge': // Purge a store cache.
+            $store = required_param('store', PARAM_TEXT);
+            cache_helper::purge_store($store);
+            redirect($PAGE->url, get_string('purgestoresuccess', 'cache'), 5);
+            break;
+    }
+}
+
+$PAGE->set_title($title);
+$PAGE->set_heading($SITE->fullname);
+$renderer = $PAGE->get_renderer('core_cache');
+
+echo $renderer->header();
+echo $renderer->heading($title);
+
+if (!is_null($notification)) {
+    echo $renderer->notification($notification, ($notifysuccess)?'notifysuccess' : 'notifyproblem');
+}
+
+if ($mform instanceof moodleform) {
+    $mform->display();
+} else {
+    echo $renderer->store_plugin_summaries($plugins);
+    echo $renderer->store_instance_summariers($stores, $plugins);
+    echo $renderer->definition_summaries($definitions, cache_administration_helper::get_definition_actions($context));
+    echo $renderer->lock_summaries($locks);
+
+    $applicationstore = join(', ', $defaultmodestores[cache_store::MODE_APPLICATION]);
+    $sessionstore = join(', ', $defaultmodestores[cache_store::MODE_SESSION]);
+    $requeststore = join(', ', $defaultmodestores[cache_store::MODE_REQUEST]);
+    $editurl = new moodle_url('/cache/admin.php', array('action' => 'editmodemappings', 'sesskey' => sesskey()));
+    echo $renderer->mode_mappings($applicationstore, $sessionstore, $requeststore, $editurl);
+}
+
+echo $renderer->footer();
diff --git a/cache/classes/config.php b/cache/classes/config.php
new file mode 100644 (file)
index 0000000..682ef18
--- /dev/null
@@ -0,0 +1,472 @@
+<?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/>.
+
+/**
+ * Cache configuration reader
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Cache configuration reader.
+ *
+ * This class is used to interact with the cache's configuration.
+ * The configuration is stored in the Moodle data directory.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cache_config {
+
+    /**
+     * The configured stores
+     * @var array
+     */
+    protected $configstores = array();
+
+    /**
+     * The configured mode mappings
+     * @var array
+     */
+    protected $configmodemappings = array();
+
+    /**
+     * The configured definitions as picked up from cache.php files
+     * @var array
+     */
+    protected $configdefinitions = array();
+
+    /**
+     * The definition mappings that have been configured.
+     * @var array
+     */
+    protected $configdefinitionmappings = array();
+
+    /**
+     * An array of configured cache lock instances.
+     * @var array
+     */
+    protected $configlocks = array();
+
+    /**
+     * Please use cache_config::instance to get an instance of the cache config that is ready to be used.
+     */
+    public function __construct() {
+        // Nothing to do here but look pretty.
+    }
+
+    /**
+     * Gets an instance of the cache_configuration class.
+     *
+     * @return cache_config
+     */
+    public static function instance() {
+        $factory = cache_factory::instance();
+        return $factory->create_config_instance();
+    }
+
+    /**
+     * Checks if the configuration file exists.
+     *
+     * @return bool True if it exists
+     */
+    public static function config_file_exists() {
+        // Allow for late static binding.
+        return file_exists(self::get_config_file_path());
+    }
+
+    /**
+     * Returns the expected path to the configuration file.
+     *
+     * @return string The absolute path
+     */
+    protected static function get_config_file_path() {
+        global $CFG;
+        if (!empty($CFG->altcacheconfigpath)) {
+            $path = $CFG->altcacheconfigpath;
+            if (is_dir($path) && is_writable($path)) {
+                // Its a writable directory, thats fine.
+                return $path.'/cacheconfig.php';
+            } else if (is_writable(dirname($path)) && (!file_exists($path) || is_writable($path))) {
+                // Its a file, either it doesn't exist and the directory is writable or the file exists and is writable.
+                return $path;
+            }
+        }
+        // Return the default location within dataroot.
+        return $CFG->dataroot.'/muc/config.php';
+    }
+
+    /**
+     * Loads the configuration file and parses its contents into the expected structure.
+     *
+     * @return boolean
+     */
+    public function load() {
+        global $CFG;
+
+        $configuration = $this->include_configuration();
+
+        $this->configstores = array();
+        $this->configdefinitions = array();
+        $this->configlocks = array();
+        $this->configmodemappings = array();
+        $this->configdefinitionmappings = array();
+        $this->configlockmappings = array();
+
+        // Filter the lock instances.
+        $defaultlock = null;
+        foreach ($configuration['locks'] as $conf) {
+            if (!is_array($conf)) {
+                // Something is very wrong here.
+                continue;
+            }
+            if (!array_key_exists('name', $conf)) {
+                // Not a valid definition configuration.
+                continue;
+            }
+            $name = $conf['name'];
+            if (array_key_exists($name, $this->configlocks)) {
+                debugging('Duplicate cache lock detected. This should never happen.', DEBUG_DEVELOPER);
+                continue;
+            }
+            $conf['default'] = (!empty($conf['default']));
+            if ($defaultlock === null || $conf['default']) {
+                $defaultlock = $name;
+            }
+            $this->configlocks[$name] = $conf;
+        }
+
+        // Filter the stores.
+        $availableplugins = cache_helper::early_get_cache_plugins();
+        foreach ($configuration['stores'] as $store) {
+            if (!is_array($store) || !array_key_exists('name', $store) || !array_key_exists('plugin', $store)) {
+                // Not a valid instance configuration.
+                debugging('Invalid cache store in config. Missing name or plugin.', DEBUG_DEVELOPER);
+                continue;
+            }
+            $plugin = $store['plugin'];
+            $class = 'cachestore_'.$plugin;
+            $exists = array_key_exists($plugin, $availableplugins);
+            if (!$exists) {
+                // Not a valid plugin, or has been uninstalled, just skip it an carry on.
+                debugging('Invalid cache store in config. Not an available plugin.', DEBUG_DEVELOPER);
+                continue;
+            }
+            $file = $CFG->dirroot.'/cache/stores/'.$plugin.'/lib.php';
+            if (!class_exists($class) && file_exists($file)) {
+                require_once($file);
+            }
+            if (!class_exists($class)) {
+                continue;
+            }
+            if (!array_key_exists('cache_store', class_implements($class))) {
+                continue;
+            }
+            if (!array_key_exists('configuration', $store) || !is_array($store['configuration'])) {
+                $store['configuration'] = array();
+            }
+            $store['class'] = $class;
+            $store['default'] = !empty($store['default']);
+            if (!array_key_exists('lock', $store) || !array_key_exists($store['lock'], $this->configlocks)) {
+                $store['lock'] = $defaultlock;
+            }
+
+            $this->configstores[$store['name']] = $store;
+        }
+
+        // Filter the definitions.
+        foreach ($configuration['definitions'] as $id => $conf) {
+            if (!is_array($conf)) {
+                // Something is very wrong here.
+                continue;
+            }
+            if (!array_key_exists('mode', $conf) || !array_key_exists('component', $conf) || !array_key_exists('area', $conf)) {
+                // Not a valid definition configuration.
+                continue;
+            }
+            if (array_key_exists($id, $this->configdefinitions)) {
+                debugging('Duplicate cache definition detected. This should never happen.', DEBUG_DEVELOPER);
+                continue;
+            }
+            $conf['mode'] = (int)$conf['mode'];
+            if ($conf['mode'] < cache_store::MODE_APPLICATION || $conf['mode'] > cache_store::MODE_REQUEST) {
+                // Invalid cache mode used for the definition.
+                continue;
+            }
+            $this->configdefinitions[$id] = $conf;
+        }
+
+        // Filter the mode mappings.
+        foreach ($configuration['modemappings'] as $mapping) {
+            if (!is_array($mapping) || !array_key_exists('mode', $mapping) || !array_key_exists('store', $mapping)) {
+                // Not a valid mapping configuration.
+                debugging('A cache mode mapping entry is invalid.', DEBUG_DEVELOPER);
+                continue;
+            }
+            if (!array_key_exists($mapping['store'], $this->configstores)) {
+                // Mapped array instance doesn't exist.
+                debugging('A cache mode mapping exists for a mode or store that does not exist.', DEBUG_DEVELOPER);
+                continue;
+            }
+            $mapping['mode'] = (int)$mapping['mode'];
+            if ($mapping['mode'] < 0 || $mapping['mode'] > 4) {
+                // Invalid cache type used for the mapping.
+                continue;
+            }
+            if (!array_key_exists('sort', $mapping)) {
+                $mapping['sort'] = 0;
+            }
+            $this->configmodemappings[] = $mapping;
+        }
+
+        // Filter the definition mappings.
+        foreach ($configuration['definitionmappings'] as $mapping) {
+            if (!is_array($mapping) || !array_key_exists('definition', $mapping) || !array_key_exists('store', $mapping)) {
+                // Not a valid mapping configuration.
+                continue;
+            }
+            if (!array_key_exists($mapping['store'], $this->configstores)) {
+                // Mapped array instance doesn't exist.
+                continue;
+            }
+            if (!array_key_exists($mapping['definition'], $this->configdefinitions)) {
+                // Mapped array instance doesn't exist.
+                continue;
+            }
+            if (!array_key_exists('sort', $mapping)) {
+                $mapping['sort'] = 0;
+            }
+            $this->configdefinitionmappings[] = $mapping;
+        }
+
+        usort($this->configmodemappings, array($this, 'sort_mappings'));
+        usort($this->configdefinitionmappings, array($this, 'sort_mappings'));
+
+        return true;
+    }
+
+    /**
+     * Includes the configuration file and makes sure it contains the expected bits.
+     *
+     * You need to ensure that the config file exists before this is called.
+     *
+     * @return array
+     * @throws cache_exception
+     */
+    protected function include_configuration() {
+        $configuration = array();
+        $cachefile = self::get_config_file_path();
+
+        if (!file_exists($cachefile)) {
+            throw new cache_exception('Default cache config could not be found. It should have already been created by now.');
+        }
+        include($cachefile);
+        if (!is_array($configuration)) {
+            throw new cache_exception('Invalid cache configuration file');
+        }
+        if (!array_key_exists('stores', $configuration) || !is_array($configuration['stores'])) {
+            $configuration['stores'] = array();
+        }
+        if (!array_key_exists('modemappings', $configuration) || !is_array($configuration['modemappings'])) {
+            $configuration['modemappings'] = array();
+        }
+        if (!array_key_exists('definitions', $configuration) || !is_array($configuration['definitions'])) {
+            $configuration['definitions'] = array();
+        }
+        if (!array_key_exists('definitionmappings', $configuration) || !is_array($configuration['definitionmappings'])) {
+            $configuration['definitionmappings'] = array();
+        }
+        if (!array_key_exists('locks', $configuration) || !is_array($configuration['locks'])) {
+            $configuration['locks'] = array();
+        }
+
+        return $configuration;
+    }
+
+    /**
+     * Used to sort cache config arrays based upon a sort key.
+     *
+     * Highest number at the top.
+     *
+     * @param array $a
+     * @param array $b
+     * @return int
+     */
+    protected function sort_mappings(array $a, array $b) {
+        if ($a['sort'] == $b['sort']) {
+            return 0;
+        }
+        return ($a['sort'] < $b['sort']) ? 1 : -1;
+    }
+
+    /**
+     * Gets a definition from the config given its name.
+     *
+     * @param string $id
+     * @return bool
+     */
+    public function get_definition_by_id($id) {
+        if (array_key_exists($id, $this->configdefinitions)) {
+            return $this->configdefinitions[$id];
+        }
+        return false;
+    }
+
+    /**
+     * Returns all the known definitions.
+     *
+     * @return array
+     */
+    public function get_definitions() {
+        return $this->configdefinitions;
+    }
+
+    /**
+     * Returns all of the stores that are suitable for the given mode and requirements.
+     *
+     * @param int $mode One of cache_store::MODE_*
+     * @param int $requirements The requirements of the cache as a binary flag
+     * @return array An array of suitable stores.
+     */
+    public function get_stores($mode, $requirements = 0) {
+        $stores = array();
+        foreach ($this->configstores as $name => $store) {
+            // If the mode is supported and all of the requirements are provided features.
+            if (($store['modes'] & $mode) && ($store['features'] & $requirements) === $requirements) {
+                $stores[$name] = $store;
+            }
+        }
+        return $stores;
+    }
+
+    /**
+     * Gets all of the stores that are to be used for the given definition.
+     *
+     * @param cache_definition $definition
+     * @return array
+     */
+    public function get_stores_for_definition(cache_definition $definition) {
+        // Check if MUC has been disabled.
+        if (defined('NO_CACHE_STORES') && NO_CACHE_STORES !== false) {
+            // Yip its been disabled.
+            // To facilitate this we are going to always return an empty array of stores to use.
+            // This will force all cache instances to use the cachestore_dummy.
+            // MUC will still be used essentially so that code using it will still continue to function but because no cache stores
+            // are being used interaction with MUC will be purely based around a static var.
+            return array();
+        }
+
+        $availablestores = $this->get_stores($definition->get_mode(), $definition->get_requirements_bin());
+        $stores = array();
+        $id = $definition->get_id();
+
+        // Now get any mappings and give them priority.
+        foreach ($this->configdefinitionmappings as $mapping) {
+            if ($mapping['definition'] !== $id) {
+                continue;
+            }
+            $storename = $mapping['store'];
+            if (!array_key_exists($storename, $availablestores)) {
+                continue;
+            }
+            if (array_key_exists($storename, $stores)) {
+                $store = $stores[$storename];
+                unset($stores[$storename]);
+                $stores[$storename] = $store;
+            } else {
+                $stores[$storename] = $availablestores[$storename];
+            }
+        }
+
+        if (empty($stores) && !$definition->is_for_mappings_only()) {
+            $mode = $definition->get_mode();
+            // Load the default stores.
+            foreach ($this->configmodemappings as $mapping) {
+                if ($mapping['mode'] === $mode && array_key_exists($mapping['store'], $availablestores)) {
+                    $store = $availablestores[$mapping['store']];
+                    if (empty($store['mappingsonly'])) {
+                        $stores[$mapping['store']] = $store;
+                    }
+                }
+            }
+        }
+
+        return $stores;
+    }
+
+    /**
+     * Returns all of the configured stores
+     * @return array
+     */
+    public function get_all_stores() {
+        return $this->configstores;
+    }
+
+    /**
+     * Returns all of the configured mode mappings
+     * @return array
+     */
+    public function get_mode_mappings() {
+        return $this->configmodemappings;
+    }
+
+    /**
+     * Returns all of the known definition mappings.
+     * @return array
+     */
+    public function get_definition_mappings() {
+        return $this->configdefinitionmappings;
+    }
+
+    /**
+     * Returns an array of the configured locks.
+     * @return array Array of name => config
+     */
+    public function get_locks() {
+        return $this->configlocks;
+    }
+
+    /**
+     * Returns the lock store configuration to use with a given store.
+     * @param string $storename
+     * @return array
+     * @throws cache_exception
+     */
+    public function get_lock_for_store($storename) {
+        if (array_key_exists($storename, $this->configstores)) {
+            if (array_key_exists($this->configstores[$storename]['lock'], $this->configlocks)) {
+                $lock = $this->configstores[$storename]['lock'];
+                return $this->configlocks[$lock];
+            }
+        }
+        foreach ($this->configlocks as $lockconf) {
+            if (!empty($lockconf['default'])) {
+                return $lockconf;
+            }
+        }
+        throw new cache_exception('ex_nodefaultlock');
+    }
+}
\ No newline at end of file
diff --git a/cache/classes/definition.php b/cache/classes/definition.php
new file mode 100644 (file)
index 0000000..a4196aa
--- /dev/null
@@ -0,0 +1,715 @@
+<?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/>.
+
+/**
+ * Cache definition class
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The cache definition class.
+ *
+ * Cache definitions need to be defined in db/caches.php files.
+ * They can be constructed with the following options.
+ *
+ * Required settings:
+ *     + mode
+ *          [int] Sets the mode for the definition. Must be one of cache_store::MODE_*
+ *
+ * Optional settings:
+ *     + requireidentifiers
+ *          [array] An array of identifiers that must be provided to the cache when it is created.
+ *     + requiredataguarantee
+ *          [bool] If set to true then only stores that can guarantee data will remain available once set will be used.
+ *     + requiremultipleidentifiers
+ *          [bool] If set to true then only stores that support multiple identifiers will be used.
+ *     + requirelockingread
+ *          [bool] If set to true then a lock will be gained before reading from the cache store. It is recommended not to use
+ *          this setting unless 100% absolutely positively required. Remember 99.9% of caches will NOT need this setting.
+ *          This setting will only be used for application caches presently.
+ *     + requirelockingwrite
+ *          [bool] If set to true then a lock will be gained before writing to the cache store. As above this is not recommended
+ *          unless truly needed. Please think about the order of your code and deal with race conditions there first.
+ *          This setting will only be used for application caches presently.
+ *     + maxsize
+ *          [int] If set this will be used as the maximum number of entries within the cache store for this definition.
+ *          Its important to note that cache stores don't actually have to acknowledge this setting or maintain it as a hard limit.
+ *     + overrideclass
+ *          [string] A class to use as the loader for this cache. This is an advanced setting and will allow the developer of the
+ *          definition to take 100% control of the caching solution.
+ *          Any class used here must inherit the cache_loader interface and must extend default cache loader for the mode they are
+ *          using.
+ *     + overrideclassfile
+ *          [string] Suplements the above setting indicated the file containing the class to be used. This file is included when
+ *          required.
+ *     + datasource
+ *          [string] A class to use as the data loader for this definition.
+ *          Any class used here must inherit the cache_data_loader interface.
+ *     + datasourcefile
+ *          [string] Suplements the above setting indicated the file containing the class to be used. This file is included when
+ *          required.
+ *     + persistent
+ *          [bool] This setting does two important things. First it tells the cache API to only instantiate the cache structure for
+ *          this definition once, further requests will be given the original instance.
+ *          Second the cache loader will keep an array of the items set and retrieved to the cache during the request.
+ *          This has several advantages including better performance without needing to start passing the cache instance between
+ *          function calls, the downside is that the cache instance + the items used stay within memory.
+ *          Consider using this setting when you know that there are going to be many calls to the cache for the same information
+ *          or when you are converting existing code to the cache and need to access the cache within functions but don't want
+ *          to add it as an argument to the function.
+ *     + persistentmaxsize
+ *          [int] This supplements the above setting by limiting the number of items in the caches persistent array of items.
+ *          Tweaking this setting lower will allow you to minimise the memory implications above while hopefully still managing to
+ *          offset calls to the cache store.
+ *     + ttl
+ *          [int] A time to live for the data (in seconds). It is strongly recommended that you don't make use of this and
+ *          instead try to create an event driven invalidation system.
+ *          Not all cache stores will support this natively and there are undesired performance impacts if the cache store does not.
+ *     + mappingsonly
+ *          [bool] If set to true only the mapped cache store(s) will be used and the default mode store will not. This is a super
+ *          advanced setting and should not be used unless absolutely required. It allows you to avoid the default stores for one
+ *          reason or another.
+ *     + invalidationevents
+ *          [array] An array of events that should cause this cache to invalidate some or all of the items within it.
+ *
+ * For examples take a look at lib/db/caches.php
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cache_definition {
+
+    /**
+     * The identifier for the definition
+     * @var string
+     */
+    protected $id;
+
+    /**
+     * The mode for the defintion. One of cache_store::MODE_*
+     * @var int
+     */
+    protected $mode;
+
+    /**
+     * The component this definition is associated with.
+     * @var string
+     */
+    protected $component;
+
+    /**
+     * The area this definition is associated with.
+     * @var string
+     */
+    protected $area;
+
+    /**
+     * An array of identifiers that must be provided when the definition is used to create a cache.
+     * @var array
+     */
+    protected $requireidentifiers = array();
+
+    /**
+     * If set to true then only stores that guarantee data may be used with this definition.
+     * @var bool
+     */
+    protected $requiredataguarantee = false;
+
+    /**
+     * If set to true then only stores that support multple identifiers may be used with this definition.
+     * @var bool
+     */
+    protected $requiremultipleidentifiers = false;
+
+    /**
+     * If set to true then we know that this definition requires the locking functionality.
+     * This gets set during construction based upon the settings requirelockingread and requirelockingwrite.
+     * @var bool
+     */
+    protected $requirelocking = false;
+
+    /**
+     * Set to true if this definition requires read locking.
+     * @var bool
+     */
+    protected $requirelockingread = false;
+
+    /**
+     * Gets set to true if this definition requires write locking.
+     * @var bool
+     */
+    protected $requirelockingwrite = false;
+
+    /**
+     * Sets the maximum number of items that can exist in the cache.
+     * Please note this isn't a hard limit, and doesn't need to be enforced by the caches. They can choose to do so optionally.
+     * @var int
+     */
+    protected $maxsize = null;
+
+    /**
+     * The class to use as the cache loader for this definition.
+     * @var string
+     */
+    protected $overrideclass = null;
+
+    /**
+     * The file in which the override class exists. This will be included if required.
+     * @var string Absolute path
+     */
+    protected $overrideclassfile = null;
+
+    /**
+     * The data source class to use with this definition.
+     * @var string
+     */
+    protected $datasource = null;
+
+    /**
+     * The file in which the data source class exists. This will be included if required.
+     * @var string
+     */
+    protected $datasourcefile = null;
+
+    /**
+     * The data source class aggregate to use. This is a super advanced setting.
+     * @var string
+     */
+    protected $datasourceaggregate = null;
+
+    /**
+     * Set to true if the definitions cache should be persistent
+     * @var bool
+     */
+    protected $persistent = false;
+
+    /**
+     * The persistent item array max size.
+     * @var int
+     */
+    protected $persistentmaxsize = false;
+
+    /**
+     * The TTL for data in this cache. Please don't use this, instead use event driven invalidation.
+     * @var int
+     */
+    protected $ttl = 0;
+
+    /**
+     * Set to true if this cache should only use mapped cache stores and not the default mode cache store.
+     * @var bool
+     */
+    protected $mappingsonly = false;
+
+    /**
+     * An array of events that should cause this cache to invalidate.
+     * @var array
+     */
+    protected $invalidationevents = array();
+
+    /**
+     * An array of identifiers provided to this cache when it was initialised.
+     * @var array
+     */
+    protected $identifiers = array();
+
+    /**
+     * Key prefix for use with single key cache stores
+     * @var string
+     */
+    protected $keyprefixsingle = null;
+
+    /**
+     * Key prefix to use with cache stores that support multi keys.
+     * @var array
+     */
+    protected $keyprefixmulti = null;
+
+    /**
+     * A hash identifier of this definition.
+     * @var string
+     */
+    protected $definitionhash = null;
+
+    /**
+     * Creates a cache definition given a definition from the cache configuration or from a caches.php file.
+     *
+     * @param string $id
+     * @param array $definition
+     * @param string $datasourceaggregate
+     * @return cache_definition
+     * @throws coding_exception
+     */
+    public static function load($id, array $definition, $datasourceaggregate = null) {
+        if (!array_key_exists('mode', $definition)) {
+            throw new coding_exception('You must provide a mode when creating a cache definition');
+        }
+        if (!array_key_exists('component', $definition)) {
+            throw new coding_exception('You must provide a mode when creating a cache definition');
+        }
+        if (!array_key_exists('area', $definition)) {
+            throw new coding_exception('You must provide a mode when creating a cache definition');
+        }
+        $mode = (int)$definition['mode'];
+        $component = (string)$definition['component'];
+        $area = (string)$definition['area'];
+
+        // Set the defaults.
+        $requireidentifiers = array();
+        $requiredataguarantee = false;
+        $requiremultipleidentifiers = false;
+        $requirelockingread = false;
+        $requirelockingwrite = false;
+        $maxsize = null;
+        $overrideclass = null;
+        $overrideclassfile = null;
+        $datasource = null;
+        $datasourcefile = null;
+        $persistent = false;
+        $persistentmaxsize = false;
+        $ttl = 0;
+        $mappingsonly = false;
+        $invalidationevents = array();
+
+        if (array_key_exists('requireidentifiers', $definition)) {
+            $requireidentifiers = (array)$definition['requireidentifiers'];
+        }
+        if (array_key_exists('requiredataguarantee', $definition)) {
+            $requiredataguarantee = (bool)$definition['requiredataguarantee'];
+        }
+        if (array_key_exists('requiremultipleidentifiers', $definition)) {
+            $requiremultipleidentifiers = (bool)$definition['requiremultipleidentifiers'];
+        }
+
+        if (array_key_exists('requirelockingread', $definition)) {
+            $requirelockingread = (bool)$definition['requirelockingread'];
+        }
+        if (array_key_exists('requirelockingwrite', $definition)) {
+            $requirelockingwrite = (bool)$definition['requirelockingwrite'];
+        }
+        $requirelocking = $requirelockingwrite || $requirelockingread;
+
+        if (array_key_exists('maxsize', $definition)) {
+            $maxsize = (int)$definition['maxsize'];
+        }
+
+        if (array_key_exists('overrideclass', $definition)) {
+            $overrideclass = $definition['overrideclass'];
+        }
+        if (array_key_exists('overrideclassfile', $definition)) {
+            $overrideclassfile = $definition['overrideclassfile'];
+        }
+
+        if (array_key_exists('datasource', $definition)) {
+            $datasource = $definition['datasource'];
+        }
+        if (array_key_exists('datasourcefile', $definition)) {
+            $datasourcefile = $definition['datasourcefile'];
+        }
+
+        if (array_key_exists('persistent', $definition)) {
+            $persistent = (bool)$definition['persistent'];
+        }
+        if (array_key_exists('persistentmaxsize', $definition)) {
+            $persistentmaxsize = (int)$definition['persistentmaxsize'];
+        }
+        if (array_key_exists('ttl', $definition)) {
+            $ttl = (int)$definition['ttl'];
+        }
+        if (array_key_exists('mappingsonly', $definition)) {
+            $mappingsonly = (bool)$definition['mappingsonly'];
+        }
+        if (array_key_exists('invalidationevents', $definition)) {
+            $invalidationevents = (array)$definition['invalidationevents'];
+        }
+
+        if (!is_null($overrideclass)) {
+            if (!is_null($overrideclassfile)) {
+                if (!file_exists($overrideclassfile)) {
+                    throw new coding_exception('The override class file does not exist.');
+                }
+                require_once($overrideclassfile);
+            }
+            if (!class_exists($overrideclass)) {
+                throw new coding_exception('The override class does not exist.');
+            }
+
+            // Make sure that the provided class extends the default class for the mode.
+            if (get_parent_class($overrideclass) !== cache_helper::get_class_for_mode($mode)) {
+                throw new coding_exception('The override class does not immediately extend the relevant cache class.');
+            }
+        }
+
+        if (!is_null($datasource)) {
+            if (!is_null($datasourcefile)) {
+                if (!file_exists($datasourcefile)) {
+                    throw new coding_exception('The override class file does not exist.');
+                }
+                require_once($datasourcefile);
+            }
+            if (!class_exists($datasource)) {
+                throw new coding_exception('The override class does not exist.');
+            }
+            if (!array_key_exists('cache_data_source', class_implements($datasource))) {
+                throw new coding_exception('Cache data source classes must implement the cache_data_source interface');
+            }
+        }
+
+        $cachedefinition = new cache_definition();
+        $cachedefinition->id = $id;
+        $cachedefinition->mode = $mode;
+        $cachedefinition->component = $component;
+        $cachedefinition->area = $area;
+        $cachedefinition->requireidentifiers = $requireidentifiers;
+        $cachedefinition->requiredataguarantee = $requiredataguarantee;
+        $cachedefinition->requiremultipleidentifiers = $requiremultipleidentifiers;
+        $cachedefinition->requirelocking = $requirelocking;
+        $cachedefinition->requirelockingread = $requirelockingread;
+        $cachedefinition->requirelockingwrite = $requirelockingwrite;
+        $cachedefinition->maxsize = $maxsize;
+        $cachedefinition->overrideclass = $overrideclass;
+        $cachedefinition->overrideclassfile = $overrideclassfile;
+        $cachedefinition->datasource = $datasource;
+        $cachedefinition->datasourcefile = $datasourcefile;
+        $cachedefinition->datasourceaggregate = $datasourceaggregate;
+        $cachedefinition->persistent = $persistent;
+        $cachedefinition->persistentmaxsize = $persistentmaxsize;
+        $cachedefinition->ttl = $ttl;
+        $cachedefinition->mappingsonly = $mappingsonly;
+        $cachedefinition->invalidationevents = $invalidationevents;
+
+        return $cachedefinition;
+    }
+
+    /**
+     * Creates an ah-hoc cache definition given the required params.
+     *
+     * Please note that when using an adhoc definition you cannot set any of the optional params.
+     * This is because we cannot guarantee consistent access and we don't want to mislead people into thinking that.
+     *
+     * @param int $mode One of cache_store::MODE_*
+     * @param string $component The component this definition relates to.
+     * @param string $area The area this definition relates to.
+     * @param string $overrideclass The class to use as the loader.
+     * @param bool $persistent If this cache should be persistent.
+     * @return cache_application|cache_session|cache_request
+     */
+    public static function load_adhoc($mode, $component, $area, $overrideclass = null, $persistent = false) {
+        $id = 'adhoc/'.$component.'_'.$area;
+        $definition = array(
+            'mode' => $mode,
+            'component' => $component,
+            'area' => $area,
+            'persistent' => $persistent
+        );
+        if (!is_null($overrideclass)) {
+            $definition['overrideclass'] = $overrideclass;
+        }
+        return self::load($id, $definition, null);
+    }
+
+    /**
+     * Returns the cache loader class that should be used for this definition.
+     * @return string
+     */
+    public function get_cache_class() {
+        if (!is_null($this->overrideclass)) {
+            return $this->overrideclass;
+        }
+        return cache_helper::get_class_for_mode($this->mode);
+    }
+
+    /**
+     * Returns the id of this definition.
+     * @return string
+     */
+    public function get_id() {
+        return $this->id;
+    }
+
+    /**
+     * Returns the name for this definition
+     * @return string
+     */
+    public function get_name() {
+        $identifier = 'cachedef_'.clean_param($this->area, PARAM_STRINGID);
+        $component = $this->component;
+        if ($component === 'core') {
+            $component = 'cache';
+        }
+        return new lang_string($identifier, $component);
+    }
+
+    /**
+     * Returns the mode of this definition
+     * @return int One more cache_store::MODE_
+     */
+    public function get_mode() {
+        return $this->mode;
+    }
+
+    /**
+     * Returns the area this definition is associated with.
+     * @return string
+     */
+    public function get_area() {
+        return $this->area;
+    }
+
+    /**
+     * Returns the component this definition is associated with.
+     * @return string
+     */
+    public function get_component() {
+        return $this->component;
+    }
+
+    /**
+     * Returns the identifiers that are being used for this definition.
+     * @return array
+     */
+    public function get_identifiers() {
+        return $this->identifiers;
+    }
+
+    /**
+     * Returns the ttl in seconds for this definition if there is one, or null if not.
+     * @return int|null
+     */
+    public function get_ttl() {
+        return $this->ttl;
+    }
+
+    /**
+     * Returns the maximum number of items allowed in this cache.
+     * @return int
+     */
+    public function get_maxsize() {
+        return $this->maxsize;
+    }
+
+    /**
+     * Returns true if this definition should only be used with mappings.
+     * @return bool
+     */
+    public function is_for_mappings_only() {
+        return $this->mappingsonly;
+    }
+
+    /**
+     * Returns true if this definition requires a data guarantee from the cache stores being used.
+     * @return bool
+     */
+    public function require_data_guarantee() {
+        return $this->requiredataguarantee;
+    }
+
+    /**
+     * Returns true if this definition requires that the cache stores support multiple identifiers
+     * @return bool
+     */
+    public function require_multiple_identifiers() {
+        return $this->requiremultipleidentifiers;
+    }
+
+    /**
+     * Returns true if this definition requires locking functionality. Either read or write locking.
+     * @return bool
+     */
+    public function require_locking() {
+        return $this->requirelocking;
+    }
+
+    /**
+     * Returns true if this definition requires read locking.
+     * @return bool
+     */
+    public function require_locking_read() {
+        return $this->requirelockingread;
+    }
+
+    /**
+     * Returns true if this definition requires write locking.
+     * @return bool
+     */
+    public function require_locking_write() {
+        return $this->requirelockingwrite;
+    }
+
+    /**
+     * Returns true if this definition has an associated data source.
+     * @return bool
+     */
+    public function has_data_source() {
+        return !is_null($this->datasource);
+    }
+
+    /**
+     * Returns an instance of the data source class used for this definition.
+     *
+     * @return cache_data_source
+     * @throws coding_exception
+     */
+    public function get_data_source() {
+        if (!$this->has_data_source()) {
+            throw new coding_exception('This cache does not use a datasource.');
+        }
+        return forward_static_call(array($this->datasource, 'get_instance_for_cache'), $this);
+    }
+
+    /**
+     * Sets the identifiers for this definition, or updates them if they have already been set.
+     *
+     * @param array $identifiers
+     * @throws coding_exception
+     */
+    public function set_identifiers(array $identifiers = array()) {
+        foreach ($this->requireidentifiers as $identifier) {
+            if (!array_key_exists($identifier, $identifiers)) {
+                throw new coding_exception('Identifier required for cache has not been provided: '.$identifier);
+            }
+        }
+        foreach ($identifiers as $name => $value) {
+            $this->identifiers[$name] = (string)$value;
+        }
+        // Reset the key prefix's they need updating now.
+        $this->keyprefixsingle = null;
+        $this->keyprefixmulti = null;
+    }
+
+    /**
+     * Returns the requirements of this definition as a binary flag.
+     * @return int
+     */
+    public function get_requirements_bin() {
+        $requires = 0;
+        if ($this->require_data_guarantee()) {
+            $requires += cache_store::SUPPORTS_DATA_GUARANTEE;
+        }
+        if ($this->require_multiple_identifiers()) {
+            $requires += cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS;
+        }
+        return $requires;
+    }
+
+    /**
+     * Returns true if this definitions cache should be made persistent.
+     * @return bool
+     */
+    public function should_be_persistent() {
+        return $this->persistent;
+    }
+
+    /**
+     * Returns the max size for the persistent item array in the cache.
+     * @return int
+     */
+    public function get_persistent_max_size() {
+        return $this->persistentmaxsize;
+    }
+
+    /**
+     * Generates a hash of this definition and returns it.
+     * @return string
+     */
+    public function generate_definition_hash() {
+        if ($this->definitionhash === null) {
+            $this->definitionhash = md5("{$this->mode} {$this->component} {$this->area}");
+        }
+        return $this->definitionhash;
+    }
+
+    /**
+     * Generates a single key prefix for this definition
+     *
+     * @return string
+     */
+    public function generate_single_key_prefix() {
+        if ($this->keyprefixsingle === null) {
+            $this->keyprefixsingle = $this->mode.'/'.$this->mode;
+            $identifiers = $this->get_identifiers();
+            if ($identifiers) {
+                foreach ($identifiers as $key => $value) {
+                    $this->keyprefixsingle .= '/'.$key.'='.$value;
+                }
+            }
+            $this->keyprefixsingle = md5($this->keyprefixsingle);
+        }
+        return $this->keyprefixsingle;
+    }
+
+    /**
+     * Generates a multi key prefix for this definition
+     *
+     * @return array
+     */
+    public function generate_multi_key_parts() {
+        if ($this->keyprefixmulti === null) {
+            $this->keyprefixmulti = array(
+                'mode' => $this->mode,
+                'component' => $this->component,
+                'area' => $this->area,
+            );
+            if (!empty($this->identifiers)) {
+                $identifiers = array();
+                foreach ($this->identifiers as $key => $value) {
+                    $identifiers[] = htmlentities($key).'='.htmlentities($value);
+                }
+                $this->keyprefixmulti['identifiers'] = join('&', $identifiers);
+            }
+        }
+        return $this->keyprefixmulti;
+    }
+
+    /**
+     * Check if this definition should invalidate on the given event.
+     *
+     * @param string $event
+     * @return bool True if the definition should invalidate on the event. False otherwise.
+     */
+    public function invalidates_on_event($event) {
+        return (in_array($event, $this->invalidationevents));
+    }
+
+    /**
+     * Check if the definition has any invalidation events.
+     *
+     * @return bool True if it does, false otherwise
+     */
+    public function has_invalidation_events() {
+        return !empty($this->invalidationevents);
+    }
+
+    /**
+     * Returns all of the invalidation events for this definition.
+     *
+     * @return array
+     */
+    public function get_invalidation_events() {
+        return $this->invalidationevents;
+    }
+}
\ No newline at end of file
diff --git a/cache/classes/dummystore.php b/cache/classes/dummystore.php
new file mode 100644 (file)
index 0000000..870d960
--- /dev/null
@@ -0,0 +1,277 @@
+<?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/>.
+
+/**
+ * Cache dummy store.
+ *
+ * This dummy store is used when a load has no other stores that it can make use of.
+ * This shouldn't happen in normal operation... I think.
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The cache dummy store.
+ *
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cachestore_dummy implements cache_store {
+
+    /**
+     * The name of this store.
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * Gets set to true if this store is going to persist data.
+     * This happens when the definition doesn't require it as the loader will not be persisting information and something has to.
+     * @var bool
+     */
+    protected $persist = false;
+
+    /**
+     * The persistent store array
+     * @var array
+     */
+    protected $store = array();
+
+    /**
+     * Constructs a dummy store instance.
+     * @param string $name
+     * @param array $configuration
+     */
+    public function __construct($name = 'Dummy store', array $configuration = array()) {
+        $this->name = $name;
+    }
+
+    /**
+     * Returns true if this store plugin is usable.
+     * @return bool
+     */
+    public static function are_requirements_met() {
+        return true;
+    }
+
+    /**
+     * Returns true if the user can add an instance.
+     * @return bool
+     */
+    public static function can_add_instance() {
+        return false;
+    }
+
+    /**
+     * Returns the supported features.
+     * @param array $configuration
+     * @return int
+     */
+    public static function get_supported_features(array $configuration = array()) {
+        return self::SUPPORTS_NATIVE_TTL;
+    }
+
+    /**
+     * Returns the supported mode.
+     * @param array $configuration
+     * @return int
+     */
+    public static function get_supported_modes(array $configuration = array()) {
+        return self::MODE_APPLICATION + self::MODE_REQUEST + self::MODE_SESSION;
+    }
+
+    /**
+     * Initialises the store instance for a definition.
+     * @param cache_definition $definition
+     */
+    public function initialise(cache_definition $definition) {
+        // If the definition isn't persistent then we need to be persistent here.
+        $this->persist = !$definition->should_be_persistent();
+    }
+
+    /**
+     * Returns true if this has been initialised.
+     * @return bool
+     */
+    public function is_initialised() {
+        return (!empty($this->definition));
+    }
+
+    /**
+     * Returns true if this is ready.
+     * @return bool
+     */
+    public function is_ready() {
+        return true;
+    }
+
+    /**
+     * Returns true the given mode is supported.
+     * @param int $mode
+     * @return bool
+     */
+    public static function is_supported_mode($mode) {
+        return true;
+    }
+
+    /**
+     * Returns true if this store supports data guarantee.
+     * @return bool
+     */
+    public function supports_data_guarantee() {
+        return false;
+    }
+
+    /**
+     * Returns true if this store supports multiple identifiers.
+     * @return bool
+     */
+    public function supports_multiple_indentifiers() {
+        return false;
+    }
+
+    /**
+     * Returns true if this store supports a native ttl.
+     * @return bool
+     */
+    public function supports_native_ttl() {
+        return true;
+    }
+
+    /**
+     * Returns the data for the given key
+     * @param string $key
+     * @return string|false
+     */
+    public function get($key) {
+        if ($this->persist && array_key_exists($key, $this->store)) {
+            return $this->store[$key];
+        }
+        return false;
+    }
+
+    /**
+     * Gets' the values for many keys
+     * @param array $keys
+     * @return bool
+     */
+    public function get_many($keys) {
+        $return = array();
+        foreach ($keys as $key) {
+            if ($this->persist && array_key_exists($key, $this->store)) {
+                $return[$key] = $this->store[$key];
+            } else {
+                $return[$key] = false;
+            }
+        }
+        return $return;
+    }
+
+    /**
+     * Sets an item in the cache
+     * @param string $key
+     * @param mixed $data
+     * @return bool
+     */
+    public function set($key, $data) {
+        if ($this->persist) {
+            $this->store[$key] = $data;
+        }
+        return true;
+    }
+
+    /**
+     * Sets many items in the cache
+     * @param array $keyvaluearray
+     * @return int
+     */
+    public function set_many(array $keyvaluearray) {
+        if ($this->persist) {
+            foreach ($keyvaluearray as $pair) {
+                $this->store[$pair['key']] = $pair['value'];
+            }
+            return count($keyvaluearray);
+        }
+        return 0;
+    }
+
+    /**
+     * Deletes an item from the cache
+     * @param string $key
+     * @return bool
+     */
+    public function delete($key) {
+        unset($this->store[$key]);
+        return true;
+    }
+    /**
+     * Deletes many items from the cache
+     * @param array $keys
+     * @return bool
+     */
+    public function delete_many(array $keys) {
+        if ($this->persist) {
+            foreach ($keys as $key) {
+                unset($this->store[$key]);
+            }
+        }
+        return count($keys);
+    }
+
+    /**
+     * Deletes all of the items from the cache.
+     * @return bool
+     */
+    public function purge() {
+        $this->store = array();
+        return true;
+    }
+
+    /**
+     * Performs any necessary clean up when the store instance is being deleted.
+     */
+    public function cleanup() {
+        $this->purge();
+    }
+
+    /**
+     * Generates an instance of the cache store that can be used for testing.
+     *
+     * @param cache_definition $definition
+     * @return false
+     */
+    public static function initialise_test_instance(cache_definition $definition) {
+        $cache = new cachestore_dummy('Dummy store test');
+        $cache->initialise($definition);
+        return $cache;;
+    }
+
+    /**
+     * Returns the name of this instance.
+     * @return string
+     */
+    public function my_name() {
+        return $this->name;
+    }
+}
\ No newline at end of file
diff --git a/cache/classes/factory.php b/cache/classes/factory.php
new file mode 100644 (file)
index 0000000..729a46b
--- /dev/null
@@ -0,0 +1,340 @@
+<?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/>.
+
+/**
+ * This file contains the cache factory class.
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The cache factory class.
+ *
+ * This factory class is important because it stores instances of objects used by the cache API and returns them upon requests.
+ * This allows us to both reuse objects saving on overhead, and gives us an easy place to "reset" the cache API in situations that
+ * we need such as unit testing.
+ *
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cache_factory {
+
+    /**
+     * An instance of the cache_factory class created upon the first request.
+     * @var cache_factory
+     */
+    protected static $instance;
+
+    /**
+     * An array containing caches created for definitions
+     * @var array
+     */
+    protected $cachesfromdefinitions = array();
+
+    /**
+     * Array of caches created by parameters, ad-hoc definitions will have been used.
+     * @var array
+     */
+    protected $cachesfromparams = array();
+
+    /**
+     * An array of instantiated stores.
+     * @var array
+     */
+    protected $stores = array();
+
+    /**
+     * An array of configuration instances
+     * @var array
+     */
+    protected $configs = array();
+
+    /**
+     * An array of initialised definitions
+     * @var array
+     */
+    protected $definitions = array();
+
+    /**
+     * An array of lock plugins.
+     * @var array
+     */
+    protected $lockplugins = null;
+
+    /**
+     * Returns an instance of the cache_factor method.
+     *
+     * @param bool $forcereload If set to true a new cache_factory instance will be created and used.
+     * @return cache_factory
+     */
+    public static function instance($forcereload = false) {
+        if ($forcereload || self::$instance === null) {
+            self::$instance = new cache_factory();
+        }
+        return self::$instance;
+    }
+
+    /**
+     * Protected constructor, please use the static instance method.
+     */
+    protected function __construct() {
+        // Nothing to do here.
+    }
+
+    /**
+     * Resets the arrays containing instantiated caches, stores, and config instances.
+     */
+    public static function reset() {
+        $factory = self::instance();
+        $factory->cachesfromdefinitions = array();
+        $factory->cachesfromparams = array();
+        $factory->stores = array();
+        $factory->configs = array();
+        $factory->definitions = array();
+        $factory->lockplugins = null; // MUST be null in order to force its regeneration.
+    }
+
+    /**
+     * Creates a cache object given the parameters for a definition.
+     *
+     * If a cache has already been created for the given definition then that cache instance will be returned.
+     *
+     * @param string $component
+     * @param string $area
+     * @param array $identifiers
+     * @param string $aggregate
+     * @return cache_application|cache_session|cache_request
+     */
+    public function create_cache_from_definition($component, $area, array $identifiers = array(), $aggregate = null) {
+        $definitionname = $component.'/'.$area;
+        if (array_key_exists($definitionname, $this->cachesfromdefinitions)) {
+            $cache = $this->cachesfromdefinitions[$definitionname];
+            $cache->set_identifiers($identifiers);
+            return $cache;
+        }
+        $definition = $this->create_definition($component, $area, $aggregate);
+        $definition->set_identifiers($identifiers);
+        $cache = $this->create_cache($definition, $identifiers);
+        if ($definition->should_be_persistent()) {
+            $this->cachesfromdefinitions[$definitionname] = $cache;
+        }
+        return $cache;
+    }
+
+    /**
+     * Creates an ad-hoc cache from the given param.
+     *
+     * If a cache has already been created using the same params then that cache instance will be returned.
+     *
+     * @param int $mode
+     * @param string $component
+     * @param string $area
+     * @param array $identifiers
+     * @param bool $persistent
+     * @return cache_application|cache_session|cache_request
+     */
+    public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), $persistent = false) {
+        $key = "{$mode}_{$component}_{$area}";
+        if (array_key_exists($key, $this->cachesfromparams)) {
+            return $this->cachesfromparams[$key];
+        }
+        // Get the class. Note this is a late static binding so we need to use get_called_class.
+        $definition = cache_definition::load_adhoc($mode, $component, $area, null, $persistent);
+        $definition->set_identifiers($identifiers);
+        $cache = $this->create_cache($definition, $identifiers);
+        if ($definition->should_be_persistent()) {
+            $cache->persist = true;
+            $cache->persistcache = array();
+            $this->cachesfromparams[$key] = $cache;
+        }
+        return $cache;
+    }
+
+    /**
+     * Common public method to create a cache instance given a definition.
+     *
+     * This is used by the static make methods.
+     *
+     * @param cache_definition $definition
+     * @return cache_application|cache_session|cache_store
+     * @throws coding_exception
+     */
+    public function create_cache(cache_definition $definition) {
+        $class = $definition->get_cache_class();
+        $stores = cache_helper::get_cache_stores($definition);
+        if (count($stores) === 0) {
+            // Hmm no stores, better provide a dummy store to mimick functionality. The dev will be none the wiser.
+            $stores[] = $this->create_dummy_store($definition);
+        }
+        $loader = null;
+        if ($definition->has_data_source()) {
+            $loader = $definition->get_data_source();
+        }
+        while (($store = array_pop($stores)) !== null) {
+            $loader = new $class($definition, $store, $loader);
+        }
+        return $loader;
+    }
+
+    /**
+     * Creates a store instance given its name and configuration.
+     *
+     * If the store has already been instantiated then the original objetc will be returned. (reused)
+     *
+     * @param string $name The name of the store (must be unique remember)
+     * @param array $details
+     * @param cache_definition $definition The definition to instantiate it for.
+     * @return boolean
+     */
+    public function create_store_from_config($name, array $details, cache_definition $definition) {
+        if (!array_key_exists($name, $this->stores)) {
+            // Properties: name, plugin, configuration, class.
+            $class = $details['class'];
+            $store = new $class($details['name'], $details['configuration']);
+            $this->stores[$name] = $store;
+        }
+        $store = $this->stores[$name];
+        if (!$store->is_ready() || !$store->is_supported_mode($definition->get_mode())) {
+            return false;
+        }
+        $store = clone($this->stores[$name]);
+        $store->initialise($definition);
+        return $store;
+    }
+
+    /**
+     * Creates a cache config instance with the ability to write if required.
+     *
+     * @param bool $writer If set to true an instance that can update the configuration will be returned.
+     * @return cache_config|cache_config_writer
+     */
+    public function create_config_instance($writer = false) {
+        global $CFG;
+
+        // Check if we need to create a config file with defaults.
+        $needtocreate = !cache_config::config_file_exists();
+
+        // The class to use.
+        $class = 'cache_config';
+        if ($writer || $needtocreate) {
+            require_once($CFG->dirroot.'/cache/locallib.php');
+            $class .= '_writer';
+        }
+
+        // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
+        if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
+            require_once($CFG->dirroot.'/cache/locallib.php');
+            require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
+            // We have just a single class for PHP unit tests. We don't care enough about its
+            // performance to do otherwise and having a single method allows us to inject things into it
+            // while testing.
+            $class = 'cache_config_phpunittest';
+        }
+
+        if ($needtocreate) {
+            // Create the default configuration.
+            $class::create_default_configuration();
+        }
+
+        if (!array_key_exists($class, $this->configs)) {
+            // Create a new instance and call it to load it.
+            $this->configs[$class] = new $class;
+            $this->configs[$class]->load();
+        }
+
+        // Return the instance.
+        return $this->configs[$class];
+    }
+
+    /**
+     * Creates a definition instance or returns the existing one if it has already been created.
+     * @param string $component
+     * @param string $area
+     * @param string $aggregate
+     * @return cache_definition
+     */
+    public function create_definition($component, $area, $aggregate = null) {
+        $id = $component.'/'.$area;
+        if ($aggregate) {
+            $id .= '::'.$aggregate;
+        }
+        if (!array_key_exists($id, $this->definitions)) {
+            $instance = $this->create_config_instance();
+            $definition = $instance->get_definition_by_id($id);
+            if (!$definition) {
+                $this->reset();
+                $instance = $this->create_config_instance(true);
+                $instance->update_definitions();
+                $definition = $instance->get_definition_by_id($id);
+                if (!$definition) {
+                    throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
+                } else {
+                    debugging('Cache definitions reparsed causing cache reset in order to locate definition.
+                        You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER);
+                }
+            }
+            $this->definitions[$id] = cache_definition::load($id, $definition, $aggregate);
+        }
+        return $this->definitions[$id];
+    }
+
+    /**
+     * Creates a dummy store object for use when a loader has no potential stores to use.
+     *
+     * @param cache_definition $definition
+     * @return cachestore_dummy
+     */
+    protected function create_dummy_store(cache_definition $definition) {
+        global $CFG;
+        require_once($CFG->dirroot.'/cache/classes/dummystore.php');
+        $store = new cachestore_dummy();
+        $store->initialise($definition);
+        return $store;
+    }
+
+    /**
+     * Returns a lock instance ready for use.
+     *
+     * @param array $config
+     * @return cache_lock_interface
+     */
+    public function create_lock_instance(array $config) {
+        if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) {
+            throw new coding_exception('Invalid cache lock instance provided');
+        }
+        $name = $config['name'];
+        $type = $config['type'];
+        unset($config['name']);
+        unset($config['type']);
+
+        if ($this->lockplugins === null) {
+            $this->lockplugins = get_plugin_list_with_class('cachelock', '', 'lib.php');
+        }
+        if (!array_key_exists($type, $this->lockplugins)) {
+            throw new coding_exception('Invalid cache lock type.');
+        }
+        $class = $this->lockplugins[$type];
+        return new $class($name, $config);
+    }
+}
\ No newline at end of file
diff --git a/cache/classes/helper.php b/cache/classes/helper.php
new file mode 100644 (file)
index 0000000..dfb3346
--- /dev/null
@@ -0,0 +1,456 @@
+<?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/>.
+
+/**
+ * Cache helper class
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The cache helper class.
+ *
+ * The cache helper class provides common functionality to the cache API and is useful to developers within to interact with
+ * the cache API in a general way.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cache_helper {
+
+    /**
+     * Statistics gathered by the cache API during its operation will be used here.
+     * @static
+     * @var array
+     */
+    protected static $stats = array();
+
+    /**
+     * The instance of the cache helper.
+     * @var cache_helper
+     */
+    protected static $instance;
+
+    /**
+     * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
+     *
+     * This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache
+     * configuration file has been created which allows use to set up caching when ever is required.
+     *
+     * @return bool
+     */
+    public static function ready_for_early_init() {
+        return cache_config::config_file_exists();
+    }
+
+    /**
+     * Returns an instance of the cache_helper.
+     *
+     * This is designed for internal use only and acts as a static store.
+     * @staticvar null $instance
+     * @return cache_helper
+     */
+    protected static function instance() {
+        if (is_null(self::$instance)) {
+            self::$instance = new cache_helper();
+        }
+        return self::$instance;
+    }
+
+    /**
+     * Constructs an instance of the cache_helper class. Again for internal use only.
+     */
+    protected function __construct() {
+        // Nothing to do here, just making sure you can't get an instance of this.
+    }
+
+    /**
+     * Used as a data store for initialised definitions.
+     * @var array
+     */
+    protected $definitions = array();
+
+    /**
+     * Used as a data store for initialised cache stores
+     * We use this because we want to avoid establishing multiple instances of a single store.
+     * @var array
+     */
+    protected $stores = array();
+
+    /**
+     * Returns the class for use as a cache loader for the given mode.
+     *
+     * @param int $mode One of cache_store::MODE_
+     * @return string
+     * @throws coding_exception
+     */
+    public static function get_class_for_mode($mode) {
+        switch ($mode) {
+            case cache_store::MODE_APPLICATION :
+                return 'cache_application';
+            case cache_store::MODE_REQUEST :
+                return 'cache_request';
+            case cache_store::MODE_SESSION :
+                return 'cache_session';
+        }
+        throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*');
+    }
+
+    /**
+     * Returns the cache stores to be used with the given definition.
+     * @param cache_definition $definition
+     * @return array
+     */
+    public static function get_cache_stores(cache_definition $definition) {
+        $instance = cache_config::instance();
+        $stores = $instance->get_stores_for_definition($definition);
+        $stores = self::initialise_cachestore_instances($stores, $definition);
+        return $stores;
+    }
+
+    /**
+     * Internal function for initialising an array of stores against a given cache definition.
+     *
+     * @param array $stores
+     * @param cache_definition $definition
+     * @return array
+     */
+    protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
+        $return = array();
+        $factory = cache_factory::instance();
+        foreach ($stores as $name => $details) {
+            $store = $factory->create_store_from_config($name, $details, $definition);
+            if ($store !== false) {
+                $return[] = $store;
+            }
+        }
+        return $return;
+    }
+
+    /**
+     * Returns a cache_lock instance suitable for use with the store.
+     *
+     * @param cache_store $store
+     * @return cache_lock_interface
+     */
+    public static function get_cachelock_for_store(cache_store $store) {
+        $instance = cache_config::instance();
+        $lockconf = $instance->get_lock_for_store($store->my_name());
+        $factory = cache_factory::instance();
+        return $factory->create_lock_instance($lockconf);
+    }
+
+    /**
+     * Returns an array of plugins without using core methods.
+     *
+     * This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has
+     * finished initialising. This happens when loading configuration for instance.
+     *
+     * @return string
+     */
+    public static function early_get_cache_plugins() {
+        global $CFG;
+        $result = array();
+        $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
+        $fulldir = $CFG->dirroot.'/cache/stores';
+        $items = new DirectoryIterator($fulldir);
+        foreach ($items as $item) {
+            if ($item->isDot() or !$item->isDir()) {
+                continue;
+            }
+            $pluginname = $item->getFilename();
+            if (in_array($pluginname, $ignored)) {
+                continue;
+            }
+            $pluginname = clean_param($pluginname, PARAM_PLUGIN);
+            if (empty($pluginname)) {
+                // Better ignore plugins with problematic names here.
+                continue;
+            }
+            $result[$pluginname] = $fulldir.'/'.$pluginname;
+            unset($item);
+        }
+        unset($items);
+        return $result;
+    }
+
+    /**
+     * Invalidates a given set of keys from a given definition.
+     *
+     * @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
+     *
+     * @param string $component
+     * @param string $area
+     * @param array $identifiers
+     * @param array $keys
+     * @return boolean
+     */
+    public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
+        $cache = cache::make($component, $area, $identifiers);
+        if (is_array($keys)) {
+            $cache->delete_many($keys);
+        } else if (is_scalar($keys)) {
+            $cache->delete($keys);
+        } else {
+            throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
+        }
+        return true;
+    }
+
+    /**
+     * Invalidates a given set of keys by means of an event.
+     *
+     * @todo add support for identifiers to be supplied and utilised.
+     *
+     * @param string $event
+     * @param array $keys
+     */
+    public static function invalidate_by_event($event, array $keys) {
+        $instance = cache_config::instance();
+        $invalidationeventset = false;
+        $factory = cache_factory::instance();
+        foreach ($instance->get_definitions() as $name => $definitionarr) {
+            $definition = cache_definition::load($name, $definitionarr);
+            if ($definition->invalidates_on_event($event)) {
+                // OK at this point we know that the definition has information to invalidate on the event.
+                // There are two routes, either its an application cache in which case we can invalidate it now.
+                // or it is a session cache in which case we need to set something to the "Event invalidation" definition.
+                // No need to deal with request caches, we don't want to change data half way through a request.
+                if ($definition->get_mode() === cache_store::MODE_APPLICATION) {
+                    $cache = $factory->create_cache($definition);
+                    $cache->delete_many($keys);
+                }
+
+                // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
+                if ($invalidationeventset === false) {
+                    // Get the event invalidation cache.
+                    $cache = cache::make('core', 'eventinvalidation');
+                    // Get any existing invalidated keys for this cache.
+                    $data = $cache->get($event);
+                    if ($data === false) {
+                        // There are none.
+                        $data = array();
+                    }
+                    // Add our keys to them with the current cache timestamp.
+                    foreach ($keys as $key) {
+                        $data[$key] = cache::now();
+                    }
+                    // Set that data back to the cache.
+                    $cache->set($event, $data);
+                    // This only needs to occur once.
+                    $invalidationeventset = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Purges the cache for a specific definition.
+     *
+     * @param string $component
+     * @param string $area
+     * @param array $identifiers
+     * @return bool
+     */
+    public static function purge_by_definition($component, $area, array $identifiers = array()) {
+        // Create the cache.
+        $cache = cache::make($component, $area, $identifiers);
+        // Purge baby, purge.
+        $cache->purge();
+        return true;
+    }
+
+    /**
+     * Purges a cache of all information on a given event.
+     *
+     * @param string $event
+     */
+    public static function purge_by_event($event) {
+        $instance = cache_config::instance();
+        $invalidationeventset = false;
+        $factory = cache_factory::instance();
+        foreach ($instance->get_definitions() as $name => $definitionarr) {
+            $definition = cache_definition::load($name, $definitionarr);
+            if ($definition->invalidates_on_event($event)) {
+                // Purge the cache.
+                $cache = $factory->create_cache($definition);
+                $cache->purge();
+
+                // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
+                if ($invalidationeventset === false) {
+                    // Get the event invalidation cache.
+                    $cache = cache::make('core', 'eventinvalidation');
+                    // Create a key to invalidate all.
+                    $data = array(
+                        'purged' => cache::now()
+                    );
+                    // Set that data back to the cache.
+                    $cache->set($event, $data);
+                    // This only needs to occur once.
+                    $invalidationeventset = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Ensure that the stats array is ready to collect information for the given store and definition.
+     * @param string $store
+     * @param string $definition
+     */
+    protected static function ensure_ready_for_stats($store, $definition) {
+        if (!array_key_exists($definition, self::$stats)) {
+            self::$stats[$definition] = array(
+                $store => array(
+                    'hits' => 0,
+                    'misses' => 0,
+                    'sets' => 0,
+                )
+            );
+        } else if (!array_key_exists($store, self::$stats[$definition])) {
+            self::$stats[$definition][$store] = array(
+                'hits' => 0,
+                'misses' => 0,
+                'sets' => 0,
+            );
+        }
+    }
+
+    /**
+     * Record a cache hit in the stats for the given store and definition.
+     *
+     * @param string $store
+     * @param string $definition
+     */
+    public static function record_cache_hit($store, $definition) {
+        self::ensure_ready_for_stats($store, $definition);
+        self::$stats[$definition][$store]['hits']++;
+    }
+
+    /**
+     * Record a cache miss in the stats for the given store and definition.
+     *
+     * @param string $store
+     * @param string $definition
+     */
+    public static function record_cache_miss($store, $definition) {
+        self::ensure_ready_for_stats($store, $definition);
+        self::$stats[$definition][$store]['misses']++;
+    }
+
+    /**
+     * Record a cache set in the stats for the given store and definition.
+     *
+     * @param string $store
+     * @param string $definition
+     */
+    public static function record_cache_set($store, $definition) {
+        self::ensure_ready_for_stats($store, $definition);
+        self::$stats[$definition][$store]['sets']++;
+    }
+
+    /**
+     * Return the stats collected so far.
+     * @return array
+     */
+    public static function get_stats() {
+        return self::$stats;
+    }
+
+    /**
+     * Purge all of the cache stores of all of their data.
+     *
+     * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
+     * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
+     * painful.
+     */
+    public static function purge_all() {
+        $config = cache_config::instance();
+        $stores = $config->get_all_stores();
+        $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'core', 'cache_purge');
+        foreach ($stores as $store) {
+            $class = $store['class'];
+            $instance = new $class($store['name'], $store['configuration']);
+            if (!$instance->is_ready()) {
+                continue;
+            }
+            $instance->initialise($definition);
+            $instance->purge();
+        }
+    }
+
+    /**
+     * Purges a store given its name.
+     *
+     * @param string $storename
+     * @return bool
+     */
+    public static function purge_store($storename) {
+        $config = cache_config::instance();
+        foreach ($config->get_all_stores() as $store) {
+            if ($store['name'] !== $storename) {
+                continue;
+            }
+            $class = $store['class'];
+            $instance = new $class($store['name'], $store['configuration']);
+            if (!$instance->is_ready()) {
+                continue;
+            }
+            $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'core', 'cache_purge');
+            $instance->initialise($definition);
+            $instance->purge();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the translated name of the definition.
+     *
+     * @param cache_definition $definition
+     * @return lang_string
+     */
+    public static function get_definition_name($definition) {
+        if ($definition instanceof cache_definition) {
+            return $definition->get_name();
+        }
+        $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
+        $component = $definition['component'];
+        if ($component === 'core') {
+            $component = 'cache';
+        }
+        return new lang_string($identifier, $component);
+    }
+
+    /**
+     * Hashes a descriptive key to make it shorter and stil unique.
+     * @param string $key
+     * @return string
+     */
+    public static function hash_key($key) {
+        return crc32($key);
+    }
+}
\ No newline at end of file
diff --git a/cache/classes/interfaces.php b/cache/classes/interfaces.php
new file mode 100644 (file)
index 0000000..1f96fa0
--- /dev/null
@@ -0,0 +1,692 @@
+<?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/>.
+
+/**
+ * Cache API interfaces
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Cache Loader.
+ *
+ * This cache loader interface provides the required structure for classes that wish to be interacted with as a
+ * means of accessing and interacting with a cache.
+ *
+ * Can be implemented by any class wishing to be a cache loader.
+ */
+interface cache_loader {
+
+    /**
+     * Retrieves the value for the given key from the cache.
+     *
+     * @param string|int $key The key for the data being requested.
+     * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
+     * @return mixed The data retrieved from the cache, or false if the key did not exist within the cache.
+     *      If MUST_EXIST was used then an exception will be thrown if the key does not exist within the cache.
+     */
+    public function get($key, $strictness = IGNORE_MISSING);
+
+    /**
+     * Retrieves an array of values for an array of keys.
+     *
+     * Using this function comes with potential performance implications.
+     * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
+     * the equivalent singular method for each item provided.
+     * This should not deter you from using this function as there is a performance benefit in situations where the cache
+     * store does support it, but you should be aware of this fact.
+     *
+     * @param array $keys The keys of the data being requested.
+     * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
+     * @return array An array of key value pairs for the items that could be retrieved from the cache.
+     *      If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
+     *      Otherwise any key that did not exist will have a data value of false within the results.
+     */
+    public function get_many(array $keys, $strictness = IGNORE_MISSING);
+
+    /**
+     * Sends a key => value pair to the cache.
+     *
+     * <code>
+     * // This code will add four entries to the cache, one for each url.
+     * $cache->set('main', 'http://moodle.org');
+     * $cache->set('docs', 'http://docs.moodle.org');
+     * $cache->set('tracker', 'http://tracker.moodle.org');
+     * $cache->set('qa', 'http://qa.moodle.net');
+     * </code>
+     *
+     * @param string|int $key The key for the data being requested.
+     * @param mixed $data The data to set against the key.
+     * @return bool True on success, false otherwise.
+     */
+    public function set($key, $data);
+
+    /**
+     * Sends several key => value pairs to the cache.
+     *
+     * Using this function comes with potential performance implications.
+     * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
+     * the equivalent singular method for each item provided.
+     * This should not deter you from using this function as there is a performance benefit in situations where the cache store
+     * does support it, but you should be aware of this fact.
+     *
+     * <code>
+     * // This code will add four entries to the cache, one for each url.
+     * $cache->set_many(array(
+     *     'main' => 'http://moodle.org',
+     *     'docs' => 'http://docs.moodle.org',
+     *     'tracker' => 'http://tracker.moodle.org',
+     *     'qa' => ''http://qa.moodle.net'
+     * ));
+     * </code>
+     *
+     * @param array $keyvaluearray An array of key => value pairs to send to the cache.
+     * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
+     *      ... if they care that is.
+     */
+    public function set_many(array $keyvaluearray);
+
+    /**
+     * Test is a cache has a key.
+     *
+     * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
+     * test and any subsequent action (get, set, delete etc).
+     * Instead it is recommended to write your code in such a way they it performs the following steps:
+     * <ol>
+     * <li>Attempt to retrieve the information.</li>
+     * <li>Generate the information.</li>
+     * <li>Attempt to set the information</li>
+     * </ol>
+     *
+     * Its also worth mentioning that not all stores support key tests.
+     * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
+     * Just one more reason you should not use these methods unless you have a very good reason to do so.
+     *
+     * @param string|int $key
+     * @return bool True if the cache has the requested key, false otherwise.
+     */
+    public function has($key);
+
+    /**
+     * Test if a cache has at least one of the given keys.
+     *
+     * It is strongly recommended to avoid the use of this function if not absolutely required.
+     * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
+     *
+     * Its also worth mentioning that not all stores support key tests.
+     * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
+     * Just one more reason you should not use these methods unless you have a very good reason to do so.
+     *
+     * @param array $keys
+     * @return bool True if the cache has at least one of the given keys
+     */
+    public function has_any(array $keys);
+
+    /**
+     * Test is a cache has all of the given keys.
+     *
+     * It is strongly recommended to avoid the use of this function if not absolutely required.
+     * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
+     *
+     * Its also worth mentioning that not all stores support key tests.
+     * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
+     * Just one more reason you should not use these methods unless you have a very good reason to do so.
+     *
+     * @param array $keys
+     * @return bool True if the cache has all of the given keys, false otherwise.
+     */
+    public function has_all(array $keys);
+
+    /**
+     * Delete the given key from the cache.
+     *
+     * @param string|int $key The key to delete.
+     * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
+     *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
+     * @return bool True of success, false otherwise.
+     */
+    public function delete($key, $recurse = true);
+
+    /**
+     * Delete all of the given keys from the cache.
+     *
+     * @param array $keys The key to delete.
+     * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
+     *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
+     * @return int The number of items successfully deleted.
+     */
+    public function delete_many(array $keys, $recurse = true);
+}
+
+/**
+ * Cache Loader supporting locking.
+ *
+ * This interface should be given to classes already implementing cache_loader that also wish to support locking.
+ * It outlines the required structure for utilising locking functionality when using a cache.
+ *
+ * Can be implemented by any class already implementing the cache_loader interface.
+ */
+interface cache_loader_with_locking {
+
+    /**
+     * Acquires a lock for the given key.
+     *
+     * Please note that this happens automatically if the cache definition requires locking.
+     * it is still made a public method so that adhoc caches can use it if they choose.
+     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
+     * are acquired, checked, and released.
+     *
+     * @param string|int $key
+     * @return bool True if the lock could be acquired, false otherwise.
+     */
+    public function acquire_lock($key);
+
+    /**
+     * Checks if the cache loader owns the lock for the given key.
+     *
+     * Please note that this happens automatically if the cache definition requires locking.
+     * it is still made a public method so that adhoc caches can use it if they choose.
+     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
+     * are acquired, checked, and released.
+     *
+     * @param string|int $key
+     * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it,
+     *      null if there is no lock.
+     */
+    public function check_lock_state($key);
+
+    /**
+     * Releases the lock for the given key.
+     *
+     * Please note that this happens automatically if the cache definition requires locking.
+     * it is still made a public method so that adhoc caches can use it if they choose.
+     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
+     * are acquired, checked, and released.
+     *
+     * @param string|int $key
+     * @return bool True if the lock has been released, false if there was a problem releasing the lock.
+     */
+    public function release_lock($key);
+}
+
+/**
+ * Cache store.
+ *
+ * This interface outlines the requirements for a cache store plugin.
+ * It must be implemented by all such plugins and provides a reference to interacting with cache stores.
+ *
+ * Must be implemented by all cache store plugins.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+interface cache_store {
+
+    /**#@+
+     * Constants for features a cache store can support
+     */
+    /**
+     * Supports multi-part keys
+     */
+    const SUPPORTS_MULTIPLE_IDENTIFIERS = 1;
+    /**
+     * Ensures data remains in the cache once set.
+     */
+    const SUPPORTS_DATA_GUARANTEE = 2;
+    /**
+     * Supports a native ttl system.
+     */
+    const SUPPORTS_NATIVE_TTL = 4;
+    /**#@-*/
+
+    /**#@+
+     * Constants for the modes of a cache store
+     */
+    /**
+     * Application caches. These are shared caches.
+     */
+    const MODE_APPLICATION = 1;
+    /**
+     * Session caches. Just access to the PHP session.
+     */
+    const MODE_SESSION = 2;
+    /**
+     * Request caches. Static caches really.
+     */
+    const MODE_REQUEST = 4;
+    /**#@-*/
+
+    /**
+     * Static method to check if the store requirements are met.
+     *
+     * @return bool True if the stores software/hardware requirements have been met and it can be used. False otherwise.
+     */
+    public static function are_requirements_met();
+
+    /**
+     * Static method to check if a store is usable with the given mode.
+     *
+     * @param int $mode One of cache_store::MODE_*
+     */
+    public static function is_supported_mode($mode);
+
+    /**
+     * Returns the supported features as a binary flag.
+     *
+     * @param array $configuration The configuration of a store to consider specifically.
+     * @return int The supported features.
+     */
+    public static function get_supported_features(array $configuration = array());
+
+    /**
+     * Returns the supported modes as a binary flag.
+     *
+     * @param array $configuration The configuration of a store to consider specifically.
+     * @return int The supported modes.
+     */
+    public static function get_supported_modes(array $configuration = array());
+
+    /**
+     * Returns true if this cache store instance supports multiple identifiers.
+     *
+     * @return bool
+     */
+    public function supports_multiple_indentifiers();
+
+    /**
+     * Returns true if this cache store instance promotes data guarantee.
+     *
+     * @return bool
+     */
+    public function supports_data_guarantee();
+
+    /**
+     * Returns true if this cache store instance supports ttl natively.
+     *
+     * @return bool
+     */
+    public function supports_native_ttl();
+
+    /**
+     * Used to control the ability to add an instance of this store through the admin interfaces.
+     *
+     * @return bool True if the user can add an instance, false otherwise.
+     */
+    public static function can_add_instance();
+
+    /**
+     * Constructs an instance of the cache store.
+     *
+     * This method should not create connections or perform and processing, it should be used
+     *
+     * @param string $name The name of the cache store
+     * @param array $configuration The configuration for this store instance.
+     */
+    public function __construct($name, array $configuration = array());
+
+    /**
+     * Returns the name of this store instance.
+     * @return string
+     */
+    public function my_name();
+
+    /**
+     * Initialises a new instance of the cache store given the definition the instance is to be used for.
+     *
+     * This function should prepare any given connections etc.
+     *
+     * @param cache_definition $definition
+     */
+    public function initialise(cache_definition $definition);
+
+    /**
+     * Returns true if this cache store instance has been initialised.
+     * @return bool
+     */
+    public function is_initialised();
+
+    /**
+     * Returns true if this cache store instance is ready to use.
+     * @return bool
+     */
+    public function is_ready();
+
+    /**
+     * Retrieves an item from the cache store given its key.
+     *
+     * @param string $key The key to retrieve
+     * @return mixed The data that was associated with the key, or false if the key did not exist.
+     */
+    public function get($key);
+
+    /**
+     * Retrieves several items from the cache store in a single transaction.
+     *
+     * If not all of the items are available in the cache then the data value for those that are missing will be set to false.
+     *
+     * @param array $keys The array of keys to retrieve
+     * @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
+     *      be set to false.
+     */
+    public function get_many($keys);
+
+    /**
+     * Sets an item in the cache given its key and data value.
+     *
+     * @param string $key The key to use.
+     * @param mixed $data The data to set.
+     * @return bool True if the operation was a success false otherwise.
+     */
+    public function set($key, $data);
+
+    /**
+     * Sets many items in the cache in a single transaction.
+     *
+     * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
+     *      keys, 'key' and 'value'.
+     * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
+     *      sent ... if they care that is.
+     */
+    public function set_many(array $keyvaluearray);
+
+    /**
+     * Deletes an item from the cache store.
+     *
+     * @param string $key The key to delete.
+     * @return bool Returns true if the operation was a success, false otherwise.
+     */
+    public function delete($key);
+
+    /**
+     * Deletes several keys from the cache in a single action.
+     *
+     * @param array $keys The keys to delete
+     * @return int The number of items successfully deleted.
+     */
+    public function delete_many(array $keys);
+
+    /**
+     * Purges the cache deleting all items within it.
+     *
+     * @return boolean True on success. False otherwise.
+     */
+    public function purge();
+
+    /**
+     * Performs any necessary clean up when the store instance is being deleted.
+     */
+    public function cleanup();
+
+    /**
+     * Generates an instance of the cache store that can be used for testing.
+     *
+     * Returns an instance of the cache store, or false if one cannot be created.
+     *
+     * @param cache_definition $definition
+     * @return cache_store|false
+     */
+    public static function initialise_test_instance(cache_definition $definition);
+}
+
+/**
+ * Cache store feature: locking
+ *
+ * This is a feature that cache stores can implement if they wish to support locking themselves rather
+ * than having the cache loader handle it for them.
+ *
+ * Can be implemented by classes already implementing cache_store.
+ */
+interface cache_is_lockable {
+
+    /**
+     * Acquires a lock on the given key for the given identifier.
+     *
+     * @param string $key The key we are locking.
+     * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
+     *      The use of this property is entirely optional and implementations can act as they like upon it.
+     * @return bool True if the lock could be acquired, false otherwise.
+     */
+    public function acquire_lock($key, $ownerid);
+
+    /**
+     * Test if there is already a lock for the given key and if there is whether it belongs to the calling code.
+     *
+     * @param string $key The key we are locking.
+     * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
+     * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there
+     *      is no lock.
+     */
+    public function check_lock_state($key, $ownerid);
+
+    /**
+     * Releases the lock on the given key.
+     *
+     * @param string $key The key we are locking.
+     * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
+     *      The use of this property is entirely optional and implementations can act as they like upon it.
+     * @return bool True if the lock has been released, false if there was a problem releasing the lock.
+     */
+    public function release_lock($key, $ownerid);
+}
+
+/**
+ * Cache store feature: key awareness.
+ *
+ * This is a feature that cache stores and cache loaders can both choose to implement.
+ * If a cache store implements this then it will be made responsible for tests for items within the cache.
+ * If the cache store being used doesn't implement this then it will be the responsibility of the cache loader to use the
+ * equivalent get methods to mimick the functionality of these tests.
+ *
+ * Cache stores should only override these methods if they natively support such features or if they have a better performing
+ * means of performing these tests than the handling that would otherwise take place in the cache_loader.
+ *
+ * Can be implemented by classes already implementing cache_store.
+ */
+interface cache_is_key_aware {
+
+    /**
+     * Test is a cache has a key.
+     *
+     * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
+     * test and any subsequent action (get, set, delete etc).
+     * Instead it is recommended to write your code in such a way they it performs the following steps:
+     * <ol>
+     * <li>Attempt to retrieve the information.</li>
+     * <li>Generate the information.</li>
+     * <li>Attempt to set the information</li>
+     * </ol>
+     *
+     * Its also worth mentioning that not all stores support key tests.
+     * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
+     * Just one more reason you should not use these methods unless you have a very good reason to do so.
+     *
+     * @param string|int $key
+     * @return bool True if the cache has the requested key, false otherwise.
+     */
+    public function has($key);
+
+    /**
+     * Test if a cache has at least one of the given keys.
+     *
+     * It is strongly recommended to avoid the use of this function if not absolutely required.
+     * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
+     *
+     * Its also worth mentioning that not all stores support key tests.
+     * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
+     * Just one more reason you should not use these methods unless you have a very good reason to do so.
+     *
+     * @param array $keys
+     * @return bool True if the cache has at least one of the given keys
+     */
+    public function has_any(array $keys);
+
+    /**
+     * Test is a cache has all of the given keys.
+     *
+     * It is strongly recommended to avoid the use of this function if not absolutely required.
+     * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
+     *
+     * Its also worth mentioning that not all stores support key tests.
+     * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
+     * Just one more reason you should not use these methods unless you have a very good reason to do so.
+     *
+     * @param array $keys
+     * @return bool True if the cache has all of the given keys, false otherwise.
+     */
+    public function has_all(array $keys);
+}
+
+/**
+ * Cache Data Source.
+ *
+ * The cache data source interface can be implemented by any class within Moodle.
+ * If implemented then the class can be reference in a cache definition and will be used to load information that cannot be
+ * retrieved from the cache. As part of its retrieval that information will also be loaded into the cache.
+ *
+ * This allows developers to created a complete cache solution that can be used through code ensuring consistent cache
+ * interaction and loading. Allowing them in turn to centralise code and help keeps things more easily maintainable.
+ *
+ * Can be implemented by any class.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+interface cache_data_source {
+
+    /**
+     * Returns an instance of the data source class that the cache can use for loading data using the other methods
+     * specified by this interface.
+     *
+     * @param cache_definition $definition
+     * @return object
+     */
+    public static function get_instance_for_cache(cache_definition $definition);
+
+    /**
+     * Loads the data for the key provided ready formatted for caching.
+     *
+     * @param string|int $key The key to load.
+     * @return mixed What ever data should be returned, or false if it can't be loaded.
+     */
+    public function load_for_cache($key);
+
+    /**
+     * Loads several keys for the cache.
+     *
+     * @param array $keys An array of keys each of which will be string|int.
+     * @return array An array of matching data items.
+     */
+    public function load_many_for_cache(array $keys);
+}
+
+/**
+ * Cacheable object.
+ *
+ * This interface can be implemented by any class that is going to be passed into a cache and allows it to take control of the
+ * structure and the information about to be cached, as well as how to deal with it when it is retrieved from a cache.
+ * Think of it like serialisation and the __sleep and __wakeup methods.
+ * This is used because cache stores are responsible for how they interact with data and what they do when storing it. This
+ * interface ensures there is always a guaranteed action.
+ */
+interface cacheable_object {
+
+    /**
+     * Prepares the object for caching. Works like the __sleep method.
+     *
+     * @return mixed The data to cache, can be anything except a class that implements the cacheable_object... that would
+     *      be dumb.
+     */
+    public function prepare_to_cache();
+
+    /**
+     * Takes the data provided by prepare_to_cache and reinitialises an instance of the associated from it.
+     *
+     * @param mixed $data
+     * @return object The instance for the given data.
+     */
+    public static function wake_from_cache($data);
+}
+
+/**
+ * Cache lock interface
+ *
+ * This interface needs to be inherited by all cache lock plugins.
+ */
+interface cache_lock_interface {
+    /**
+     * Constructs an instance of the cache lock given its name and its configuration data
+     *
+     * @param string $name The unique name of the lock instance
+     * @param array $configuration
+     */
+    public function __construct($name, array $configuration = array());
+
+    /**
+     * Acquires a lock on a given key.
+     *
+     * @param string $key The key to acquire a lock for.
+     * @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin
+     *      to use this. Each implementation can decide for themselves.
+     * @param bool $block If set to true the application will wait until a lock can be acquired
+     * @return bool True if the lock can be acquired false otherwise.
+     */
+    public function lock($key, $ownerid, $block = false);
+
+    /**
+     * Releases the lock held on a certain key.
+     *
+     * @param string $key The key to release the lock for.
+     * @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin
+     *      to use this. Each implementation can decide for themselves.
+     * @param bool $forceunlock If set to true the lock will be removed if it exists regardless of whether or not we own it.
+     */
+    public function unlock($key, $ownerid, $forceunlock = false);
+
+    /**
+     * Checks the state of the given key.
+     *
+     * Returns true if the key is locked and belongs to the ownerid.
+     * Returns false if the key is locked but does not belong to the ownerid.
+     * Returns null if there is no lock
+     *
+     * @param string $key The key we are checking for.
+     * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else.
+     * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there
+     *      is no lock.
+     */
+    public function check_state($key, $ownerid);
+
+    /**
+     * Cleans up any left over locks.
+     *
+     * This function MUST clean up any locks that have been acquired and not released during processing.
+     * Although the situation of acquiring a lock and not releasing it should be insanely rare we need to deal with it.
+     * Things such as unfortunate timeouts etc could cause this situation.
+     */
+    public function __destruct();
+}
\ No newline at end of file
diff --git a/cache/classes/loaders.php b/cache/classes/loaders.php
new file mode 100644 (file)
index 0000000..aa6a0ed
--- /dev/null
@@ -0,0 +1,1454 @@
+<?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/>.
+
+/**
+ * Cache loaders
+ *
+ * This file is part of Moodle's cache API, affectionately called MUC.
+ * It contains the components that are requried in order to use caching.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The main cache class.
+ *
+ * This class if the first class that any end developer will interact with.
+ * In order to create an instance of a cache that they can work with they must call one of the static make methods belonging
+ * to this class.
+ *
+ * @package    core
+ * @category   cache
+ * @copyright  2012 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cache implements cache_loader {
+
+    /**
+     * We need a timestamp to use within the cache API.
+     * This stamp needs to be used for all ttl and time based operations to ensure that we don't end up with
+     * timing issues.
+     * @var int
+     */
+    protected static $now;
+
+    /**
+     * The definition used when loading this cache if there was one.
+     * @var cache_definition
+     */
+    private $definition = false;
+
+    /**
+     * The cache store that this loader will make use of.
+     * @var cache_store
+     */
+    private $store;
+
+    /**
+     * The next cache loader in the chain if there is one.
+     * If a cache request misses for the store belonging to this loader then the loader
+     * stored here will be checked next.
+     * If there is a loader here then $datasource must be false.
+     * @var cache_loader|false
+     */
+    private $loader = false;
+
+    /**
+     * The data source to use if we need to load data (because if doesn't exist in the cache store).
+     * If there is a data source here then $loader above must be false.
+     * @var cache_data_source|false
+     */
+    private $datasource = false;
+
+    /**
+     * Used to quickly check if the store supports key awareness.
+     * This is set when the cache is initialised and is used to speed up processing.
+     * @var bool
+     */
+    private $supportskeyawareness = null;
+
+    /**
+     * Used to quickly check if the store supports ttl natively.
+     * This is set when the cache is initialised and is used to speed up processing.
+     * @var bool
+     */
+    private $supportsnativettl = null;
+
+    /**
+     * Gets set to true if the cache is going to be using the build in static "persist" cache.
+     * The persist cache statically caches items used during the lifetime of the request. This greatly speeds up interaction
+     * with the cache in areas where it will be repetitively hit for the same information such as with strings.
+     * There are several other variables to control how this persist cache works.
+     * @var bool
+     */
+    private $persist = false;
+
+    /**
+     * The persist cache itself.
+     * Items will be stored in this cache as they were provided. This ensure there is no unnecessary processing taking place.
+     * @var array
+     */
+    private $persistcache = array();
+
+    /**
+     * The number of items in the persist cache. Avoids count calls like you wouldn't believe.
+     * @var int
+     */
+    private $persistcount = 0;
+
+    /**
+     * An array containing just the keys being used in the persist cache.
+     * This seems redundant perhaps but is used when managing the size of the persist cache.
+     * @var array
+     */
+    private $persistkeys = array();
+
+    /**
+     * The maximum size of the persist cache. If set to false there is no max size.
+     * Caches that make use of the persist cache should seriously consider setting this to something reasonably small, but
+     * still large enough to offset repetitive calls.
+     * @var int|false
+     */
+    private $persistmaxsize = false;
+
+    /**
+     * Gets set to true during initialisation if the definition is making use of a ttl.
+     * Used to speed up processing.
+     * @var bool
+     */
+    private $hasattl = false;
+
+    /**
+     * Gets set to the class name of the store during initialisation. This is used several times in the cache class internally
+     * and having it here helps speed up processing.
+     * @var strubg
+     */
+    private $storetype = 'unknown';
+
+    /**
+     * Gets set to true if we want to collect performance information about the cache API.
+     * @var bool
+     */
+    protected $perfdebug = false;
+
+    /**
+     * Determines if this loader is a sub loader, not the top of the chain.
+     * @var bool
+     */
+    protected $subloader = false;
+
+    /**
+     * Creates a new cache instance for a pre-defined definition.
+     *
+     * @param string $component The component for the definition
+     * @param string $area The area for the definition
+     * @param array $identifiers Any additional identifiers that should be provided to the definition.
+     * @param string $aggregate Super advanced feature. More docs later.
+     * @return cache_application|cache_session|cache_store
+     */
+    public static function make($component, $area, array $identifiers = array(), $aggregate = null) {
+        $factory = cache_factory::instance();
+        return $factory->create_cache_from_definition($component, $area, $identifiers, $aggregate);
+    }
+
+    /**
+     * Creates a new cache instance based upon the given params.
+     *
+     * @param int $mode One of cache_store::MODE_*
+     * @param string $component The component this cache relates to.
+     * @param string $area The area this cache relates to.
+     * @param array $identifiers Any additional identifiers that should be provided to the definition.
+     * @param bool $persistent If set to true the cache will persist construction requests.
+     * @return cache_application|cache_session|cache_store
+     */
+    public static function make_from_params($mode, $component, $area, array $identifiers = array(), $persistent = false) {
+        $factory = cache_factory::instance();
+        return $factory->create_cache_from_params($mode, $component, $area, $identifiers, $persistent);
+    }
+
+    /**
+     * Constructs a new cache instance.
+     *
+     * You should not call this method from your code, instead you should use the cache::make methods.
+     *
+     * This method is public so that the cache_factory is able to instantiate cache instances.
+     * Ideally we would make this method protected and expose its construction to the factory method internally somehow.
+     * The factory class is responsible for this in order to centralise the storage of instances once created. This way if needed
+     * we can force a reset of the cache API (used during unit testing).
+     *
+     * @param cache_definition $definition The definition for the cache instance.
+     * @param cache_store $store The store that cache should use.
+     * @param cache_loader|cache_data_source $loader The next loader in the chain or the data source if there is one and there
+     *      are no other cache_loaders in the chain.
+     */
+    public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
+        global $CFG;
+        $this->definition = $definition;
+        $this->store = $store;
+        $this->storetype = get_class($store);
+        $this->perfdebug = !empty($CFG->perfdebug);
+        if ($loader instanceof cache_loader) {
+            $this->loader = $loader;
+            // Mark the loader as a sub (chained) loader.
+            $this->loader->set_is_sub_loader(true);
+        } else if ($loader instanceof cache_data_source) {
+            $this->datasource = $loader;
+        }
+        $this->definition->generate_definition_hash();
+        $this->persist = $this->definition->should_be_persistent();
+        if ($this->persist) {
+            $this->persistmaxsize = $this->definition->get_persistent_max_size();
+        }
+        $this->hasattl = ($this->definition->get_ttl() > 0);
+    }
+
+    /**
+     * Used to inform the loader of its state as a sub loader, or as the top of the chain.
+     *
+     * This is important as it ensures that we do not have more than one loader keeping persistent data.
+     * Subloaders need to be "pure" loaders in the sense that they are used to store and retrieve information from stores or the
+     * next loader/data source in the chain.
+     * Nothing fancy, nothing flash.
+     *
+     * @param bool $setting
+     */
+    protected function set_is_sub_loader($setting = true) {
+        if ($setting) {
+            $this->subloader = true;
+            // Subloaders should not keep persistent data.
+            $this->persist = false;
+            $this->persistmaxsize = false;
+        } else {
+            $this->subloader = true;
+            $this->persist = $this->definition->should_be_persistent();
+            if ($this->persist) {
+                $this->persistmaxsize = $this->definition->get_persistent_max_size();
+            }
+        }
+    }
+
+    /**
+     * Alters the identifiers that have been provided to the definition.
+     *
+     * This is an advanced method and should not be used unless really needed.
+     * It allows the developer to slightly alter the definition without having to re-establish the cache.
+     * It will cause more processing as the definition will need to clear and reprepare some of its properties.
+     *
+     * @param array $identifiers
+     */
+    public function set_identifiers(array $identifiers) {
+        $this->definition->set_identifiers($identifiers);
+    }
+
+    /**
+     * Retrieves the value for the given key from the cache.
+     *
+     * @param string|int $key The key for the data being requested.
+     *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
+     *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
+     * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
+     * @return mixed|false The data from the cache or false if the key did not exist within the cache.
+     * @throws moodle_exception
+     */
+    public function get($key, $strictness = IGNORE_MISSING) {
+        // 1. Parse the key.
+        $parsedkey = $this->parse_key($key);
+        // 2. Get it from the persist cache if we can (only when persist is enabled and it has already been requested/set).
+        $result = $this->get_from_persist_cache($parsedkey);
+        if ($result !== false) {
+            if ($this->perfdebug) {
+                cache_helper::record_cache_hit('** static persist **', $this->definition->get_id());
+            }
+            if (!is_scalar($result)) {
+                // If data is an object it will be a reference.
+                // If data is an array if may contain references.
+                // We want to break references so that the cache cannot be modified outside of itself.
+                // Call the function to unreference it (in the best way possible).
+                $result = $this->unref($result);
+            }
+            return $result;
+        } else if ($this->perfdebug) {
+            cache_helper::record_cache_miss('** static persist **', $this->definition->get_id());
+        }
+        // 3. Get it from the store. Obviously wasn't in the persist cache.
+        $result = $this->store->get($parsedkey);
+        if ($result !== false) {
+            if ($result instanceof cache_ttl_wrapper) {
+                if ($result->has_expired()) {
+                    $this->store->delete($parsedkey);
+                    $result = false;
+                } else {
+                    $result = $result->data;
+                }
+            }
+            if ($result instanceof cache_cached_object) {
+                $result = $result->restore_object();
+            }
+            if ($this->is_using_persist_cache()) {
+                $this->set_in_persist_cache($parsedkey, $result);
+            }
+        }
+        // 4. Load if from the loader/datasource if we don't already have it.
+        $setaftervalidation = false;
+        if ($result === false) {
+            if ($this->perfdebug) {
+                cache_helper::record_cache_miss($this->storetype, $this->definition->get_id());
+            }
+            if ($this->loader !== false) {
+                $result = $this->loader->get($parsedkey);
+            } else if ($this->datasource !== false) {
+                $result = $this->datasource->load_for_cache($key);
+            }
+            $setaftervalidation = ($result !== false);
+        } else if ($this->perfdebug) {
+            cache_helper::record_cache_hit($this->storetype, $this->definition->get_id());
+        }
+        // 5. Validate strictness.
+        if ($strictness === MUST_EXIST && $result === false) {
+            throw new moodle_exception('Requested key did not exist in any cache stores and could not be loaded.');
+        }
+        // 6. Set it to the store if we got it from the loader/datasource.
+        if ($setaftervalidation) {
+            $this->set($key, $result);
+        }
+        // 7. Make sure we don't pass back anything that could be a reference.
+        //    We don't want people modifying the data in the cache.
+        if (!is_scalar($result)) {
+            // If data is an object it will be a reference.
+            // If data is an array if may contain references.
+            // We want to break references so that the cache cannot be modified outside of itself.
+            // Call the function to unreference it (in the best way possible).
+            $result = $this->unref($result);
+        }
+        return $result;
+    }
+
+    /**
+     * Retrieves an array of values for an array of keys.
+     *
+     * Using this function comes with potential performance implications.
+     * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
+     * the equivalent singular method for each item provided.
+     * This should not deter you from using this function as there is a performance benefit in situations where the cache store
+     * does support it, but you should be aware of this fact.
+     *
+     * @param array $keys The keys of the data being requested.
+     *      Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
+     *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
+     * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
+     * @return array An array of key value pairs for the items that could be retrieved from the cache.
+     *      If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
+     *      Otherwise any key that did not exist will have a data value of false within the results.
+     * @throws moodle_exception
+     */
+    public function get_many(array $keys, $strictness = IGNORE_MISSING) {
+
+        $parsedkeys = array();
+        $resultpersist = array();
+        $resultstore = array();
+        $keystofind = array();
+
+        // First up check the persist cache for each key.
+        $isusingpersist = $this->is_using_persist_cache();
+        foreach ($keys as $key) {
+            $pkey&nbs