From 07b248886d94c1c6016534c5dd6737a36349be0f Mon Sep 17 00:00:00 2001 From: Brian King Date: Tue, 13 Aug 2013 10:00:46 +0200 Subject: [PATCH 01/23] MDL-29004 Wiki: display images in printer-friendly view --- mod/wiki/pagelib.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mod/wiki/pagelib.php b/mod/wiki/pagelib.php index c0d95da19407d..01c5105dc29a3 100644 --- a/mod/wiki/pagelib.php +++ b/mod/wiki/pagelib.php @@ -2206,8 +2206,14 @@ private function print_pretty_view() { $content = wiki_parse_content($version->contentformat, $version->content, array('printable' => true, 'swid' => $this->subwiki->id, 'pageid' => $this->page->id, 'pretty_print' => true)); + $html = $content['parsed_text']; + $id = $this->subwiki->wikiid; + if ($cm = get_coursemodule_from_instance("wiki", $id)) { + $context = context_module::instance($cm->id); + $html = file_rewrite_pluginfile_urls($html, 'pluginfile.php', $context->id, 'mod_wiki', 'attachments', $this->subwiki->id); + } echo '
'; - echo format_text($content['parsed_text'], FORMAT_HTML); + echo format_text($html, FORMAT_HTML); echo '
'; } } From c68f17dfc5757e7593923f4f757a4944dbb70c01 Mon Sep 17 00:00:00 2001 From: Rossiani Wijaya Date: Fri, 16 Aug 2013 12:14:22 +0800 Subject: [PATCH 02/23] MDL-35930 filepicker filemanaer: make announcement on page change when selecting view files options --- lib/form/filemanager.js | 2 ++ repository/filepicker.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/form/filemanager.js b/lib/form/filemanager.js index 4e8183af9209c..fa0c62e3e5377 100644 --- a/lib/form/filemanager.js +++ b/lib/form/filemanager.js @@ -380,6 +380,8 @@ M.form_filemanager.init = function(Y, options) { } e.currentTarget.addClass('checked') this.render(); + this.filemanager.one('.fp-content').setAttribute('tabIndex', '0'); + this.filemanager.one('.fp-content').focus(); } }, this); }, diff --git a/repository/filepicker.js b/repository/filepicker.js index 5ae305ea13cbf..b13aee502cb7d 100644 --- a/repository/filepicker.js +++ b/repository/filepicker.js @@ -794,6 +794,8 @@ M.core_filepicker.init = function(Y, options) { } else { this.view_as_icons(appenditems); } + this.fpnode.one('.fp-content').setAttribute('tabIndex', '0'); + this.fpnode.one('.fp-content').focus(); // display/hide the link for requesting next page if (!appenditems && this.active_repo.hasmorepages) { if (!this.fpnode.one('.fp-content .fp-nextpage')) { From d0720e784ee094d0390ec38799878eaa99f1837b Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Fri, 16 Aug 2013 10:29:32 +0800 Subject: [PATCH 03/23] MDL-40877 core_text: Added UTF-8 safe strrchr method --- lib/tests/textlib_test.php | 12 ++++++++++++ lib/textlib.class.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/lib/tests/textlib_test.php b/lib/tests/textlib_test.php index 35f500dcd04fa..6ffcf06ff9e1a 100644 --- a/lib/tests/textlib_test.php +++ b/lib/tests/textlib_test.php @@ -366,6 +366,18 @@ public function test_deprecated_textlib_get_instance() { $this->assertSame($textlib->specialtoascii('ábc'), 'abc'); $this->assertSame($textlib->strtotitle('abc ABC'), 'Abc Abc'); } + + /** + * Test strrchr. + */ + public function test_strrchr() { + $str = "Žluťoučký koníček"; + $this->assertSame('koníček', textlib::strrchr($str, 'koní')); + $this->assertSame('Žluťoučký ', textlib::strrchr($str, 'koní', true)); + $this->assertFalse(textlib::strrchr($str, 'A')); + $this->assertFalse(textlib::strrchr($str, 'ç', true)); + } + } diff --git a/lib/textlib.class.php b/lib/textlib.class.php index bddcaa721d635..37351e0ca0ef2 100644 --- a/lib/textlib.class.php +++ b/lib/textlib.class.php @@ -238,6 +238,36 @@ public static function substr($text, $start, $len=null, $charset='utf-8') { return $result; } + /** + * Finds the last occurrence of a character in a string within another. + * UTF-8 ONLY safe mb_strrchr(). + * + * @param string $haystack The string from which to get the last occurrence of needle. + * @param string $needle The string to find in haystack. + * @param boolean $part If true, returns the portion before needle, else return the portion after (including needle). + * @return string|false False when not found. + * @since 2.4.6, 2.5.2, 2.6 + */ + public static function strrchr($haystack, $needle, $part = false) { + + if (function_exists('mb_strrchr')) { + return mb_strrchr($haystack, $needle, $part, 'UTF-8'); + } + + $pos = self::strrpos($haystack, $needle); + if ($pos === false) { + return false; + } + + $length = null; + if ($part) { + $length = $pos; + $pos = 0; + } + + return self::substr($haystack, $pos, $length, 'utf-8'); + } + /** * Multibyte safe strlen() function, uses mbstring or iconv for UTF-8, falls back to typo3. * From db7d0186ee9aff4fabfcb1a2d342030ad87439a2 Mon Sep 17 00:00:00 2001 From: Nadav Kavalerchik Date: Sat, 17 Aug 2013 23:30:39 +0300 Subject: [PATCH 04/23] MDL-40447 Administration: Fix translation: "Profile" should be "Profile Fields" on advanced (more...) user filter form --- user/filters/lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/filters/lib.php b/user/filters/lib.php index 6c6a8395be7f1..72b3d4be94ed1 100644 --- a/user/filters/lib.php +++ b/user/filters/lib.php @@ -111,7 +111,7 @@ function get_field($fieldname, $advanced) { case 'country': return new user_filter_select('country', get_string('country'), $advanced, 'country', get_string_manager()->get_list_of_countries(), $USER->country); case 'confirmed': return new user_filter_yesno('confirmed', get_string('confirmed', 'admin'), $advanced, 'confirmed'); case 'suspended': return new user_filter_yesno('suspended', get_string('suspended', 'auth'), $advanced, 'suspended'); - case 'profile': return new user_filter_profilefield('profile', get_string('profile'), $advanced); + case 'profile': return new user_filter_profilefield('profile', get_string('profilefields', 'admin'), $advanced); case 'courserole': return new user_filter_courserole('courserole', get_string('courserole', 'filters'), $advanced); case 'systemrole': return new user_filter_globalrole('systemrole', get_string('globalrole', 'role'), $advanced); case 'firstaccess': return new user_filter_date('firstaccess', get_string('firstaccess', 'filters'), $advanced, 'firstaccess'); From a62d50ffb88fd2a2def3ef727a07e1e421a4a308 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Tue, 20 Aug 2013 10:21:12 +1000 Subject: [PATCH 05/23] MDL-41222 Remove unnecessary course cache rebuild --- lib/modinfolib.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/modinfolib.php b/lib/modinfolib.php index a507ba613f07a..19367efe4ca7e 100644 --- a/lib/modinfolib.php +++ b/lib/modinfolib.php @@ -242,7 +242,17 @@ public function get_section_info($sectionnumber, $strictness = IGNORE_MISSING) { * @param int $userid User ID */ public function __construct($course, $userid) { - global $CFG, $DB; + global $CFG, $DB, $COURSE, $SITE; + + if (!isset($course->modinfo) || !isset($course->sectioncache)) { + if (!empty($COURSE->id) && $COURSE->id == $course->id) { + $course = $COURSE; + } else if (!empty($SITE->id) && $SITE->id == $course->id) { + $course = $SITE; + } else { + $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); + } + } // Check modinfo field is set. If not, build and load it. if (empty($course->modinfo) || empty($course->sectioncache)) { @@ -1315,7 +1325,7 @@ function get_fast_modinfo($courseorid, $userid = 0, $resetonly = false) { if (is_object($courseorid)) { $course = $courseorid; } else { - $course = (object)array('id' => $courseorid, 'modinfo' => null, 'sectioncache' => null); + $course = (object)array('id' => $courseorid); } // Function is called with $reset = true @@ -1346,14 +1356,6 @@ function get_fast_modinfo($courseorid, $userid = 0, $resetonly = false) { } } - if (!property_exists($course, 'modinfo')) { - debugging('Coding problem - missing course modinfo property in get_fast_modinfo() call'); - } - - if (!property_exists($course, 'sectioncache')) { - debugging('Coding problem - missing course sectioncache property in get_fast_modinfo() call'); - } - unset($cache[$course->id]); // prevent potential reference problems when switching users $cache[$course->id] = new course_modinfo($course, $userid); From 334827b61e5f06e957880b2fd55caeb62b014d5d Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 19 Aug 2013 19:12:39 +0100 Subject: [PATCH 06/23] MDL-36803 TinyMCE: Fix TinyMCE following touch events on iOS browsers --- lib/editor/tinymce/module.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/editor/tinymce/module.js b/lib/editor/tinymce/module.js index eedc3e1146ba5..3a77d5194c932 100644 --- a/lib/editor/tinymce/module.js +++ b/lib/editor/tinymce/module.js @@ -56,6 +56,21 @@ M.editor_tinymce.init_editor = function(Y, editorid, options) { M.cfg.wwwroot + '/lib/editor/tinymce/plugins/' + filedetails[1]); } } + + // We have to override the editor setup to work around a bug in iOS browsers - MDL-36803. + if (Y.UA.ios) { + // Retain any setup which is already defined. + options.originalSetupFunction = options.setup || function(){}; + options.setup = function(editor) { + options.originalSetupFunction(); + editor.onPostRender.add(function(ed) { + // Whenever there is a keydown event, ensure that the contentWindow still have focus. + ed.contentDocument.addEventListener('keydown', function() { + ed.contentWindow.focus(); + }); + }); + }; + } tinyMCE.init(options); var item = document.getElementById(editorid+'_filemanager'); From a8f5f4b6e2f5b47834396c657607174a5ac07bba Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Tue, 13 Aug 2013 09:35:59 +0800 Subject: [PATCH 07/23] MDL-40877 repository_url: Use textlib functions instead of mb_strlen() --- repository/url/locallib.php | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/repository/url/locallib.php b/repository/url/locallib.php index 042350b4951f7..3a47017027484 100644 --- a/repository/url/locallib.php +++ b/repository/url/locallib.php @@ -40,6 +40,8 @@ * See: http://www.opensource.org/licenses/bsd-license.php */ +defined('MOODLE_INTERNAL') || die(); + /** * Combine a base URL and a relative URL to produce a new * absolute URL. The base URL is often the URL of a page, @@ -79,9 +81,9 @@ function url_to_absolute( $baseUrl, $relativeUrl ) if ( $b === FALSE || empty( $b['scheme'] ) || empty( $b['host'] ) ) return FALSE; $r['scheme'] = $b['scheme']; - if (empty($b['path'])) { - $b['path'] = ''; - } + if (empty($b['path'])) { + $b['path'] = ''; + } // If relative URL has an authority, clean path and return. if ( isset( $r['host'] ) ) @@ -110,15 +112,16 @@ function url_to_absolute( $baseUrl, $relativeUrl ) return join_url( $r ); } - // If relative URL path doesn't start with /, merge with base path - if ( $r['path'][0] != '/' ) - { - $base = mb_strrchr( $b['path'], '/', TRUE, 'UTF-8' ); - if ( $base === FALSE ) $base = ''; + // If relative URL path doesn't start with /, merge with base path. + if ($r['path'][0] != '/') { + $base = textlib::strrchr($b['path'], '/', TRUE); + if ($base === FALSE) { + $base = ''; + } $r['path'] = $base . '/' . $r['path']; } - $r['path'] = url_remove_dot_segments( $r['path'] ); - return join_url( $r ); + $r['path'] = url_remove_dot_segments($r['path']); + return join_url($r); } /** @@ -152,12 +155,15 @@ function url_remove_dot_segments( $path ) array_push( $outSegs, $seg ); } $outPath = implode( '/', $outSegs ); - if ( $path[0] == '/' ) + + if ($path[0] == '/') { $outPath = '/' . $outPath; - // compare last multi-byte character against '/' - if ( $outPath != '/' && - (mb_strlen($path)-1) == mb_strrpos( $path, '/', 'UTF-8' ) ) + } + + // Compare last multi-byte character against '/'. + if ($outPath != '/' && (textlib::strlen($path) - 1) == textlib::strrpos($path, '/', 'UTF-8')) { $outPath .= '/'; + } return $outPath; } From 6dc4e89073dcf05896296acce353a156b51f14dd Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Tue, 13 Aug 2013 14:28:09 +0100 Subject: [PATCH 08/23] MDL-38765 quiz view: fix duplicate message. The "No more attempts are allowed" message was being shown twice. I checked back, and it has been like that since before the quiz was converted to use a renderer. Still, it looks horrible, so changing it. --- mod/quiz/renderer.php | 12 ++---------- mod/quiz/upgrade.txt | 6 ++++++ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php index bc50a865ed64e..5eaf858ccdf4c 100644 --- a/mod/quiz/renderer.php +++ b/mod/quiz/renderer.php @@ -690,7 +690,6 @@ public function view_page($course, $quiz, $cm, $context, $viewobj) { $output = ''; $output .= $this->view_information($quiz, $cm, $context, $viewobj->infomessages); $output .= $this->view_table($quiz, $context, $viewobj); - $output .= $this->view_best_score($viewobj); $output .= $this->view_result_info($quiz, $context, $cm, $viewobj); $output .= $this->box($this->view_page_buttons($viewobj), 'quizattempt'); return $output; @@ -1031,17 +1030,10 @@ public function attempt_state($attemptobj) { } /** - * Prints the students best score - * - * @param mod_quiz_view_object $viewobj + * @deprecated Do not use any more. Removed in Moodle 2.5. */ public function view_best_score($viewobj) { - $output = ''; - // Print information about the student's best score for this quiz if possible. - if (!$viewobj->moreattempts) { - $output .= $this->heading(get_string('nomoreattempts', 'quiz')); - } - return $output; + return ''; } /** diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt index cd73830b3386b..35b886b43f284 100644 --- a/mod/quiz/upgrade.txt +++ b/mod/quiz/upgrade.txt @@ -1,6 +1,12 @@ This files describes API changes in the quiz code. +=== 2.4.5 === + +* mod_quiz_renderer::view_best_score should not be used any more. (It did not do + what the name suggested anyway.) + + === 2.4 === * mod_quiz_renderer::finish_review_link now requires $attemptobj to be passed in From 67051eb403f1b92189b020548f0692ba5bf2a55d Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Wed, 21 Aug 2013 12:28:20 +0100 Subject: [PATCH 09/23] MDL-41366 qbehaviour_informationitem fix name capitalisation. --- .../informationitem/lang/en/qbehaviour_informationitem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/question/behaviour/informationitem/lang/en/qbehaviour_informationitem.php b/question/behaviour/informationitem/lang/en/qbehaviour_informationitem.php index 8fef1d73acdad..f0869b86c50c0 100644 --- a/question/behaviour/informationitem/lang/en/qbehaviour_informationitem.php +++ b/question/behaviour/informationitem/lang/en/qbehaviour_informationitem.php @@ -23,5 +23,5 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['pluginname'] = 'behaviour for information items'; +$string['pluginname'] = 'Behaviour for information items'; $string['seen'] = 'Seen'; \ No newline at end of file From f0a60c5f2f8e03eec7fcc06963ac24abf967878b Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Thu, 22 Aug 2013 17:12:30 +0800 Subject: [PATCH 10/23] MDL-41387 tags: changed character case in string used when deleting a course --- lang/en/tag.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/en/tag.php b/lang/en/tag.php index 3521f24fb831f..eef906d21217a 100644 --- a/lang/en/tag.php +++ b/lang/en/tag.php @@ -30,7 +30,7 @@ $string['count'] = 'Count'; $string['delete'] = 'Delete'; $string['deleted'] = 'Deleted'; -$string['deletedcoursetags'] = 'Deleted - course tags'; +$string['deletedcoursetags'] = 'Deleted - Course tags'; $string['description'] = 'Description'; $string['edittag'] = 'Edit this tag'; $string['entertags'] = 'Enter tags separated by commas'; From 95899cab2fbd25510f21eaa6600bccd0eb8d9ed3 Mon Sep 17 00:00:00 2001 From: Dan Poltawski Date: Fri, 23 Aug 2013 15:09:12 +0800 Subject: [PATCH 11/23] MDL-41401 backup: fix misnamed test method --- backup/util/structure/tests/baseoptiogroup_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backup/util/structure/tests/baseoptiogroup_test.php b/backup/util/structure/tests/baseoptiogroup_test.php index 7d70be9047b5c..773a15639608f 100644 --- a/backup/util/structure/tests/baseoptiogroup_test.php +++ b/backup/util/structure/tests/baseoptiogroup_test.php @@ -69,7 +69,7 @@ function test_creation() { /** * Incorrect creation tests (attributes and final elements) */ - function itest_wrong_creation() { + function test_wrong_creation() { // Create instance with invalid name try { From 91b9fb37491492b546c681541d9fd8dae5ecce86 Mon Sep 17 00:00:00 2001 From: Matteo Scaramuccia Date: Sun, 25 Aug 2013 14:00:47 +0200 Subject: [PATCH 12/23] MDL-41410 Caching: move comment to the appropriate place. MDL-38162 fixes the issue about testing the connection to the memcached daemon, which means testing once the connection to the pool of servers and not to each of the servers while being added to the pool. It misses to move the comment to that code too. --- cache/stores/memcache/lib.php | 2 +- cache/stores/memcached/lib.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cache/stores/memcache/lib.php b/cache/stores/memcache/lib.php index 2e104bd6385dd..1569da6bd8044 100644 --- a/cache/stores/memcache/lib.php +++ b/cache/stores/memcache/lib.php @@ -132,8 +132,8 @@ public function __construct($name, array $configuration = array()) { $this->connection = new Memcache; foreach ($this->servers as $server) { $this->connection->addServer($server[0], $server[1], true, $server[2]); - // Test the connection to this server. } + // Test the connection to the pool of servers. $this->isready = @$this->connection->set($this->parse_key('ping'), 'ping', MEMCACHE_COMPRESSED, 1); } diff --git a/cache/stores/memcached/lib.php b/cache/stores/memcached/lib.php index a74dc83e053b0..508628e38838f 100644 --- a/cache/stores/memcached/lib.php +++ b/cache/stores/memcached/lib.php @@ -141,6 +141,7 @@ public function __construct($name, array $configuration = array()) { } $this->connection->addServers($this->servers); } + // Test the connection to the pool of servers. $this->isready = @$this->connection->set("ping", 'ping', 1); } From 56f24fadd4198b91f24e091710189a6210e20b77 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Sun, 25 Aug 2013 21:20:53 +0800 Subject: [PATCH 13/23] MDL-41249 assign: Restore step missing gradingform mapping Thanks to Matthieu Raggett for the report and the solution. --- mod/assign/backup/moodle2/restore_assign_stepslib.php | 1 + 1 file changed, 1 insertion(+) diff --git a/mod/assign/backup/moodle2/restore_assign_stepslib.php b/mod/assign/backup/moodle2/restore_assign_stepslib.php index 6053447d03b2c..84b8eea8f0596 100644 --- a/mod/assign/backup/moodle2/restore_assign_stepslib.php +++ b/mod/assign/backup/moodle2/restore_assign_stepslib.php @@ -124,6 +124,7 @@ protected function process_assign_submission($data) { // Note - the old contextid is required in order to be able to restore files stored in // sub plugin file areas attached to the submissionid $this->set_mapping('submission', $oldid, $newitemid, false, null, $this->task->get_old_contextid()); + $this->set_mapping(restore_gradingform_plugin::itemid_mapping('submissions'), $oldid, $newitemid); } /** From 2254ea48a9e9f833523515c91daf58c10eee300e Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Fri, 23 Aug 2013 10:00:48 +1200 Subject: [PATCH 14/23] MDL-41213 filepicker: corrected outline of selected file dialogue --- theme/base/style/core.css | 1 + 1 file changed, 1 insertion(+) diff --git a/theme/base/style/core.css b/theme/base/style/core.css index 3a91681d7dcf9..71c05fbe83db7 100644 --- a/theme/base/style/core.css +++ b/theme/base/style/core.css @@ -870,6 +870,7 @@ sup {vertical-align: super;} } .moodle-dialogue-base .moodle-dialogue { + outline: #000 dotted 0; background: none!important; border: 0 none!important; } From 426e9c79ac1f2e40ae7841319f71266e07a842cf Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Mon, 26 Aug 2013 09:15:57 +1200 Subject: [PATCH 15/23] MDL-41106 cache: several fixes for the session cache. This issue makes several fixes for the session loader and the session store. * maxsize argument now works for session caches. * fixed performance hole when interation occurs frequently. * fixed cache purge bug occuring when multiple caches are defined before being used. * improved lastaccess handling. Big thanks to Marina who contributed the following commits: * Always make sure the elements in cache are sorted so we need to remove only elements in the beginning of array * Remove expired elements from session store to free memory * Minor bug fixes --- cache/classes/loaders.php | 333 ++++++++++++++++++++--------------- cache/stores/session/lib.php | 68 ++++++- cache/tests/cache_test.php | 91 +++++++++- cache/tests/fixtures/lib.php | 18 ++ 4 files changed, 359 insertions(+), 151 deletions(-) diff --git a/cache/classes/loaders.php b/cache/classes/loaders.php index da8fe6758bf13..6de94e299b342 100644 --- a/cache/classes/loaders.php +++ b/cache/classes/loaders.php @@ -271,7 +271,7 @@ public function set_identifiers(array $identifiers) { * 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 + * @throws coding_exception */ public function get($key, $strictness = IGNORE_MISSING) { // 1. Parse the key. @@ -329,7 +329,7 @@ public function get($key, $strictness = IGNORE_MISSING) { } // 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.'); + throw new coding_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) { @@ -363,7 +363,7 @@ public function get($key, $strictness = IGNORE_MISSING) { * @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 + * @throws coding_exception */ public function get_many(array $keys, $strictness = IGNORE_MISSING) { @@ -420,7 +420,7 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) { $missingkeys = array(); foreach ($result as $key => $value) { if ($value === false) { - $missingkeys[] = ($usingloader) ? $key : $parsedkeys[$key]; + $missingkeys[] = $parsedkeys[$key]; } } if (!empty($missingkeys)) { @@ -430,11 +430,9 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) { $resultmissing = $this->datasource->load_many_for_cache($missingkeys); } foreach ($resultmissing as $key => $value) { - $pkey = ($usingloader) ? $key : $keysparsed[$key]; - $realkey = ($usingloader) ? $parsedkeys[$key] : $key; - $result[$pkey] = $value; + $result[$keysparsed[$key]] = $value; if ($value !== false) { - $this->set($realkey, $value); + $this->set($key, $value); } } unset($resultmissing); @@ -453,7 +451,7 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) { if ($strictness === MUST_EXIST) { foreach ($keys as $key) { if (!array_key_exists($key, $fullresult)) { - throw new moodle_exception('Not all the requested keys existed within the cache stores.'); + throw new coding_exception('Not all the requested keys existed within the cache stores.'); } } } @@ -483,6 +481,11 @@ public function set($key, $data) { if ($this->perfdebug) { cache_helper::record_cache_set($this->storetype, $this->definition->get_id()); } + if ($this->loader !== false) { + // We have a loader available set it there as well. + // We have to let the loader do its own parsing of data as it may be unique. + $this->loader->set($key, $data); + } if (is_object($data) && $data instanceof cacheable_object) { $data = new cache_cached_object($data); } else if (!is_scalar($data)) { @@ -506,6 +509,7 @@ public function set($key, $data) { * Removes references where required. * * @param stdClass|array $data + * @return mixed What ever was put in but without any references. */ protected function unref($data) { if ($this->definition->uses_simple_data()) { @@ -593,6 +597,11 @@ protected function deep_clone($value) { * ... if they care that is. */ public function set_many(array $keyvaluearray) { + if ($this->loader !== false) { + // We have a loader available set it there as well. + // We have to let the loader do its own parsing of data as it may be unique. + $this->loader->set_many($keyvaluearray); + } $data = array(); $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl(); $usepersistcache = $this->is_using_persist_cache(); @@ -858,7 +867,7 @@ protected function get_store() { * Returns the loader associated with this instance. * * @since 2.4.4 - * @return cache_loader|false + * @return cache|false */ protected function get_loader() { return $this->loader; @@ -1340,7 +1349,6 @@ public function set_many(array $keyvaluearray) { * @param string|int $key The key for the data being requested. * @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) { if ($this->requirelockingread && $this->check_lock_state($key) === false) { @@ -1364,7 +1372,7 @@ public function get($key, $strictness = IGNORE_MISSING) { * @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 + * @throws coding_exception */ public function get_many(array $keys, $strictness = IGNORE_MISSING) { if ($this->requirelockingread) { @@ -1458,6 +1466,7 @@ public function delete_many(array $keys, $recurse = true) { * @todo we should support locking in the session as well. Should be pretty simple to set up. * * @internal don't use me directly. + * @method cache_store|cache_is_searchable get_store() Returns the cache store which must implement both cache_is_searchable. * * @package core * @category cache @@ -1494,6 +1503,11 @@ class cache_session extends cache { */ const KEY_PREFIX = 'sess_'; + /** + * This is the key used to track last access. + */ + const LASTACCESS = '__lastaccess__'; + /** * Override the cache::construct method. * @@ -1507,12 +1521,15 @@ class cache_session extends cache { * @param cache_definition $definition * @param cache_store $store * @param cache_loader|cache_data_source $loader - * @return void */ public function __construct(cache_definition $definition, cache_store $store, $loader = null) { // First up copy the loadeduserid to the current user id. $this->currentuserid = self::$loadeduserid; parent::__construct($definition, $store, $loader); + + // This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place. + $this->set(self::LASTACCESS, cache::now()); + if ($definition->has_invalidation_events()) { $lastinvalidation = $this->get('lastsessioninvalidation'); if ($lastinvalidation === false) { @@ -1561,6 +1578,21 @@ public function __construct(cache_definition $definition, cache_store $store, $l } } + /** + * Sets the session id for the loader. + */ + protected function set_session_id() { + $this->sessionid = preg_replace('#[^a-zA-Z0-9_]#', '_', session_id()); + } + + /** + * Returns the prefix used for all keys. + * @return string + */ + protected function get_key_prefix() { + return 'u'.$this->currentuserid.'_'.$this->sessionid; + } + /** * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store. * @@ -1573,17 +1605,18 @@ public function __construct(cache_definition $definition, cache_store $store, $l * @return string|array String unless the store supports multi-identifiers in which case an array if returned. */ protected function parse_key($key) { - if ($key === 'lastaccess') { - $key = '__lastaccess__'; + $prefix = $this->get_key_prefix(); + if ($key === self::LASTACCESS) { + return $key.$prefix; } - return 'sess_'.parent::parse_key($key); + return $prefix.'_'.parent::parse_key($key); } /** * Check that this cache instance is tracking the current user. */ protected function check_tracked_user() { - if (isset($_SESSION['USER']->id)) { + if (isset($_SESSION['USER']->id) && $_SESSION['USER']->id !== null) { // Get the id of the current user. $new = $_SESSION['USER']->id; } else { @@ -1597,55 +1630,25 @@ protected function check_tracked_user() { // This way we don't bloat the session. $this->purge(); // Update the session id just in case! - $this->sessionid = session_id(); + $this->set_session_id(); } self::$loadeduserid = $new; $this->currentuserid = $new; } else if ($new !== $this->currentuserid) { // The current user matches the loaded user but not the user last used by this cache. - $this->purge(); + $this->purge_current_user(); $this->currentuserid = $new; // Update the session id just in case! - $this->sessionid = session_id(); + $this->set_session_id(); } } /** - * Gets the session data. - * - * @param bool $force If true the session data will be loaded from the store again. - * @return array An array of session data. - */ - protected function get_session_data($force = false) { - if ($this->sessionid === null) { - $this->sessionid = session_id(); - } - if (is_array($this->session) && !$force) { - return $this->session; - } - $session = parent::get($this->sessionid); - if ($session === false) { - $session = array(); - } - // We have to write here to ensure that the lastaccess time is recorded. - // And also in order to ensure the session entry exists as when we save it on __destruct - // $CFG is likely to have already been destroyed. - $this->save_session($session); - return $this->session; - } - - /** - * Saves the session data. - * - * This function also updates the last access time. - * - * @param array $session - * @return bool + * Purges the session cache of all data belonging to the current user. */ - protected function save_session(array $session) { - $session['lastaccess'] = time(); - $this->session = $session; - return parent::set($this->sessionid, $this->session); + public function purge_current_user() { + $keys = $this->get_store()->find_all($this->get_key_prefix()); + $this->get_store()->delete_many($keys); } /** @@ -1656,7 +1659,7 @@ protected function save_session(array $session) { * 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 + * @throws coding_exception */ public function get($key, $strictness = IGNORE_MISSING) { // Check the tracked user. @@ -1664,10 +1667,8 @@ public function get($key, $strictness = IGNORE_MISSING) { // 2. Parse the key. $parsedkey = $this->parse_key($key); // 3. Get it from the store. - $result = false; - $session = $this->get_session_data(); - if (array_key_exists($parsedkey, $session)) { - $result = $session[$parsedkey]; + $result = $this->get_store()->get($parsedkey); + if ($result !== false) { if ($result instanceof cache_ttl_wrapper) { if ($result->has_expired()) { $this->get_store()->delete($parsedkey); @@ -1681,10 +1682,9 @@ public function get($key, $strictness = IGNORE_MISSING) { } } // 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('**static session**', $this->get_definition()->get_id()); + cache_helper::record_cache_miss($this->storetype, $this->get_definition()->get_id()); } if ($this->get_loader() !== false) { // We must pass the original (unparsed) key to the next loader in the chain. @@ -1694,19 +1694,18 @@ public function get($key, $strictness = IGNORE_MISSING) { } else if ($this->get_datasource() !== false) { $result = $this->get_datasource()->load_for_cache($key); } - $setaftervalidation = ($result !== false); + // 5. Set it to the store if we got it from the loader/datasource. + if ($result !== false) { + $this->set($key, $result); + } } else if ($this->perfdebug) { - cache_helper::record_cache_hit('**static session**', $this->get_definition()->get_id()); + cache_helper::record_cache_hit($this->storetype, $this->get_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); + throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.'); } - // 7. Make sure we don't pass back anything that could be a reference. + // 6. 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. @@ -1737,8 +1736,14 @@ public function get($key, $strictness = IGNORE_MISSING) { */ public function set($key, $data) { $this->check_tracked_user(); + $loader = $this->get_loader(); + if ($loader !== false) { + // We have a loader available set it there as well. + // We have to let the loader do its own parsing of data as it may be unique. + $loader->set($key, $data); + } if ($this->perfdebug) { - cache_helper::record_cache_set('**static session**', $this->get_definition()->get_id()); + cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id()); } if (is_object($data) && $data instanceof cacheable_object) { $data = new cache_cached_object($data); @@ -1750,12 +1755,10 @@ public function set($key, $data) { $data = $this->unref($data); } // We dont' support native TTL here as we consolidate data for sessions. - if ($this->has_a_ttl()) { + if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) { $data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl()); } - $session = $this->get_session_data(); - $session[$this->parse_key($key)] = $data; - return $this->save_session($session); + return $this->get_store()->set($this->parse_key($key), $data); } /** @@ -1767,15 +1770,12 @@ public function set($key, $data) { * @return bool True of success, false otherwise. */ public function delete($key, $recurse = true) { - $this->check_tracked_user(); $parsedkey = $this->parse_key($key); if ($recurse && $this->get_loader() !== false) { // Delete from the bottom of the stack first. $this->get_loader()->delete($key, $recurse); } - $session = $this->get_session_data(); - unset($session[$parsedkey]); - return $this->save_session($session); + return $this->get_store()->delete($parsedkey); } /** @@ -1794,15 +1794,72 @@ public function delete($key, $recurse = true) { * @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 + * @throws coding_exception */ public function get_many(array $keys, $strictness = IGNORE_MISSING) { $this->check_tracked_user(); - $return = array(); + $parsedkeys = array(); + $keymap = array(); foreach ($keys as $key) { - $return[$key] = $this->get($key, $strictness); + $parsedkey = $this->parse_key($key); + $parsedkeys[$key] = $parsedkey; + $keymap[$parsedkey] = $key; + } + $result = $this->get_store()->get_many($parsedkeys); + $return = array(); + $missingkeys = array(); + $hasmissingkeys = false; + foreach ($result as $parsedkey => $value) { + $key = $keymap[$parsedkey]; + if ($value instanceof cache_ttl_wrapper) { + /* @var cache_ttl_wrapper $value */ + if ($value->has_expired()) { + $this->delete($keymap[$parsedkey]); + $value = false; + } else { + $value = $value->data; + } + } + if ($value instanceof cache_cached_object) { + /* @var cache_cached_object $value */ + $value = $value->restore_object(); + } + $return[$key] = $value; + if ($value === false) { + $hasmissingkeys = true; + $missingkeys[$parsedkey] = $key; + } + } + if ($hasmissingkeys) { + // We've got missing keys - we've got to check any loaders or data sources. + $loader = $this->get_loader(); + $datasource = $this->get_datasource(); + if ($loader !== false) { + foreach ($loader->get_many($missingkeys) as $key => $value) { + if ($value !== false) { + $return[$key] = $value; + unset($missingkeys[$parsedkeys[$key]]); + } + } + } + $hasmissingkeys = count($missingkeys) > 0; + if ($datasource !== false && $hasmissingkeys) { + // We're still missing keys but we've got a datasource. + foreach ($datasource->load_many_for_cache($missingkeys) as $key => $value) { + if ($value !== false) { + $return[$key] = $value; + unset($missingkeys[$parsedkeys[$key]]); + } + } + $hasmissingkeys = count($missingkeys) > 0; + } } + if ($hasmissingkeys && $strictness === MUST_EXIST) { + throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.'); + } + return $return; + } /** @@ -1814,18 +1871,12 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) { * @return int The number of items successfully deleted. */ public function delete_many(array $keys, $recurse = true) { - $this->check_tracked_user(); $parsedkeys = array_map(array($this, 'parse_key'), $keys); if ($recurse && $this->get_loader() !== false) { // Delete from the bottom of the stack first. $this->get_loader()->delete_many($keys, $recurse); } - $session = $this->get_session_data(); - foreach ($parsedkeys as $parsedkey) { - unset($session[$parsedkey]); - } - $this->save_session($session); - return count($keys); + return $this->get_store()->delete_many($parsedkeys); } /** @@ -1853,8 +1904,15 @@ public function delete_many(array $keys, $recurse = true) { */ public function set_many(array $keyvaluearray) { $this->check_tracked_user(); - $session = $this->get_session_data(); - $simulatettl = $this->has_a_ttl(); + $loader = $this->get_loader(); + if ($loader !== false) { + // We have a loader available set it there as well. + // We have to let the loader do its own parsing of data as it may be unique. + $loader->set_many($keyvaluearray); + } + $data = array(); + $definitionid = $this->get_definition()->get_ttl(); + $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl(); foreach ($keyvaluearray as $key => $value) { if (is_object($value) && $value instanceof cacheable_object) { $value = new cache_cached_object($value); @@ -1866,16 +1924,17 @@ public function set_many(array $keyvaluearray) { $value = $this->unref($value); } if ($simulatettl) { - $value = new cache_ttl_wrapper($value, $this->get_definition()->get_ttl()); + $value = new cache_ttl_wrapper($value, $definitionid); } - $parsedkey = $this->parse_key($key); - $session[$parsedkey] = $value; + $data[$key] = array( + 'key' => $this->parse_key($key), + 'value' => $value + ); } if ($this->perfdebug) { - cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id()); + cache_helper::record_cache_set($this->storetype, $definitionid); } - $this->save_session($session); - return count($keyvaluearray); + return $this->get_store()->set_many($data); } /** @@ -1884,13 +1943,9 @@ public function set_many(array $keyvaluearray) { * @return bool True on success, false otherwise */ public function purge() { - // 1. Purge the session object. - $this->session = array(); - // 2. Delete the record for this users session from the store. - $this->get_store()->delete($this->sessionid); - // 3. Optionally purge any stacked loaders in the same way. + $this->get_store()->purge(); if ($this->get_loader()) { - $this->get_loader()->delete($this->sessionid); + $this->get_loader()->purge(); } return true; } @@ -1919,21 +1974,27 @@ public function purge() { public function has($key, $tryloadifpossible = false) { $this->check_tracked_user(); $parsedkey = $this->parse_key($key); - $session = $this->get_session_data(); - $has = false; - if ($this->has_a_ttl()) { + $store = $this->get_store(); + if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) { // The data has a TTL and the store doesn't support it natively. // We must fetch the data and expect a ttl wrapper. - if (array_key_exists($parsedkey, $session)) { - $data = $session[$parsedkey]; - $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired()); - } + $data = $store->get($parsedkey); + $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired()); + } else if (!$this->store_supports_key_awareness()) { + // The store doesn't support key awareness, get the data and check it manually... puke. + // Either no TTL is set of the store supports its handling natively. + $data = $store->get($parsedkey); + $has = ($data !== false); } else { - $has = array_key_exists($parsedkey, $session); + // The store supports key awareness, this is easy! + // Either no TTL is set of the store supports its handling natively. + /* @var cache_store|cache_is_key_aware $store */ + $has = $store->has($parsedkey); } if (!$has && $tryloadifpossible) { + $result = null; if ($this->get_loader() !== false) { - $result = $this->get_loader()->get($key); + $result = $this->get_loader()->get($parsedkey); } else if ($this->get_datasource() !== null) { $result = $this->get_datasource()->load_for_cache($key); } @@ -1960,25 +2021,18 @@ public function has($key, $tryloadifpossible = false) { */ public function has_all(array $keys) { $this->check_tracked_user(); - $session = $this->get_session_data(); - foreach ($keys as $key) { - $has = false; - $parsedkey = $this->parse_key($key); - if ($this->has_a_ttl()) { - // The data has a TTL and the store doesn't support it natively. - // We must fetch the data and expect a ttl wrapper. - if (array_key_exists($parsedkey, $session)) { - $data = $session[$parsedkey]; - $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired()); + if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) { + foreach ($keys as $key) { + if (!$this->has($key)) { + return false; } - } else { - $has = array_key_exists($parsedkey, $session); - } - if (!$has) { - return false; } + return true; } - return true; + // The cache must be key aware and if support native ttl if it a ttl is set. + /* @var cache_store|cache_is_key_aware $store */ + $store = $this->get_store(); + return $store->has_all(array_map(array($this, 'parse_key'), $keys)); } /** @@ -1995,26 +2049,17 @@ public function has_all(array $keys) { * @return bool True if the cache has at least one of the given keys */ public function has_any(array $keys) { - $this->check_tracked_user(); - $session = $this->get_session_data(); - foreach ($keys as $key) { - $has = false; - $parsedkey = $this->parse_key($key); - if ($this->has_a_ttl()) { - // The data has a TTL and the store doesn't support it natively. - // We must fetch the data and expect a ttl wrapper. - if (array_key_exists($parsedkey, $session)) { - $data = $session[$parsedkey]; - $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired()); + if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) { + foreach ($keys as $key) { + if ($this->has($key)) { + return true; } - } else { - $has = array_key_exists($parsedkey, $session); - } - if ($has) { - return true; } + return false; } - return false; + /* @var cache_store|cache_is_key_aware $store */ + $store = $this->get_store(); + return $store->has_any(array_map(array($this, 'parse_key'), $keys)); } /** diff --git a/cache/stores/session/lib.php b/cache/stores/session/lib.php index 801dfcb3ee868..d1f735d3b5917 100644 --- a/cache/stores/session/lib.php +++ b/cache/stores/session/lib.php @@ -190,8 +190,9 @@ public static function is_supported_mode($mode) { */ public function initialise(cache_definition $definition) { $this->storeid = $definition->generate_definition_hash(); - $this->store = &self::register_store_id($definition->get_id()); + $this->store = &self::register_store_id($this->name.'-'.$definition->get_id()); $this->ttl = $definition->get_ttl(); + $this->check_ttl(); } /** @@ -219,10 +220,13 @@ public function is_ready() { */ public function get($key) { if (isset($this->store[$key])) { - if ($this->ttl == 0) { + if ($this->ttl === 0) { return $this->store[$key][0]; } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) { return $this->store[$key][0]; + } else { + // Element is present but has expired. + $this->check_ttl(); } } return false; @@ -239,10 +243,12 @@ public function get($key) { */ public function get_many($keys) { $return = array(); + $maxtime = 0; if ($this->ttl != 0) { $maxtime = cache::now() - $this->ttl; } + $hasexpiredelements = false; foreach ($keys as $key) { $return[$key] = false; if (isset($this->store[$key])) { @@ -250,9 +256,15 @@ public function get_many($keys) { $return[$key] = $this->store[$key][0]; } else if ($this->store[$key][1] >= $maxtime) { $return[$key] = $this->store[$key][0]; + } else { + $hasexpiredelements = true; } } } + if ($hasexpiredelements) { + // There are some elements that are present but have expired. + $this->check_ttl(); + } return $return; } @@ -264,8 +276,8 @@ public function get_many($keys) { * @return bool True if the operation was a success false otherwise. */ public function set($key, $data) { - if ($this->ttl == 0) { - $this->store[$key][0] = $data; + if ($this->ttl === 0) { + $this->store[$key] = array($data, 0); } else { $this->store[$key] = array($data, cache::now()); } @@ -283,8 +295,14 @@ public function set($key, $data) { public function set_many(array $keyvaluearray) { $count = 0; foreach ($keyvaluearray as $pair) { - $this->set($pair['key'], $pair['value']); + $key = $pair['key']; + $data = $pair['value']; $count++; + if ($this->ttl === 0) { + $this->store[$key] = array($data, 0); + } else { + $this->store[$key] = array($data, cache::now()); + } } return $count; } @@ -313,6 +331,7 @@ public function has($key) { * @return bool */ public function has_all(array $keys) { + $maxtime = 0; if ($this->ttl != 0) { $maxtime = cache::now() - $this->ttl; } @@ -335,6 +354,7 @@ public function has_all(array $keys) { * @return bool */ public function has_any(array $keys) { + $maxtime = 0; if ($this->ttl != 0) { $maxtime = cache::now() - $this->ttl; } @@ -354,6 +374,9 @@ public function has_any(array $keys) { * @return bool Returns true if the operation was a success, false otherwise. */ public function delete($key) { + if (!isset($this->store[$key])) { + return false; + } unset($this->store[$key]); return true; } @@ -365,6 +388,7 @@ public function delete($key) { * @return int The number of items successfully deleted. */ public function delete_many(array $keys) { + // The number of items that have been successfully deleted. $count = 0; foreach ($keys as $key) { unset($this->store[$key]); @@ -420,12 +444,37 @@ public function my_name() { return $this->name; } + /** + * Removes expired elements. + * @return int number of removed elements + */ + protected function check_ttl() { + if ($this->ttl === 0) { + return 0; + } + $maxtime = cache::now() - $this->ttl; + $count = 0; + for ($value = reset($this->store); $value !== false; $value = next($this->store)) { + if ($value[1] >= $maxtime) { + // We know that elements are sorted by ttl so no need to continue. + break; + } + $count++; + } + if ($count) { + // Remove first $count elements as they are expired. + $this->store = array_slice($this->store, $count, null, true); + } + return $count; + } + /** * Finds all of the keys being stored in the cache store instance. * * @return array */ public function find_all() { + $this->check_ttl(); return array_keys($this->store); } @@ -433,6 +482,7 @@ public function find_all() { * Finds all of the keys whose keys start with the given prefix. * * @param string $prefix + * @return array An array of keys. */ public function find_by_prefix($prefix) { $return = array(); @@ -443,4 +493,12 @@ public function find_by_prefix($prefix) { } return $return; } + + /** + * This store supports native TTL handling. + * @return bool + */ + public function store_supports_native_ttl() { + return true; + } } diff --git a/cache/tests/cache_test.php b/cache/tests/cache_test.php index 26a331d84407f..9d36ef6a1f2df 100644 --- a/cache/tests/cache_test.php +++ b/cache/tests/cache_test.php @@ -1109,9 +1109,9 @@ public function test_disable() { } /** - * Test that multiple loaders work ok. + * Test that multiple application loaders work ok. */ - public function test_multiple_loaders() { + public function test_multiple_application_loaders() { $instance = cache_config_phpunittest::instance(true); $instance->phpunit_add_file_store('phpunittest1'); $instance->phpunit_add_file_store('phpunittest2'); @@ -1156,6 +1156,93 @@ public function test_multiple_loaders() { $this->assertFalse($result['a']); $this->assertEquals('B', $result['b']); $this->assertFalse($result['c']); + + // Test non-recursive deletes. + $this->assertTrue($cache->set('test', 'test')); + $this->assertSame('test', $cache->get('test')); + $this->assertTrue($cache->delete('test', false)); + // We should still have it on a deeper loader. + $this->assertSame('test', $cache->get('test')); + // Test non-recusive with many functions. + $this->assertSame(3, $cache->set_many(array( + 'one' => 'one', + 'two' => 'two', + 'three' => 'three' + ))); + $this->assertSame('one', $cache->get('one')); + $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three'))); + $this->assertSame(3, $cache->delete_many(array('one', 'two', 'three'), false)); + $this->assertSame('one', $cache->get('one')); + $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three'))); + } + + /** + * Test that multiple application loaders work ok. + */ + public function test_multiple_session_loaders() { + /* @var cache_config_phpunittest $instance */ + $instance = cache_config_phpunittest::instance(true); + $instance->phpunit_add_session_store('phpunittest1'); + $instance->phpunit_add_session_store('phpunittest2'); + $instance->phpunit_add_definition('phpunit/multi_loader', array( + 'mode' => cache_store::MODE_SESSION, + 'component' => 'phpunit', + 'area' => 'multi_loader' + )); + $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest1', 3); + $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest2', 2); + + $cache = cache::make('phpunit', 'multi_loader'); + $this->assertInstanceOf('cache_session', $cache); + $this->assertFalse($cache->get('test')); + $this->assertTrue($cache->set('test', 'test')); + $this->assertEquals('test', $cache->get('test')); + $this->assertTrue($cache->delete('test')); + $this->assertFalse($cache->get('test')); + $this->assertTrue($cache->set('test', 'test')); + $this->assertTrue($cache->purge()); + $this->assertFalse($cache->get('test')); + + // Test the many commands. + $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C'))); + $result = $cache->get_many(array('a', 'b', 'c')); + $this->assertInternalType('array', $result); + $this->assertCount(3, $result); + $this->assertArrayHasKey('a', $result); + $this->assertArrayHasKey('b', $result); + $this->assertArrayHasKey('c', $result); + $this->assertEquals('A', $result['a']); + $this->assertEquals('B', $result['b']); + $this->assertEquals('C', $result['c']); + $this->assertEquals($result, $cache->get_many(array('a', 'b', 'c'))); + $this->assertEquals(2, $cache->delete_many(array('a', 'c'))); + $result = $cache->get_many(array('a', 'b', 'c')); + $this->assertInternalType('array', $result); + $this->assertCount(3, $result); + $this->assertArrayHasKey('a', $result); + $this->assertArrayHasKey('b', $result); + $this->assertArrayHasKey('c', $result); + $this->assertFalse($result['a']); + $this->assertEquals('B', $result['b']); + $this->assertFalse($result['c']); + + // Test non-recursive deletes. + $this->assertTrue($cache->set('test', 'test')); + $this->assertSame('test', $cache->get('test')); + $this->assertTrue($cache->delete('test', false)); + // We should still have it on a deeper loader. + $this->assertSame('test', $cache->get('test')); + // Test non-recusive with many functions. + $this->assertSame(3, $cache->set_many(array( + 'one' => 'one', + 'two' => 'two', + 'three' => 'three' + ))); + $this->assertSame('one', $cache->get('one')); + $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three'))); + $this->assertSame(3, $cache->delete_many(array('one', 'two', 'three'), false)); + $this->assertSame('one', $cache->get('one')); + $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three'))); } /** diff --git a/cache/tests/fixtures/lib.php b/cache/tests/fixtures/lib.php index b68bfd4c8fdd7..fda991f703ad9 100644 --- a/cache/tests/fixtures/lib.php +++ b/cache/tests/fixtures/lib.php @@ -86,6 +86,24 @@ public function phpunit_add_file_store($name) { ); } + /** + * Forcefully adds a session store. + * + * @param string $name + */ + public function phpunit_add_session_store($name) { + $this->configstores[$name] = array( + 'name' => $name, + 'plugin' => 'session', + 'configuration' => array(), + 'features' => 14, + 'modes' => 2, + 'default' => true, + 'class' => 'cachestore_session', + 'lock' => 'cachelock_file_default', + ); + } + /** * Forcefully injects a definition => store mapping. * From ab2d7d4f3d863f80c0affb57f5f78b5394c6a5c6 Mon Sep 17 00:00:00 2001 From: Russell Smith Date: Mon, 19 Aug 2013 20:18:57 +1000 Subject: [PATCH 16/23] MDL-41291 cache: Use fread to avoid race conditions --- cache/stores/file/lib.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/cache/stores/file/lib.php b/cache/stores/file/lib.php index 7ef6531b0f19a..94f81626e3a4e 100644 --- a/cache/stores/file/lib.php +++ b/cache/stores/file/lib.php @@ -352,23 +352,19 @@ public function get($key) { if (!$readfile) { return false; } - // Check the filesize first, likely not needed but important none the less. - $filesize = filesize($file); - if (!$filesize) { - return false; - } - // Open ensuring the file for writing, truncating it and setting the pointer to the start. + // Open ensuring the file for reading in binary format. if (!$handle = fopen($file, 'rb')) { return false; } // Lock it up! // We don't care if this succeeds or not, on some systems it will, on some it won't, meah either way. flock($handle, LOCK_SH); - // HACK ALERT - // There is a problem when reading from the file during PHPUNIT tests. For one reason or another the filesize is not correct - // Doesn't happen during normal operation, just during unit tests. - // Read it. - $data = fread($handle, $filesize+128); + $data = ''; + // Read the data in 1Mb chunks. Small caches will not loop more than once. We don't use filesize as it may + // be cached with a different value than what we need to read from the file. + do { + $data .= fread($handle, 1048576); + } while (!feof($handle)); // Unlock it. flock($handle, LOCK_UN); // Return it unserialised. From 81ff8adcc5d79f530eabca5f6a97360ca4156241 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Mon, 26 Aug 2013 09:34:15 +0800 Subject: [PATCH 17/23] MDL-36803 tinymce: Version bump for js changes --- lib/editor/tinymce/version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/editor/tinymce/version.php b/lib/editor/tinymce/version.php index aab5446d16cc9..47beb034bf052 100644 --- a/lib/editor/tinymce/version.php +++ b/lib/editor/tinymce/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2012112900; // The current plugin version (Date: YYYYMMDDXX) +$plugin->version = 2012112901; // The current plugin version (Date: YYYYMMDDXX) $plugin->requires = 2012112900; // Requires this Moodle version $plugin->component = 'editor_tinymce'; // Full name of the plugin (used for diagnostics) $plugin->release = '3.5.7b'; // This is NOT a directory name, see lib.php if you need to know where is the editor code! From 0534f2dd9b93faa4167aac527790ea7885a59844 Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Thu, 22 Aug 2013 16:23:26 +0800 Subject: [PATCH 18/23] MDL-36126 backup: ensure the backup_logs message does not exceed the maximum allowed length in the DB --- backup/util/loggers/database_logger.class.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backup/util/loggers/database_logger.class.php b/backup/util/loggers/database_logger.class.php index dcfddec7e19d8..1897af5b6a73a 100644 --- a/backup/util/loggers/database_logger.class.php +++ b/backup/util/loggers/database_logger.class.php @@ -59,7 +59,13 @@ protected function action($message, $level, $options = null) { if ($this->levelcol) { $columns[$this->levelcol] = $level; } - $columns[$this->messagecol] = $message; //TODO: should this be cleaned? + $message = clean_param($message, PARAM_NOTAGS); + // Check if the message exceeds the 255 character limit in the database, + // if it does, shorten it so that it can be inserted successfully. + if (textlib::strlen($message) > 255) { + $message = textlib::substr($message, 0, 252) . '...'; + } + $columns[$this->messagecol] = $message; return $this->insert_log_record($this->logtable, $columns); } @@ -69,6 +75,6 @@ protected function insert_log_record($table, $columns) { // to preserve DB logs if the whole backup/restore transaction is // rollback global $DB; - return $DB->insert_record($this->logtable, $columns, false); // Don't return inserted id + return $DB->insert_record($table, $columns, false); // Don't return inserted id } } From 727c220c7abc0f100ffe8304efd2794bd6cd8166 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Mon, 26 Aug 2013 16:22:08 +0800 Subject: [PATCH 19/23] MDL-41213 filepicker: Version bump for css changes --- version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.php b/version.php index 6874be20aea78..a435d2e78ecc9 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2012120305.10; // 20121203 = branching date YYYYMMDD - do not modify! +$version = 2012120305.11; // 20121203 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches // .XX = incremental changes From 9684aab5a2517ea37341c46c0bcfb0024a2d0ebe Mon Sep 17 00:00:00 2001 From: Nadav Kavalerchik Date: Fri, 16 Aug 2013 12:24:10 +0300 Subject: [PATCH 20/23] MDL-37211 Backup: Migrate hard-coded strings to language string for the table header labels on the restore page --- backup/util/ui/renderer.php | 2 +- lang/en/backup.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/backup/util/ui/renderer.php b/backup/util/ui/renderer.php index 64cfdda3df114..5349a2ce5910e 100644 --- a/backup/util/ui/renderer.php +++ b/backup/util/ui/renderer.php @@ -140,7 +140,7 @@ public function backup_details($details, $nextstageurl) { } if (empty($table)) { $table = new html_table(); - $table->head = array('Module', 'Title', 'Userinfo'); + $table->head = array(get_string('module','backup'), get_string('title','backup'), get_string('userinfo','backup')); $table->colclasses = array('modulename', 'moduletitle', 'userinfoincluded'); $table->align = array('left','left', 'center'); $table->attributes = array('class'=>'activitytable generaltable'); diff --git a/lang/en/backup.php b/lang/en/backup.php index 3ae89f7f50cff..e2d7296eeb598 100644 --- a/lang/en/backup.php +++ b/lang/en/backup.php @@ -245,6 +245,9 @@ $string['skipmodifdayshelp'] = 'Choose to skip courses that have not been modified since a number of days'; $string['skipmodifprev'] = 'Skip courses not modified since previous backup'; $string['skipmodifprevhelp'] = 'Choose whether or not to skip courses that have not been modified since previous backup'; +$string['title'] = 'Title'; $string['totalcategorysearchresults'] = 'Total categories: {$a}'; $string['totalcoursesearchresults'] = 'Total courses: {$a}'; +$string['userinfo'] = 'Userinfo'; +$string['module'] = 'Module'; $string['morecoursesearchresults'] = 'More than {$a} courses found, showing first {$a} results'; From e3559db951802cd3d0f3937da4bfa3f22916d0ec Mon Sep 17 00:00:00 2001 From: Dan Poltawski Date: Tue, 27 Aug 2013 13:59:24 +0800 Subject: [PATCH 21/23] MDL-38765 quiz: fix incorrect comment --- mod/quiz/renderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php index 80848392ad90a..ecc293ca65378 100644 --- a/mod/quiz/renderer.php +++ b/mod/quiz/renderer.php @@ -1030,7 +1030,7 @@ public function attempt_state($attemptobj) { } /** - * @deprecated Do not use any more. Removed in Moodle 2.5. + * @deprecated Do not use any more. Removed in Moodle 2.6. */ public function view_best_score($viewobj) { return ''; From 8e4c96c5193ce0368670d894ca5bf21871e7e62f Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Wed, 28 Aug 2013 16:29:19 +0800 Subject: [PATCH 22/23] MDL-41249 assign: Fix incorrect gradingform restore step The mapping in the previous patch was wrong - mod assign uses the area "submissions" but the itemid is the grade->id not the submission->id. --- mod/assign/backup/moodle2/restore_assign_stepslib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/assign/backup/moodle2/restore_assign_stepslib.php b/mod/assign/backup/moodle2/restore_assign_stepslib.php index 84b8eea8f0596..db94a461f73d4 100644 --- a/mod/assign/backup/moodle2/restore_assign_stepslib.php +++ b/mod/assign/backup/moodle2/restore_assign_stepslib.php @@ -124,7 +124,6 @@ protected function process_assign_submission($data) { // Note - the old contextid is required in order to be able to restore files stored in // sub plugin file areas attached to the submissionid $this->set_mapping('submission', $oldid, $newitemid, false, null, $this->task->get_old_contextid()); - $this->set_mapping(restore_gradingform_plugin::itemid_mapping('submissions'), $oldid, $newitemid); } /** @@ -155,6 +154,7 @@ protected function process_assign_grade($data) { // Note - the old contextid is required in order to be able to restore files stored in // sub plugin file areas attached to the gradeid $this->set_mapping('grade', $oldid, $newitemid, false, null, $this->task->get_old_contextid()); + $this->set_mapping(restore_gradingform_plugin::itemid_mapping('submissions'), $oldid, $newitemid); } /** From 6cef890fbc5e6f7d8c80132b2b8a8bac211041c0 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Fri, 30 Aug 2013 09:39:54 +0800 Subject: [PATCH 23/23] weekly release 2.4.5+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index a435d2e78ecc9..a92c37cd64984 100644 --- a/version.php +++ b/version.php @@ -29,11 +29,11 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2012120305.11; // 20121203 = branching date YYYYMMDD - do not modify! +$version = 2012120305.12; // 20121203 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches // .XX = incremental changes -$release = '2.4.5+ (Build: 20130823)'; // Human-friendly version name +$release = '2.4.5+ (Build: 20130830)'; // Human-friendly version name $branch = '24'; // this version's branch $maturity = MATURITY_STABLE; // this version's maturity level