* 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>
* 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>
$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';
'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
+ )
+
);
}
public function get_rank() {
- return 50;
+ return 10;
}
}
}
public function get_rank() {
- return 20;
+ return 50;
}
}
}
public function get_rank() {
- return 10;
+ return 20;
}
}
// 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++;
}
// 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');
* 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;
+
}
/**
$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());
}
/**
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.
// 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;
+ }
}
}
$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();
$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();
}
}
// 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;
}
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.