From a2161d57a4a07c2f34956b6733fea3bd068c39f0 Mon Sep 17 00:00:00 2001 From: Adrian Greeve Date: Thu, 21 Jan 2016 10:43:26 +0800 Subject: [PATCH] MDL-52715 lib: Clean up of comments and variable names. --- lib/amd/build/fragment.min.js | 2 +- lib/amd/src/fragment.js | 61 +++++++-------- lib/db/services.php | 1 - lib/external/externallib.php | 22 ++++-- lib/outputfragmentrequirementslib.php | 104 +++++--------------------- lib/outputlib.php | 1 - lib/pagelib.php | 22 +++++- version.php | 2 +- 8 files changed, 81 insertions(+), 134 deletions(-) diff --git a/lib/amd/build/fragment.min.js b/lib/amd/build/fragment.min.js index 2cc8888cf6f..89a69520141 100644 --- a/lib/amd/build/fragment.min.js +++ b/lib/amd/build/fragment.min.js @@ -1 +1 @@ -define(["jquery","core/ajax"],function(a,b){var c=function(c,d,e){var f=[];for(var g in e)f.push({name:g,value:e[g]});var h=a.Deferred(),i=b.call([{methodname:"core_get_fragment",args:{component:c,callback:d,args:f}}],!1);return i[0].done(function(a){h.resolve(a)}).fail(function(a){h.reject(a)}),h.promise()},d=function(b){b.children().each(function(b,c){var e=a(c);d(e)});var c=new Y.Node(b);b.empty(),b.remove(),c.get("childNodes")&&c.empty(),c.remove()};return{fragment_load:function(a,b,d){return c(a,b,d)},fragment_append:function(b,e,f,g,h){d(a("#fragment-html")),d(a("#ajax-import-scripts")),a.when(c("mod_assign","fragment",f)).then(function(b){a(g).append('
'),a(g).append("
"),a("#fragment-html").append(b.html),a(h).append('
'),a(h).append("
"),a("#ajax-import-scripts").append(b.javascript)})}}}); \ No newline at end of file +define(["jquery","core/ajax","core/notification"],function(a,b,c){var d=function(c,d,e,f){var g=[];for(var h in f)g.push({name:h,value:f[h]});var i=a.Deferred(),j=b.call([{methodname:"core_get_fragment",args:{component:c,callback:d,contextid:e,args:g}}],!1);return j[0].done(function(a){i.resolve(a)}).fail(function(a){i.reject(a)}),i.promise()},e=function(b){b.children().each(function(b,c){var d=a(c);e(d)});var c=new Y.Node(b);b.empty(),b.remove(),c.detachAll(),c.get("childNodes")&&c.empty(),c.remove(!0)};return{fragmentAppend:function(b,f,g,h,i,j){a.when(d(b,f,g,h)).then(function(b){e(a("#fragment-html")),e(a("#fragment-scripts")),a(i).append('
'+b.html+"
"),a(j).append('
'+b.javascript+"
")}).fail(function(a){c.exception(a)})}}}); \ No newline at end of file diff --git a/lib/amd/src/fragment.js b/lib/amd/src/fragment.js index 79a59a8a83c..92d7e6d6278 100644 --- a/lib/amd/src/fragment.js +++ b/lib/amd/src/fragment.js @@ -23,18 +23,19 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.1 */ -define(['jquery', 'core/ajax'], function($, ajax) { +define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) { /** * Loads an HTML fragment through a callback. * - * @method load_fragment + * @method loadFragment * @param {string} component Component where callback is located. * @param {string} callback Callback function name. + * @param {integer} contextid Context ID of the fragment. * @param {object} params Parameters for the callback. * @return {Promise} JQuery promise object resolved when the fragment has been loaded. */ - var load_fragment = function(component, callback, params) { + var loadFragment = function(component, callback, contextid, params) { // Change params into required webservice format. var formattedparams = []; for (var index in params) { @@ -49,12 +50,11 @@ define(['jquery', 'core/ajax'], function($, ajax) { args:{ component: component, callback: callback, + contextid: contextid, args: formattedparams } }], false); - // Worth noting somewhere that the assign module seems to require userid, rownum etc. to be passed via POST / GET. - promises[0].done(function(data) { deferred.resolve(data); }).fail(function(ex) { @@ -63,6 +63,14 @@ define(['jquery', 'core/ajax'], function($, ajax) { return deferred.promise(); }; + /** + * Removes and cleans children of a node. This includes event handlers and listeners that may be + * attached to the nodes for both jquery and yui. + * + * @method recursiveCleanup + * @param {object} DOM node to be cleaned. + * @return {void} + */ var recursiveCleanup = function(node) { node.children().each(function(index, el) { var child = $(el); @@ -75,50 +83,35 @@ define(['jquery', 'core/ajax'], function($, ajax) { if (yuinode.get('childNodes')) { yuinode.empty(); } - yuinode.remove(); + yuinode.remove(true); }; return /** @alias module:core/fragment */{ - - /** - * Loads an HTML fragment through a callback. - * - * @method fragment_load - * @param {string} component Component where callback is located. - * @param {string} callback Callback function name. - * @param {object} params Parameters for the callback. - * @return {Promise} JQuery promise object resolved when the fragment has been loaded. - */ - fragment_load: function(component, callback, params) { - - return load_fragment(component, callback, params); - }, - /** * Appends HTML and JavaScript fragments to specified nodes. + * Callbacks called by this AMD module are responsible for doing the appropriate security checks + * to access the information that is returned. This only does minimal validation on the context. * - * @method fragment_append + * @method fragmentAppend * @param {string} component Component where callback is located. * @param {string} callback Callback function name. + * @param {integer} contextid Context ID of the fragment. * @param {object} params Parameters for the callback. * @param {string} htmlnodeidentifier The 'class' or 'id' to attach the HTML. * @param {string} javascriptnodeidentifier The 'class' or 'id' to attach the JavaScript. * @return {void} */ - fragment_append: function(component, callback, params, htmlnodeidentifier, javascriptnodeidentifier) { - // Clean up previous code if found first. - recursiveCleanup($('#fragment-html')); - recursiveCleanup($('#ajax-import-scripts')); - - $.when(load_fragment('mod_assign', "fragment", params)).then(function(data) { + fragmentAppend: function(component, callback, contextid, params, htmlnodeidentifier, javascriptnodeidentifier) { + $.when(loadFragment(component, callback, contextid, params)).then(function(data) { + // Clean up previous code if found first. + recursiveCleanup($('#fragment-html')); + recursiveCleanup($('#fragment-scripts')); // Attach new HTML and JavaScript. - $(htmlnodeidentifier).append("
"); - $(htmlnodeidentifier).append("
"); - $('#fragment-html').append(data.html); + $(htmlnodeidentifier).append('
' + data.html + '
'); + $(javascriptnodeidentifier).append('
' + data.javascript + '
'); - $(javascriptnodeidentifier).append("
"); - $(javascriptnodeidentifier).append("
"); - $('#ajax-import-scripts').append(data.javascript); + }).fail(function(ex) { + notification.exception(ex); }); } }; diff --git a/lib/db/services.php b/lib/db/services.php index 6707877aec8..f422c5dc8f2 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1057,7 +1057,6 @@ $functions = array( 'classpath' => 'lib/external/externallib.php', 'description' => 'Return a fragment for inclusion, such as a JavaScript page.', 'type' => 'read', - 'loginrequired' => false, 'ajax' => true, ), diff --git a/lib/external/externallib.php b/lib/external/externallib.php index 4a37c83438d..6114f991d9d 100644 --- a/lib/external/externallib.php +++ b/lib/external/externallib.php @@ -270,13 +270,14 @@ class core_external extends external_api { public static function get_fragment_parameters() { return new external_function_parameters( array( - 'component' => new external_value(PARAM_RAW, 'Component for the callback e.g. mod_asign'), - 'callback' => new external_value(PARAM_RAW, 'Name of the callback to execute'), + 'component' => new external_value(PARAM_COMPONENT, 'Component for the callback e.g. mod_assign'), + 'callback' => new external_value(PARAM_ALPHANUMEXT, 'Name of the callback to execute'), + 'contextid' => new external_value(PARAM_INT, 'Context ID that the fragment is from'), 'args' => new external_multiple_structure( new external_single_structure( array( 'name' => new external_value(PARAM_ALPHANUMEXT, 'param name'), - 'value' => new external_value(PARAM_TEXT, 'param value') + 'value' => new external_value(PARAM_RAW, 'param value') ) ), 'args for the callback are optional', VALUE_OPTIONAL ) @@ -287,20 +288,25 @@ class core_external extends external_api { /** * Get a HTML fragment for inserting into something. Initial use is for inserting mforms into * a page using AJAX. + * This web service is designed to be called only via AJAX and not directly. + * Callbacks that are called by this web service are responsible for doing the appropriate security checks + * to access the information returned. This only does minimal validation on the context. * * @param string $component Name of the component. * @param string $callback Function callback name. + * @param int $contextid Context ID this fragment is in. * @param array $args optional arguments for the callback. * @return array HTML and JavaScript fragments for insertion into stuff. * @since Moodle 3.1 */ - public static function get_fragment($component, $callback, $args = null) { + public static function get_fragment($component, $callback, $contextid, $args = null) { global $OUTPUT, $PAGE; $params = self::validate_parameters(self::get_fragment_parameters(), array( 'component' => $component, 'callback' => $callback, + 'contextid' => $contextid, 'args' => $args ) ); @@ -311,16 +317,16 @@ class core_external extends external_api { $arguments[$paramargument['name']] = $paramargument['value']; } - // Remove warning about context not being set. - $PAGE->set_context(context_system::instance()); + $context = context::instance_by_id($contextid); + self::validate_context($context); // Hack alert: Forcing bootstrap_renderer to initiate moodle page. $OUTPUT->header(); // Overwriting page_requirements_manager with the fragment one so only JS included from // this point is returned to the user. - $PAGE->set_requirements_for_fragments(); - $data = component_callback($params['component'], $params['callback'], $arguments); + $PAGE->start_collecting_javascript_requirements(); + $data = component_callback($params['component'], 'output_fragment_' . $params['callback'], $arguments); $jsfooter = $PAGE->requires->get_end_code(); $output = array('html' => $data, 'javascript' => $jsfooter); return $output; diff --git a/lib/outputfragmentrequirementslib.php b/lib/outputfragmentrequirementslib.php index 0bba1f08816..0ae13fbb789 100644 --- a/lib/outputfragmentrequirementslib.php +++ b/lib/outputfragmentrequirementslib.php @@ -17,8 +17,6 @@ /** * Library functions to facilitate the use of JavaScript in Moodle. * - * Note: you can find history of this file in lib/ajax/ajaxlib.php - * * @copyright 2016 Adrian Greeve * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package core @@ -28,28 +26,8 @@ defined('MOODLE_INTERNAL') || die(); /** - * This class tracks all the things that are needed by the current page. - * - * Normally, the only instance of this class you will need to work with is the - * one accessible via $PAGE->requires. - * - * Typical usage would be - *
- *     $PAGE->requires->js_call_amd('mod_forum/view', 'init');
- * 
- * - * It also supports obsoleted coding style with/without YUI3 modules. - *
- *     $PAGE->requires->js_init_call('M.mod_forum.init_view');
- *     $PAGE->requires->css('/mod/mymod/userstyles.php?id='.$id); // not overridable via themes!
- *     $PAGE->requires->js('/mod/mymod/script.js');
- *     $PAGE->requires->js('/mod/mymod/small_but_urgent.js', true);
- *     $PAGE->requires->js_function_call('init_mymod', array($data), true);
- * 
- * - * There are some natural restrictions on some methods. For example, {@link css()} - * can only be called before the tag is output. See the comments on the - * individual methods for details. + * This requirements manager captures the appropriate html for creating a fragment to + * be inserted elsewhere. * * @copyright 2016 Adrian Greeve * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -60,57 +38,18 @@ defined('MOODLE_INTERNAL') || die(); class fragment_requirements_manager extends page_requirements_manager { /** - * Append YUI3 module to default YUI3 JS loader. - * The structure of module array is described at {@link http://developer.yahoo.com/yui/3/yui/} - * - * @param string|array $module name of module (details are autodetected), or full module specification as array - * @return void + * Page fragment constructor. */ - public function js_module($module) { - global $CFG; - - if (empty($module)) { - throw new coding_exception('Missing YUI3 module name or full description.'); - } - - if (is_string($module)) { - $module = $this->find_module($module); - } - - if (empty($module) or empty($module['name']) or empty($module['fullpath'])) { - throw new coding_exception('Missing YUI3 module details.'); - } - - $module['fullpath'] = $this->js_fix_url($module['fullpath'])->out(false); - // Add all needed strings. - if (!empty($module['strings'])) { - foreach ($module['strings'] as $string) { - $identifier = $string[0]; - $component = isset($string[1]) ? $string[1] : 'moodle'; - $a = isset($string[2]) ? $string[2] : null; - $this->string_for_js($identifier, $component, $a); - } - } - unset($module['strings']); - - // Process module requirements and attempt to load each. This allows - // moodle modules to require each other. - if (!empty($module['requires'])) { - foreach ($module['requires'] as $requirement) { - $rmodule = $this->find_module($requirement); - if (is_array($rmodule)) { - $this->js_module($rmodule); - } - } - } - - $this->extramodules[$module['name']] = $module; + public function __construct() { + parent::__construct(); + // As this is a fragment the header should already be done. + $this->headdone = true; } - /** * Returns js code to load amd module loader, then insert inline script tags * that contain require() calls using RequireJS. + * * @return string */ protected function get_amd_footercode() { @@ -128,9 +67,6 @@ class fragment_requirements_manager extends page_requirements_manager { /** * Generate any HTML that needs to go at the end of the page. * - * Normally, this method is called automatically by the code that prints the - * page footer. You should not normally need to call it in your own code. - * * @return string the HTML code to to at the end of the page. */ public function get_end_code() { @@ -144,8 +80,6 @@ class fragment_requirements_manager extends page_requirements_manager { // Add other requested modules. $output .= $this->get_extra_modules_code(); - $this->js_init_code('M.util.js_complete("init");', true); - // All the other linked scripts - there should be as few as possible. if ($this->jsincludes['footer']) { foreach ($this->jsincludes['footer'] as $url) { @@ -153,17 +87,19 @@ class fragment_requirements_manager extends page_requirements_manager { } } - // Add all needed strings. - $strings = array(); - foreach ($this->stringsforjs as $component => $v) { - foreach ($v as $indentifier => $langstring) { - $strings[$component][$indentifier] = $langstring->out(); + if (!empty($this->stringsforjs)) { + // Add all needed strings. + $strings = array(); + foreach ($this->stringsforjs as $component => $v) { + foreach ($v as $indentifier => $langstring) { + $strings[$component][$indentifier] = $langstring->out(); + } } + // Append don't overwrite. + $output .= html_writer::script('require(["jquery"], function($) { + M.str = $.extend(true, M.str, ' . json_encode($strings) . '); + });'); } - // Append don't overwrite. - $output .= html_writer::script('require(["jquery"], function($) { - M.str = $.extend(true, M.str, ' . json_encode($strings) . '); -});'); // Add variables. if ($this->jsinitvariables['footer']) { @@ -186,8 +122,6 @@ class fragment_requirements_manager extends page_requirements_manager { $output .= html_writer::script($js); - // return 'bottom stuff'; return $output; - } } diff --git a/lib/outputlib.php b/lib/outputlib.php index e438f718017..64e02997da4 100644 --- a/lib/outputlib.php +++ b/lib/outputlib.php @@ -33,7 +33,6 @@ require_once($CFG->libdir.'/outputactions.php'); require_once($CFG->libdir.'/outputfactories.php'); require_once($CFG->libdir.'/outputrenderers.php'); require_once($CFG->libdir.'/outputrequirementslib.php'); -require_once($CFG->libdir.'/outputfragmentrequirementslib.php'); /** * Invalidate all server and client side caches. diff --git a/lib/pagelib.php b/lib/pagelib.php index 3fcf08e6875..811234f24cc 100644 --- a/lib/pagelib.php +++ b/lib/pagelib.php @@ -825,10 +825,26 @@ class moodle_page { } /** - * Changes the requirements manager over to receive fragments. + * Switches from the regular requirements manager to the fragment requirements manager to + * capture all necessary JavaScript to display a chunk of HTML such as an mform. This is for use + * by the get_fragment() web service and not for use elsewhere. */ - public function set_requirements_for_fragments() { - $this->_requires = new fragment_requirements_manager(); + public function start_collecting_javascript_requirements() { + global $CFG; + require_once($CFG->libdir.'/outputfragmentrequirementslib.php'); + + // Check that the requirements manager has not already been switched. + if (get_class($this->_requires) == 'fragment_requirements_manager') { + throw new coding_exception('JavaScript collection has already been started.'); + } + // The header needs to have been called to flush out the generic JavaScript for the page. This allows only + // JavaScript for the fragment to be collected. _wherethemewasinitialised is set when header() is called. + if (!empty($this->_wherethemewasinitialised)) { + // Change the current requirements manager over to the fragment manager to capture JS. + $this->_requires = new fragment_requirements_manager(); + } else { + throw new coding_exception('$OUTPUT->header() needs to be called before collecting JavaScript requirements.'); + } } /** diff --git a/version.php b/version.php index c60895d516b..cfaa55e7fe7 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2016012900.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2016012900.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.