Merge branch 'wip-MDL-50649-master' of git://github.com/marinaglancy/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 27 Jul 2015 10:57:52 +0000 (11:57 +0100)
committerDan Poltawski <dan@moodle.com>
Mon, 27 Jul 2015 10:57:52 +0000 (11:57 +0100)
availability/classes/multiple_messages.php
lang/en/cache.php
lib/db/caches.php
lib/medialib.php
lib/moodlelib.php
lib/tests/medialib_test.php
lib/tests/moodlelib_test.php
user/classes/output/myprofile/manager.php
version.php

index 1a478f6..0ba70a7 100644 (file)
@@ -22,7 +22,7 @@
  * using bulleted lists.
  *
  * The tree structure of this object matches that of the availability
- * restrictions. 
+ * restrictions.
  *
  * @package core_availability
  * @copyright 2015 Andrew Nicols <andrew@nicols.co.uk>
@@ -37,7 +37,7 @@
  * using bulleted lists.
  *
  * The tree structure of this object matches that of the availability
- * restrictions. 
+ * restrictions.
  *
  * @package core_availability
  * @copyright 2015 Andrew Nicols <andrew@nicols.co.uk>
index f4e9312..731a5b0 100644 (file)
@@ -56,6 +56,7 @@ $string['cachedef_langmenu'] = 'List of available languages';
 $string['cachedef_locking'] = 'Locking';
 $string['cachedef_navigation_expandcourse'] = 'Navigation expandable courses';
 $string['cachedef_observers'] = 'Event observers';
+$string['cachedef_plugin_functions'] = 'Plugins available callbacks';
 $string['cachedef_plugin_manager'] = 'Plugin info manager';
 $string['cachedef_questiondata'] = 'Question definitions';
 $string['cachedef_repositories'] = 'Repositories instances data';
index 6701e12..45599cc 100644 (file)
@@ -249,4 +249,15 @@ $definitions = array(
         'simplekeys' => true,
         'simpledata' => true,
     ),
+
+    // Caches plugins existing functions by function name and file.
+    // Set static acceleration size to 5 to load a few functions.
+    'plugin_functions' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+        'simplekeys' => true,
+        'simpledata' => true,
+        'staticacceleration' => true,
+        'staticaccelerationsize' => 5
+    )
+
 );
index 9725e35..34c2829 100644 (file)
@@ -899,7 +899,7 @@ OET;
     }
 
     public function get_rank() {
-        return 50;
+        return 10;
     }
 }
 
@@ -1141,7 +1141,7 @@ OET;
     }
 
     public function get_rank() {
-        return 20;
+        return 50;
     }
 }
 
@@ -1222,7 +1222,7 @@ OET;
     }
 
     public function get_rank() {
-        return 10;
+        return 20;
     }
 }
 
index 574225b..771c578 100644 (file)
@@ -3980,8 +3980,11 @@ function delete_user(stdClass $user) {
     // Force logout - may fail if file based sessions used, sorry.
     \core\session\manager::kill_user_sessions($user->id);
 
+    // Generate username from email address, or a fake email.
+    $delemail = !empty($user->email) ? $user->email : $user->username . '.' . $user->id . '@unknownemail.invalid';
+    $delname = clean_param($delemail . "." . time(), PARAM_USERNAME);
+
     // Workaround for bulk deletes of users with the same email address.
-    $delname = clean_param($user->email . "." . time(), PARAM_USERNAME);
     while ($DB->record_exists('user', array('username' => $delname))) { // No need to use mnethostid here.
         $delname++;
     }
@@ -4840,10 +4843,12 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
 
     // Cleanup the rest of plugins.
     $cleanuplugintypes = array('report', 'coursereport', 'format');
+    $callbacks = get_plugins_with_function('delete_course', 'lib.php');
     foreach ($cleanuplugintypes as $type) {
-        $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
-        foreach ($plugins as $plugin => $pluginfunction) {
-            $pluginfunction($course->id, $showfeedback);
+        if (!empty($callbacks[$type])) {
+            foreach ($callbacks[$type] as $pluginfunction) {
+                $pluginfunction($course->id, $showfeedback);
+            }
         }
         if ($showfeedback) {
             echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
@@ -7020,24 +7025,120 @@ function is_valid_plugin_name($name) {
  *      and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
  */
 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
+    global $CFG;
+
+    // We don't include here as all plugin types files would be included.
+    $plugins = get_plugins_with_function($function, $file, false);
+
+    if (empty($plugins[$plugintype])) {
+        return array();
+    }
+
+    $allplugins = core_component::get_plugin_list($plugintype);
+
+    // Reformat the array and include the files.
+    $pluginfunctions = array();
+    foreach ($plugins[$plugintype] as $pluginname => $functionname) {
+
+        // Check that it has not been removed and the file is still available.
+        if (!empty($allplugins[$pluginname])) {
+
+            $filepath = $allplugins[$pluginname] . DIRECTORY_SEPARATOR . $file;
+            if (file_exists($filepath)) {
+                include_once($filepath);
+                $pluginfunctions[$plugintype . '_' . $pluginname] = $functionname;
+            }
+        }
+    }
+
+    return $pluginfunctions;
+}
+
+/**
+ * Get a list of all the plugins that define a certain API function in a certain file.
+ *
+ * @param string $function the part of the name of the function after the
+ *      frankenstyle prefix. e.g 'hook' if you are looking for functions with
+ *      names like report_courselist_hook.
+ * @param string $file the name of file within the plugin that defines the
+ *      function. Defaults to lib.php.
+ * @param bool $include Whether to include the files that contain the functions or not.
+ * @return array with [plugintype][plugin] = functionname
+ */
+function get_plugins_with_function($function, $file = 'lib.php', $include = true) {
+    global $CFG;
+
+    $cache = \cache::make('core', 'plugin_functions');
+
+    // Including both although I doubt that we will find two functions definitions with the same name.
+    // Clearning the filename as cache_helper::hash_key only allows a-zA-Z0-9_.
+    $key = $function . '_' . clean_param($file, PARAM_ALPHA);
+
+    if ($pluginfunctions = $cache->get($key)) {
+
+        // Checking that the files are still available.
+        foreach ($pluginfunctions as $plugintype => $plugins) {
+
+            $allplugins = \core_component::get_plugin_list($plugintype);
+            foreach ($plugins as $plugin => $fullpath) {
+
+                // Cache might be out of sync with the codebase, skip the plugin if it is not available.
+                if (empty($allplugins[$plugin])) {
+                    unset($pluginfunctions[$plugintype][$plugin]);
+                    continue;
+                }
+
+                $fileexists = file_exists($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
+                if ($include && $fileexists) {
+                    // Include the files if it was requested.
+                    include_once($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
+                } else if (!$fileexists) {
+                    // If the file is not available any more it should not be returned.
+                    unset($pluginfunctions[$plugintype][$plugin]);
+                }
+            }
+        }
+        return $pluginfunctions;
+    }
+
     $pluginfunctions = array();
-    $pluginswithfile = core_component::get_plugin_list_with_file($plugintype, $file, true);
-    foreach ($pluginswithfile as $plugin => $notused) {
-        $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
 
-        if (function_exists($fullfunction)) {
-            // Function exists with standard name. Store, indexed by frankenstyle name of plugin.
-            $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
+    // To fill the cached. Also, everything should continue working with cache disabled.
+    $plugintypes = \core_component::get_plugin_types();
+    foreach ($plugintypes as $plugintype => $unused) {
+
+        // We need to include files here.
+        $pluginswithfile = \core_component::get_plugin_list_with_file($plugintype, $file, true);
+        foreach ($pluginswithfile as $plugin => $notused) {
 
-        } else if ($plugintype === 'mod') {
-            // For modules, we also allow plugin without full frankenstyle but just starting with the module name.
-            $shortfunction = $plugin . '_' . $function;
-            if (function_exists($shortfunction)) {
-                $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
+            $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
+
+            $pluginfunction = false;
+            if (function_exists($fullfunction)) {
+                // Function exists with standard name. Store, indexed by frankenstyle name of plugin.
+                $pluginfunction = $fullfunction;
+
+            } else if ($plugintype === 'mod') {
+                // For modules, we also allow plugin without full frankenstyle but just starting with the module name.
+                $shortfunction = $plugin . '_' . $function;
+                if (function_exists($shortfunction)) {
+                    $pluginfunction = $shortfunction;
+                }
             }
+
+            if ($pluginfunction) {
+                if (empty($pluginfunctions[$plugintype])) {
+                    $pluginfunctions[$plugintype] = array();
+                }
+                $pluginfunctions[$plugintype][$plugin] = $pluginfunction;
+            }
+
         }
     }
+    $cache->set($key, $pluginfunctions);
+
     return $pluginfunctions;
+
 }
 
 /**
index ed19db7..7129e80 100644 (file)
@@ -168,6 +168,13 @@ class core_medialib_testcase extends advanced_testcase {
         $CFG->core_media_enable_mp3 = true;
         $renderer = new core_media_renderer_test($PAGE, '');
         $this->assertSame('mp3, html5audio, link', $renderer->get_players_test());
+
+        // Test QT and HTML5 media order.
+        $CFG->core_media_enable_mp3 = false;
+        $CFG->core_media_enable_html5video = true;
+        $CFG->core_media_enable_qt = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertSame('html5video, html5audio, qt, link', $renderer->get_players_test());
     }
 
     /**
@@ -210,6 +217,13 @@ class core_medialib_testcase extends advanced_testcase {
     public function test_embed_url_fallbacks() {
         global $CFG, $PAGE;
 
+        // Key strings in the embed code that identify with the media formats being tested.
+        $qt = 'qtplugin.cab';
+        $html5video = '</video>';
+        $html5audio = '</audio>';
+        $link = 'mediafallbacklink';
+        $mp3 = 'mediaplugin_mp3';
+
         $url = new moodle_url('http://example.org/test.mp4');
 
         // All plugins disabled, NOLINK option.
@@ -222,59 +236,58 @@ class core_medialib_testcase extends advanced_testcase {
         // All plugins disabled but not NOLINK.
         $renderer = new core_media_renderer_test($PAGE, '');
         $t = $renderer->embed_url($url);
-        $this->assert_contents(false, false, true, $t);
+        $this->assertContains($link, $t);
 
-        // HTML5 plugin enabled.
+        // Enable media players that can play the same media formats. (ie. qt & html5video for mp4 files, etc.)
         $CFG->core_media_enable_html5video = true;
-        $renderer = new core_media_renderer_test($PAGE, '');
-        $t = $renderer->embed_url($url);
-        $this->assert_contents(false, true, true, $t);
-
-        // QT plugin enabled as well.
+        $CFG->core_media_enable_html5audio = true;
+        $CFG->core_media_enable_mp3 = true;
         $CFG->core_media_enable_qt = true;
-        $renderer = new core_media_renderer_test($PAGE, '');
-        $t = $renderer->embed_url($url);
-        $this->assert_contents(true, true, true, $t);
 
-        // Nolink turned off, plugins still enabled.
-        $t = $renderer->embed_url($url, 0, 0, '',
-                array(core_media::OPTION_NO_LINK => true));
-        $this->assert_contents(true, true, false, $t);
-    }
-
-    /**
-     * Checks the contents of the resulting HTML code to ensure it used the
-     * correct embed method(s).
-     *
-     * @param bool $hasqt True if it should have QT embed code
-     * @param bool $hashtml5 True if it should have HTML5 embed code
-     * @param bool $haslink True if it should have a fallback link
-     * @param string $text HTML content
-     */
-    private function assert_contents($hasqt, $hashtml5, $haslink, $text) {
-        // I tried to avoid making it specific to the exact details of the html
-        // code, picking out only single key strings that would let it check
-        // whether final code contains the right things.
-        $qt = 'qtplugin.cab';
-        $html5 = '</video>';
-        $link = 'mediafallbacklink';
-
-        if ($hasqt) {
-            $this->assertContains($qt, $text);
-        } else {
-            $this->assertNotContains($qt, $text);
-        }
-
-        if ($hashtml5) {
-            $this->assertContains($html5, $text);
-        } else {
-            $this->assertNotContains($html5, $text);
-        }
-
-        if ($haslink) {
-            $this->assertContains($link, $text);
-        } else {
-            $this->assertNotContains($link, $text);
+        // Test media formats that can be played by 2 or more players.
+        $mediaformats = array('mp3', 'm4a', 'mp4', 'm4v');
+
+        foreach ($mediaformats as $format) {
+            $url = new moodle_url('http://example.org/test.' . $format);
+            $renderer = new core_media_renderer_test($PAGE, '');
+            $textwithlink = $renderer->embed_url($url);
+            $textwithoutlink = $renderer->embed_url($url, 0, 0, '', array(core_media::OPTION_NO_LINK => true));
+
+            switch ($format) {
+                case 'mp3':
+                    $this->assertContains($mp3, $textwithlink);
+                    $this->assertContains($html5audio, $textwithlink);
+                    $this->assertContains($link, $textwithlink);
+
+                    $this->assertContains($mp3, $textwithoutlink);
+                    $this->assertContains($html5audio, $textwithoutlink);
+                    $this->assertNotContains($link, $textwithoutlink);
+                    break;
+
+                case 'm4a':
+                    $this->assertContains($qt, $textwithlink);
+                    $this->assertContains($html5audio, $textwithlink);
+                    $this->assertContains($link, $textwithlink);
+
+                    $this->assertContains($qt, $textwithoutlink);
+                    $this->assertContains($html5audio, $textwithoutlink);
+                    $this->assertNotContains($link, $textwithoutlink);
+                    break;
+
+                case 'mp4':
+                case 'm4v':
+                    $this->assertContains($qt, $textwithlink);
+                    $this->assertContains($html5video, $textwithlink);
+                    $this->assertContains($link, $textwithlink);
+
+                    $this->assertContains($qt, $textwithoutlink);
+                    $this->assertContains($html5video, $textwithoutlink);
+                    $this->assertNotContains($link, $textwithoutlink);
+                    break;
+
+                default:
+                    break;
+            }
         }
     }
 
index 3d33588..8ee6dd2 100644 (file)
@@ -1882,6 +1882,10 @@ class core_moodlelib_testcase extends advanced_testcase {
 
         $user = $this->getDataGenerator()->create_user(array('idnumber'=>'abc'));
         $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'xyz'));
+        $usersharedemail1 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
+        $usersharedemail2 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
+        $useremptyemail1 = $this->getDataGenerator()->create_user(array('email' => ''));
+        $useremptyemail2 = $this->getDataGenerator()->create_user(array('email' => ''));
 
         // Delete user and capture event.
         $sink = $this->redirectEvents();
@@ -1947,6 +1951,30 @@ class core_moodlelib_testcase extends advanced_testcase {
         $result = delete_user($admin);
         $this->assertFalse($result);
 
+        // Simultaneously deleting users with identical email addresses.
+        $result1 = delete_user($usersharedemail1);
+        $result2 = delete_user($usersharedemail2);
+
+        $usersharedemail1after = $DB->get_record('user', array('id' => $usersharedemail1->id));
+        $usersharedemail2after = $DB->get_record('user', array('id' => $usersharedemail2->id));
+        $this->assertTrue($result1);
+        $this->assertTrue($result2);
+        $this->assertStringStartsWith($usersharedemail1->email . '.', $usersharedemail1after->username);
+        $this->assertStringStartsWith($usersharedemail2->email . '.', $usersharedemail2after->username);
+
+        // Simultaneously deleting users without email addresses.
+        $result1 = delete_user($useremptyemail1);
+        $result2 = delete_user($useremptyemail2);
+
+        $useremptyemail1after = $DB->get_record('user', array('id' => $useremptyemail1->id));
+        $useremptyemail2after = $DB->get_record('user', array('id' => $useremptyemail2->id));
+        $this->assertTrue($result1);
+        $this->assertTrue($result2);
+        $this->assertStringStartsWith($useremptyemail1->username . '.' . $useremptyemail1->id . '@unknownemail.invalid.',
+            $useremptyemail1after->username);
+        $this->assertStringStartsWith($useremptyemail2->username . '.' . $useremptyemail2->id . '@unknownemail.invalid.',
+            $useremptyemail2after->username);
+
         $this->resetDebugging();
     }
 
index 05837e8..d306425 100644 (file)
@@ -69,13 +69,13 @@ class manager {
         }
 
         // Plugins.
-        $types = \core_component::get_plugin_types();
-        foreach ($types as $type => $dir) {
-            $pluginlist = get_plugin_list_with_function($type, "myprofile_navigation", "lib.php");
-            foreach ($pluginlist as $function) {
+        $pluginswithfunction = get_plugins_with_function('myprofile_navigation', 'lib.php');
+        foreach ($pluginswithfunction as $plugins) {
+            foreach ($plugins as $function) {
                 $function($tree, $user, $iscurrentuser, $course);
             }
         }
+
         $tree->sort_categories();
         return $tree;
     }
index 8cd33c1..ec77c35 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2015072300.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2015072700.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.