diff --git a/lang/en/cache.php b/lang/en/cache.php index f4e9312a8e3..731a5b0a259 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -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'; diff --git a/lib/db/caches.php b/lib/db/caches.php index 6701e125067..45599ccc010 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -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 + ) + ); diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 049184a7df4..771c57821d7 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -4843,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'); @@ -7023,26 +7025,122 @@ 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(); - $pluginswithfile = core_component::get_plugin_list_with_file($plugintype, $file, true); - foreach ($pluginswithfile as $plugin => $notused) { - $fullfunction = $plugintype . '_' . $plugin . '_' . $function; + foreach ($plugins[$plugintype] as $pluginname => $functionname) { - if (function_exists($fullfunction)) { - // Function exists with standard name. Store, indexed by frankenstyle name of plugin. - $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction; + // Check that it has not been removed and the file is still available. + if (!empty($allplugins[$pluginname])) { - } 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; + $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(); + + // 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) { + + $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; + +} + /** * Lists plugin-like directories within specified directory * diff --git a/user/classes/output/myprofile/manager.php b/user/classes/output/myprofile/manager.php index 05837e8765f..d3064250344 100644 --- a/user/classes/output/myprofile/manager.php +++ b/user/classes/output/myprofile/manager.php @@ -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; } diff --git a/version.php b/version.php index 8cd33c1cc13..ec77c3528c1 100644 --- a/version.php +++ b/version.php @@ -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.