"sub": false,
"supernew": false,
"maxerr": 500,
- "maxlen": 150,
+ "maxlen": 180,
"passfail": false,
"latedef": true
}
define('NO_OUTPUT_BUFFERING', true);
-if (empty($_GET['cache']) and empty($_POST['cache']) and empty($_GET['sesskey']) and empty($_POST['sesskey'])) {
+if ((isset($_GET['cache']) and $_GET['cache'] === '0')
+ or (isset($_POST['cache']) and $_POST['cache'] === '0')
+ or (!isset($_POST['cache']) and !isset($_GET['cache']) and empty($_GET['sesskey']) and empty($_POST['sesskey']))) {
// Prevent caching at all cost when visiting this page directly,
// we redirect to self once we known no upgrades are necessary.
// Note: $_GET and $_POST are used here intentionally because our param cleaning is not loaded yet.
// Set up PAGE.
$url = new moodle_url('/admin/index.php');
-if ($cache) {
- $url->param('cache', 1);
-}
+$url->param('cache', $cache);
$PAGE->set_url($url);
unset($url);
$PAGE->set_pagelayout('maintenance');
$PAGE->set_popup_notification_allowed(false);
+ /** @var core_admin_renderer $output */
+ $output = $PAGE->get_renderer('core', 'admin');
+
if (upgrade_stale_php_files_present()) {
$PAGE->set_title($stradministration);
$PAGE->set_cacheable(false);
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
echo $output->upgrade_stale_php_files_page();
die();
}
$PAGE->set_heading($strdatabasechecking);
$PAGE->set_cacheable(false);
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
echo $output->upgrade_confirm_page($a->newversion, $maturity, $testsite);
die();
$PAGE->set_heading($strcurrentrelease);
$PAGE->set_cacheable(false);
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
echo $output->upgrade_environment_page($release, $envstatus, $environment_results);
die();
$PAGE->set_heading($strplugincheck);
$PAGE->set_cacheable(false);
- $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1));
-
- /** @var core_admin_renderer $output */
- $output = $PAGE->get_renderer('core', 'admin');
-
- // check plugin dependencies first
- $failed = array();
- if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
- echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
- die();
- }
- unset($failed);
+ $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0));
if ($fetchupdates) {
- // no sesskey support guaranteed here
- if (empty($CFG->disableupdatenotifications)) {
- \core\update\checker::instance()->fetch();
+ // No sesskey support guaranteed here, because sessions might not work yet.
+ $updateschecker = \core\update\checker::instance();
+ if ($updateschecker->enabled()) {
+ $updateschecker->fetch();
}
redirect($reloadurl);
}
$deploydata = $deployer->submitted_data();
if (!empty($deploydata)) {
+ // No sesskey support guaranteed here, because sessions might not work yet.
echo $output->upgrade_plugin_confirm_deploy_page($deployer, $deploydata);
die();
}
echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(),
$version, $showallplugins, $reloadurl,
- new moodle_url('/admin/index.php', array('confirmupgrade'=>1, 'confirmrelease'=>1, 'confirmplugincheck'=>1)));
+ new moodle_url('/admin/index.php', array('confirmupgrade'=>1, 'confirmrelease'=>1, 'confirmplugincheck'=>1, 'cache'=>0)));
die();
} else {
- // Launch main upgrade
+ // Always verify plugin dependencies!
+ $failed = array();
+ if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+ $PAGE->set_pagelayout('maintenance');
+ $PAGE->set_popup_notification_allowed(false);
+ $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0));
+ echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
+ die();
+ }
+ unset($failed);
+
+ // Launch main upgrade.
upgrade_core($version, true);
}
} else if ($version < $CFG->version) {
if (!$cache and moodle_needs_upgrading()) {
if (!$PAGE->headerprinted) {
// means core upgrade or installation was not already done
+
+ /** @var core_admin_renderer $output */
+ $output = $PAGE->get_renderer('core', 'admin');
+
if (!$confirmplugins) {
$strplugincheck = get_string('plugincheck');
$PAGE->set_cacheable(false);
if ($fetchupdates) {
- // no sesskey support guaranteed here
- \core\update\checker::instance()->fetch();
+ require_sesskey();
+ $updateschecker = \core\update\checker::instance();
+ if ($updateschecker->enabled()) {
+ $updateschecker->fetch();
+ }
redirect($PAGE->url);
}
- $output = $PAGE->get_renderer('core', 'admin');
-
$deployer = \core\update\deployer::instance();
if ($deployer->enabled()) {
$deployer->initialize($PAGE->url, $PAGE->url);
$deploydata = $deployer->submitted_data();
if (!empty($deploydata)) {
+ require_sesskey();
echo $output->upgrade_plugin_confirm_deploy_page($deployer, $deploydata);
die();
}
}
- // check plugin dependencies first
- $failed = array();
- if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
- echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
- die();
- }
- unset($failed);
-
- // dependencies check passed, let's rock!
+ // Show plugins info.
echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(),
$version, $showallplugins,
new moodle_url($PAGE->url),
- new moodle_url('/admin/index.php', array('confirmplugincheck'=>1)));
+ new moodle_url('/admin/index.php', array('confirmplugincheck'=>1, 'cache'=>0)));
+ die();
+ }
+
+ // Make sure plugin dependencies are always checked.
+ $failed = array();
+ if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+ $PAGE->set_pagelayout('maintenance');
+ $PAGE->set_popup_notification_allowed(false);
+ $reloadurl = new moodle_url('/admin/index.php', array('cache' => 0));
+ echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
die();
}
+ unset($failed);
}
+
// install/upgrade all plugins and other parts
upgrade_noncore(true);
}
upgrade_finished('upgradesettings.php');
}
+if (has_capability('moodle/site:config', context_system::instance())) {
+ if ($fetchupdates) {
+ require_sesskey();
+ $updateschecker = \core\update\checker::instance();
+ if ($updateschecker->enabled()) {
+ $updateschecker->fetch();
+ }
+ redirect(new moodle_url('/admin/index.php', array('cache' => 0)));
+ }
+}
+
// Now we can be sure everything was upgraded and caches work fine,
// redirect if necessary to make sure caching is enabled.
if (!$cache) {
admin_externalpage_setup('adminnotifications');
-if ($fetchupdates) {
- require_sesskey();
- $updateschecker->fetch();
- redirect(new moodle_url('/admin/index.php'));
-}
-
$output = $PAGE->get_renderer('core', 'admin');
echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
$cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch, $buggyiconvnomb,
public function upgrade_confirm_page($strnewversion, $maturity, $testsite) {
$output = '';
- $continueurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1));
+ $continueurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'cache' => 0));
$continue = new single_button($continueurl, get_string('continue'), 'get');
$cancelurl = new moodle_url('/admin/index.php');
$output .= $this->environment_check_table($envstatus, $environment_results);
if (!$envstatus) {
- $output .= $this->upgrade_reload(new moodle_url('/admin/index.php'), array('confirmupgrade' => 1));
+ $output .= $this->upgrade_reload(new moodle_url('/admin/index.php'), array('confirmupgrade' => 1, 'cache' => 0));
} else {
$output .= $this->notification(get_string('environmentok', 'admin'), 'notifysuccess');
$output .= $this->box(get_string('langpackwillbeupdated', 'admin'), 'generalbox', 'notice');
}
- $output .= $this->continue_button(new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1)));
+ $output .= $this->continue_button(new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0)));
}
$output .= $this->footer();
}
$updateinfo .= $this->container_start('checkforupdates');
- $fetchurl = new moodle_url('/admin/index.php', array('fetchupdates' => 1, 'sesskey' => sesskey(), 'cache' => 1));
+ $fetchurl = new moodle_url('/admin/index.php', array('fetchupdates' => 1, 'sesskey' => sesskey(), 'cache' => 0));
$updateinfo .= $this->single_button($fetchurl, get_string('checkforupdates', 'core_plugin'));
if ($fetch) {
$updateinfo .= $this->container(get_string('checkforupdateslast', 'core_plugin',
$out .= $this->output->heading(get_string('nonehighlighted', 'core_plugin'));
if (empty($options['full'])) {
$out .= html_writer::link(new moodle_url('/admin/index.php',
- array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1)),
+ array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1, 'cache' => 0)),
get_string('nonehighlightedinfo', 'core_plugin'));
}
$out .= $this->output->container_end();
$out .= $this->output->heading(get_string('somehighlighted', 'core_plugin', $sumofhighlighted));
if (empty($options['full'])) {
$out .= html_writer::link(new moodle_url('/admin/index.php',
- array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1)),
+ array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1, 'cache' => 0)),
get_string('somehighlightedinfo', 'core_plugin'));
} else {
$out .= html_writer::link(new moodle_url('/admin/index.php',
- array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 0)),
+ array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 0, 'cache' => 0)),
get_string('somehighlightedonly', 'core_plugin'));
}
$out .= $this->output->container_end();
$config->yesLabel = get_string('confirmcancelyes', 'backup');
$config->noLabel = get_string('confirmcancelno', 'backup');
$config->closeButtonTitle = get_string('close', 'editor');
- $PAGE->requires->yui_module('moodle-backup-confirmcancel', 'M.core_backup.watch_cancel_buttons', array($config));
+ $PAGE->requires->yui_module('moodle-backup-confirmcancel', 'M.core_backup.confirmcancel.watch_cancel_buttons', array($config));
// Get list of module types on course.
$modinfo = get_fast_modinfo($COURSE);
$modnames = $modinfo->get_used_module_names(true);
- $PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.select_all_init',
+ $PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.backupselectall',
array($modnames));
$PAGE->requires->strings_for_js(array('select', 'all', 'none'), 'moodle');
$PAGE->requires->strings_for_js(array('showtypes', 'hidetypes'), 'backup');
+++ /dev/null
-YUI.add('moodle-backup-confirmcancel', function(Y) {
-
-// Namespace for the backup
-M.core_backup = M.core_backup || {};
-/**
- * Adds confirmation dialogues to the cancel buttons on the page.
- *
- * @param {object} config
- */
-M.core_backup.watch_cancel_buttons = function(config) {
- Y.all('.confirmcancel').each(function(){
- this._confirmationListener = this._confirmationListener || this.on('click', function(e){
- // Prevent the default event (sumbit) from firing
- e.preventDefault();
- // Create the confirm box
- var confirm = new M.core.confirm(config);
- // If the user clicks yes
- confirm.on('complete-yes', function(e){
- // Detach the listener for the confirm box so it doesn't fire again.
- this._confirmationListener.detach();
- // Simulate the original cancel button click
- this.simulate('click');
- }, this);
- // Show the confirm box
- confirm.show();
- }, this);
- });
-}
-
-}, '@VERSION@', {'requires':['base','node','node-event-simulate','moodle-core-notification']});
--- /dev/null
+{
+ "name": "moodle-backup-backupselectall",
+ "builds": {
+ "moodle-backup-backupselectall": {
+ "jsfiles": [
+ "backupselectall.js"
+ ]
+ }
+ }
+}
-YUI.add('moodle-backup-backupselectall', function(Y) {
+/**
+ * Adds select all/none links to the top of the backup/restore/import schema page.
+ *
+ * @module moodle-backup-backupselectall
+ */
// Namespace for the backup
M.core_backup = M.core_backup || {};
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
+ *
+ * @class M.core_backup.backupselectall
*/
-M.core_backup.select_all_init = function(modnames) {
+M.core_backup.backupselectall = function(modnames) {
var formid = null;
var helper = function(e, check, type, mod) {
if (prefix && name.substring(0, prefix.length) !== prefix) {
return;
}
- if (name.substring(name.length - len) == type) {
+ if (name.substring(name.length - len) === type) {
checkbox.set('checked', check);
}
});
var withuserdata = false;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
- if (name.substring(name.length - 9) == '_userdata') {
+ if (name.substring(name.length - 9) === '_userdata') {
withuserdata = '_userdata';
- } else if (name.substring(name.length - 9) == '_userinfo') {
+ } else if (name.substring(name.length - 9) === '_userinfo') {
withuserdata = '_userinfo';
}
});
if (!modnames.hasOwnProperty(mod)) {
continue;
}
- var html = html_generator('include_setting section_level', 'mod_' + mod, modnames[mod]);
+ html = html_generator('include_setting section_level', 'mod_' + mod, modnames[mod]);
if (withuserdata) {
html += html_generator('normal_setting', 'userdata-mod_' + mod, modnames[mod]);
}
modlist.currentlyshown = !modlist.currentlyshown;
// Either hide or show the links.
- var animcfg = { node: modlist, duration: 0.2 };
+ var animcfg = { node: modlist, duration: 0.2 },
+ anim;
if (modlist.currentlyshown) {
// Animate reveal of the module links.
modlist.show();
animcfg.to = { maxHeight: modlist.get('clientHeight') + 'px' };
modlist.setStyle('maxHeight', '0px');
- var anim = new Y.Anim(animcfg);
+ anim = new Y.Anim(animcfg);
anim.on('end', function() { modlist.setStyle('maxHeight', 'none'); });
anim.run();
} else {
// Animate hide of the module links.
animcfg.to = { maxHeight: '0px' };
modlist.setStyle('maxHeight', modlist.get('clientHeight') + 'px');
- var anim = new Y.Anim(animcfg);
+ anim = new Y.Anim(animcfg);
anim.on('end', function() { modlist.hide(); modlist.setStyle('maxHeight', 'none'); });
anim.run();
}
};
- Y.one('#backup-bytype').on('click', function(e) { toggletypes(); });
+ Y.one('#backup-bytype').on('click', function() { toggletypes(); });
Y.one('#backup-all-included').on('click', function(e) { helper(e, true, '_included'); });
Y.one('#backup-none-included').on('click', function(e) { helper(e, false, '_included'); });
Y.one('#backup-all-userdata').on('click', function(e) { helper(e, true, withuserdata); });
Y.one('#backup-none-userdata').on('click', function(e) { helper(e, false, withuserdata); });
}
-}
-
-}, '@VERSION@', {'requires':['base', 'node', 'event', 'node-event-simulate', 'anim']});
+};
--- /dev/null
+{
+ "moodle-backup-backupselectall": {
+ "requires": [
+ "node",
+ "event",
+ "node-event-simulate",
+ "anim"
+ ]
+ }
+}
--- /dev/null
+{
+ "moodle-backup-backupselectall": {
+ "requires": [
+ "node",
+ "event",
+ "node-event-simulate",
+ "anim"
+ ]
+ }
+}
--- /dev/null
+{
+ "name": "moodle-backup-confirmcancel",
+ "builds": {
+ "moodle-backup-confirmcancel": {
+ "jsfiles": [
+ "confirmcancel.js"
+ ]
+ }
+ }
+}
--- /dev/null
+/**
+ * Add a confirmation dialogue when cancelling a backup.
+ *
+ * @module moodle-backup-confirmcancel
+ */
+
+/**
+ * Add a confirmation dialogue when cancelling a backup.
+ *
+ * @class M.core_backup.confirmcancel
+ */
+
+
+// Namespace for the backup.
+M.core_backup = M.core_backup || {};
+
+M.core_backup.confirmcancel = {
+ /**
+ * An array of EventHandlers which call the confirm_cancel dialogue.
+ *
+ * @property listeners
+ * @protected
+ * @type Array
+ */
+ listeners: [],
+
+ /**
+ * The configuration supplied to this instance.
+ *
+ * @property config
+ * @protected
+ * @type Object
+ */
+ config: {},
+
+ /**
+ * Initializer to watch all cancel buttons.
+ *
+ * @method watch_cancel_buttons
+ * @param {Object} config The configuration for the confirmation dialogue.
+ */
+ watch_cancel_buttons: function(config) {
+ this.config = config;
+
+ this.listeners.push(
+ Y.one(Y.config.doc.body).delegate('click', this.confirm_cancel, '.confirmcancel', this)
+ );
+ },
+
+ /**
+ * Display the confirmation dialogue.
+ *
+ * @method confirm_cancel
+ * @protected
+ * @param {EventFacade} e
+ */
+ confirm_cancel: function(e) {
+ // Prevent the default event (submit) from firing.
+ e.preventDefault();
+
+ // Create the confirmation dialogue.
+ var confirm = new M.core.confirm(this.config);
+
+ // If the user clicks yes.
+ confirm.on('complete-yes', function(){
+ // Detach the listeners for the confirm box so they don't fire again.
+ new Y.EventHandle(M.core_backup.confirmcancel.listeners).detach();
+
+ // Simulate the original cancel button click.
+ c.currentTarget.simulate('click');
+ }, this);
+
+
+ // Show the confirm box.
+ confirm.show();
+ }
+};
--- /dev/null
+{
+ "moodle-backup-confirmcancel": {
+ "requires": [
+ "node",
+ "node-event-simulate",
+ "moodle-core-notification-confirm"
+ ]
+ }
+}
instance : {
value : false,
setter : function(val) {
- return parseInt(val);
+ return parseInt(val, 10);
}
}
}
// Example:
// $CFG->behat_usedeprecated = true;
//
+// Including feature files from directories outside the dirroot is possible if required. The setting
+// requires that the running user has executable permissions on all parent directories in the paths.
+// Example:
+// $CFG->behat_additionalfeatures = array('/home/developer/code/wipfeatures');
+//
//=========================================================================
// 12. DEVELOPER DATA GENERATOR
//=========================================================================
if ($hasmanageactivities) {
$pixicon = 'i/dragdrop';
- if ($mod->course == SITEID) {
+ if (!course_ajax_enabled($mod->get_course())) {
// Override for course frontpage until we get drag/drop working there.
$pixicon = 't/move';
}
+++ /dev/null
-/**
- * This module provides backwards compatability and should be removed
- * entirely in Moodle 2.5
- */
-YUI.add('moodle-enrol-notification', function(Y) {
- console.log("You are using a deprecated name. Please update your YUI module to use moodle-core-notification instead of moodle-enrol-notification");
-}, '@VERSION@', {requires:['base','node','overlay','event-key', 'moodle-core-notification']});
$features = array_values($featurespaths);
}
+ // Optionally include features from additional directories.
+ if (!empty($CFG->behat_additionalfeatures)) {
+ $features = array_merge($features, array_map("realpath", $CFG->behat_additionalfeatures));
+ }
+
// Gets all the components with steps definitions.
$stepsdefinitions = array();
$steps = self::get_components_steps_definitions();
self::load_classes('core', "$CFG->dirroot/lib/classes");
foreach (self::$subsystems as $subsystem => $fulldir) {
+ if (!$fulldir) {
+ continue;
+ }
self::load_classes('core_'.$subsystem, "$fulldir/classes");
}
debugging('Number of event data fields must not be changed in event classes', DEBUG_DEVELOPER);
}
$encoded = json_encode($this->data['other']);
- if ($encoded === false or $this->data['other'] !== json_decode($encoded, true)) {
+ // The comparison here is not set to strict as whole float numbers will be converted to integers through JSON encoding /
+ // decoding and send an unwanted debugging message.
+ if ($encoded === false or $this->data['other'] != json_decode($encoded, true)) {
debugging('other event data must be compatible with json encoding', DEBUG_DEVELOPER);
}
if ($this->data['userid'] and !is_number($this->data['userid'])) {
}
}
+ /**
+ * Is automatic deployment enabled?
+ *
+ * @return bool
+ */
+ public function enabled() {
+ global $CFG;
+
+ // The feature can be prohibited via config.php.
+ return empty($CFG->disableupdateautodeploy);
+ }
+
/**
* Returns the timestamp of the last execution of {@link fetch()}
*
*/
class deployer {
- const HTTP_PARAM_PREFIX = 'updteautodpldata_'; // Hey, even Google has not heard of such a prefix! So it MUST be safe :-p.
- const HTTP_PARAM_CHECKER = 'datapackagesize'; // Name of the parameter that holds the number of items in the received data items.
-
/** @var \core\update\deployer holds the singleton instance */
protected static $singletoninstance;
/** @var moodle_url URL of a page that includes the deployer UI */
throw new coding_exception('Illegal method call - deployer not initialized.');
}
- $params = $this->data_to_params(array(
- 'updateinfo' => (array)$info, // See http://www.php.net/manual/en/language.types.array.php#language.types.array.casting .
- ));
+ $params = array(
+ 'updateaddon' => $info->component,
+ 'version' =>$info->version,
+ 'sesskey' => sesskey(),
+ );
+
+ // Append some our own data.
+ if (!empty($this->callerurl)) {
+ $params['callerurl'] = $this->callerurl->out(false);
+ }
+ if (!empty($this->returnurl)) {
+ $params['returnurl'] = $this->returnurl->out(false);
+ }
$widget = new \single_button(
new moodle_url($this->callerurl, $params),
* @return array
*/
public function submitted_data() {
+ $component = optional_param('updateaddon', '', PARAM_COMPONENT);
+ $version = optional_param('version', '', PARAM_RAW);
+ if (!$component or !$version) {
+ return false;
+ }
+
+ $plugininfo = \core_plugin_manager::instance()->get_plugin_info($component);
+ if (!$plugininfo) {
+ return false;
+ }
- $data = $this->params_to_data($_POST);
+ if ($plugininfo->is_standard()) {
+ return false;
+ }
- if (empty($data) or empty($data[self::HTTP_PARAM_CHECKER])) {
+ if (!$updates = $plugininfo->available_updates()) {
return false;
}
- if (!empty($data['updateinfo']) and is_object($data['updateinfo'])) {
- $updateinfo = $data['updateinfo'];
- if (!empty($updateinfo->component) and !empty($updateinfo->version)) {
- $data['updateinfo'] = new info($updateinfo->component, (array)$updateinfo);
+ $info = null;
+ foreach ($updates as $update) {
+ if ($update->version == $version) {
+ $info = $update;
+ break;
}
}
+ if (!$info) {
+ return false;
+ }
- if (!empty($data['callerurl'])) {
+ $data = array(
+ 'updateaddon' => $component,
+ 'updateinfo' => $info,
+ 'callerurl' => optional_param('callerurl', null, PARAM_URL),
+ 'returnurl' => optional_param('returnurl', null, PARAM_URL),
+ );
+ if ($data['callerurl']) {
$data['callerurl'] = new moodle_url($data['callerurl']);
}
-
- if (!empty($data['returnurl'])) {
+ if ($data['callerurl']) {
$data['returnurl'] = new moodle_url($data['returnurl']);
}
/* === End of external API === */
- /**
- * Prepares an array of HTTP parameters that can be passed to another page.
- *
- * @param array|object $data associative array or an object holding the data, data JSON-able
- * @return array suitable as a param for moodle_url
- */
- protected function data_to_params($data) {
-
- // Append some our own data.
- if (!empty($this->callerurl)) {
- $data['callerurl'] = $this->callerurl->out(false);
- }
- if (!empty($this->returnurl)) {
- $data['returnurl'] = $this->returnurl->out(false);
- }
-
- // Finally append the count of items in the package.
- $data[self::HTTP_PARAM_CHECKER] = count($data);
-
- // Generate params.
- $params = array();
- foreach ($data as $name => $value) {
- $transname = self::HTTP_PARAM_PREFIX.$name;
- $transvalue = json_encode($value);
- $params[$transname] = $transvalue;
- }
-
- return $params;
- }
-
- /**
- * Converts HTTP parameters passed to the script into native PHP data
- *
- * @param array $params such as $_REQUEST or $_POST
- * @return array data passed for this class
- */
- protected function params_to_data(array $params) {
-
- if (empty($params)) {
- return array();
- }
-
- $data = array();
- foreach ($params as $name => $value) {
- if (strpos($name, self::HTTP_PARAM_PREFIX) === 0) {
- $realname = substr($name, strlen(self::HTTP_PARAM_PREFIX));
- $realvalue = json_decode($value);
- $data[$realname] = $realvalue;
- }
- }
-
- return $data;
- }
-
/**
* Returns a random string to be used as a filename of the password storage.
*
$directory = core_component::get_plugin_directory($plugintype, $pluginname);
if (is_null($directory)) {
- throw new coding_exception('Unknown component location', $component);
+ // Plugin unknown, most probably deleted or missing during upgrade,
+ // look at the parent directory instead because they might want to install it.
+ $plugintypes = core_component::get_plugin_types();
+ if (!isset($plugintypes[$plugintype])) {
+ throw new coding_exception('Unknown component location', $component);
+ }
+ $directory = $plugintypes[$plugintype];
}
return $this->directory_writable($directory);
} else {
return false;
}
- $compat_view = false;
+ $compatview = false;
// IE8 and later versions may pretend to be IE7 for intranet sites, use Trident version instead,
// the Trident should always describe the capabilities of IE in any emulation mode.
if ($browser === '7.0' and preg_match("/Trident\/([0-9\.]+)/", $useragent, $match)) {
- $compat_view = true;
+ $compatview = true;
$browser = $match[1] + 4; // NOTE: Hopefully this will work also for future IE versions.
}
$browser = round($browser, 1);
return array(
'version' => $browser,
- 'compatview' => $compat_view
+ 'compatview' => $compatview
);
}
return false;
}
$this->load_administration_settings();
+
+ // Check if local plugins is adding node to site admin.
+ $this->load_local_plugin_settings();
+
$this->initialised = true;
}
}
$event6->trigger();
$this->assertDebuggingCalled();
+ // Check that whole float numbers do not trigger debugging messages.
+ $event7 = \core_tests\event\unittest_executed::create(array('courseid'=>1, 'context'=>\context_system::instance(),
+ 'other' => array('wholenumber' => 90.0000, 'numberwithdecimals' => 54.7656, 'sample' => 1)));
+ $event7->trigger();
+ $this->assertDebuggingNotCalled();
+
$event = \core_tests\event\problematic_event2::create(array());
$this->assertDebuggingNotCalled();
$event = \core_tests\event\problematic_event2::create(array('context'=>\context_system::instance()));
+++ /dev/null
-YUI.add('moodle-core-dragdrop', function(Y) {
- var MOVEICON = {
- pix: "i/move_2d",
- largepix: "i/dragdrop",
- component: 'moodle',
- cssclass: 'moodle-core-dragdrop-draghandle'
- };
-
- /*
- * General DRAGDROP class, this should not be used directly,
- * it is supposed to be extended by your class
- */
- var DRAGDROP = function() {
- DRAGDROP.superclass.constructor.apply(this, arguments);
- };
-
- Y.extend(DRAGDROP, Y.Base, {
- goingup : null,
- absgoingup : null,
- samenodeclass : null,
- parentnodeclass : null,
- groups : [],
- lastdroptarget : null,
- initializer : function() {
- // Listen for all drag:start events
- Y.DD.DDM.on('drag:start', this.global_drag_start, this);
- // Listen for all drag:end events
- Y.DD.DDM.on('drag:end', this.global_drag_end, this);
- // Listen for all drag:drag events
- Y.DD.DDM.on('drag:drag', this.global_drag_drag, this);
- // Listen for all drop:over events
- Y.DD.DDM.on('drop:over', this.global_drop_over, this);
- // Listen for all drop:hit events
- Y.DD.DDM.on('drop:hit', this.global_drop_hit, this);
- // Listen for all drop:miss events
- Y.DD.DDM.on('drag:dropmiss', this.global_drag_dropmiss, this);
-
- Y.one(Y.config.doc.body).delegate('key', this.global_keydown, 'down:32, enter, esc', '.' + MOVEICON.cssclass, this);
- Y.one(Y.config.doc.body).delegate('click', this.global_keydown, '.' + MOVEICON.cssclass , this);
- },
-
- get_drag_handle: function(title, classname, iconclass, large) {
- var iconname = MOVEICON.pix;
- if (large) {
- iconname = MOVEICON.largepix;
- }
- var dragicon = Y.Node.create('<img />')
- .setStyle('cursor', 'move')
- .setAttrs({
- 'src' : M.util.image_url(iconname, MOVEICON.component),
- 'alt' : title
- });
- if (iconclass) {
- dragicon.addClass(iconclass);
- }
-
- var dragelement = Y.Node.create('<span></span>')
- .addClass(classname)
- .setAttribute('title', title)
- .setAttribute('tabIndex', 0)
- .setAttribute('data-draggroups', this.groups)
- .setAttribute('aria-grabbed', 'false')
- .setAttribute('role', 'button');
- dragelement.appendChild(dragicon);
- dragelement.addClass(MOVEICON.cssclass);
-
- return dragelement;
- },
-
- lock_drag_handle: function(drag, classname) {
- drag.removeHandle('.'+classname);
- },
-
- unlock_drag_handle: function(drag, classname) {
- drag.addHandle('.'+classname);
- },
-
- ajax_failure: function(response) {
- var e = {
- name : response.status+' '+response.statusText,
- message : response.responseText
- };
- return new M.core.exception(e);
- },
-
- in_group: function(target) {
- var ret = false;
- Y.each(this.groups, function(v, k) {
- if (target._groups[v]) {
- ret = true;
- }
- }, this);
- return ret;
- },
- /*
- * Drag-dropping related functions
- */
- global_drag_start : function(e) {
- // Get our drag object
- var drag = e.target;
- // Check that drag object belongs to correct group
- if (!this.in_group(drag)) {
- return;
- }
- // Set some general styles here
- drag.get('node').setStyle('opacity', '.25');
- drag.get('dragNode').setStyles({
- opacity: '.75',
- borderColor: drag.get('node').getStyle('borderColor'),
- backgroundColor: drag.get('node').getStyle('backgroundColor')
- });
- drag.get('dragNode').empty();
- this.drag_start(e);
- },
-
- global_drag_end : function(e) {
- var drag = e.target;
- // Check that drag object belongs to correct group
- if (!this.in_group(drag)) {
- return;
- }
- //Put our general styles back
- drag.get('node').setStyles({
- visibility: '',
- opacity: '1'
- });
- this.drag_end(e);
- },
-
- global_drag_drag : function(e) {
- var drag = e.target,
- info = e.info;
-
- // Check that drag object belongs to correct group
- if (!this.in_group(drag)) {
- return;
- }
-
- // Note, we test both < and > situations here. We don't want to
- // effect a change in direction if the user is only moving side
- // to side with no Y position change.
-
- // Detect changes in the position relative to the start point.
- if (info.start[1] < info.xy[1]) {
- // We are going up if our final position is higher than our start position.
- this.absgoingup = true;
-
- } else if (info.start[1] > info.xy[1]) {
- // Otherwise we're going down.
- this.absgoingup = false;
- }
-
- // Detect changes in the position relative to the last movement.
- if (info.delta[1] < 0) {
- // We are going up if our final position is higher than our start position.
- this.goingup = true;
-
- } else if (info.delta[1] > 0) {
- // Otherwise we're going down.
- this.goingup = false;
- }
-
- this.drag_drag(e);
- },
-
- global_drop_over : function(e) {
- // Check that drop object belong to correct group.
- if (!e.drop || !e.drop.inGroup(this.groups)) {
- return;
- }
-
- // Get a reference to our drag and drop nodes.
- var drag = e.drag.get('node'),
- drop = e.drop.get('node');
-
- // Save last drop target for the case of missed target processing.
- this.lastdroptarget = e.drop;
-
- // Are we dropping within the same parent node?
- if (drop.hasClass(this.samenodeclass)) {
- var where;
-
- if (this.goingup) {
- where = "before";
- } else {
- where = "after";
- }
-
- // Add the node contents so that it's moved, otherwise only the drag handle is moved.
- drop.insert(drag, where);
- } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
- // We are dropping on parent node and it is empty
- if (this.goingup) {
- drop.append(drag);
- } else {
- drop.prepend(drag);
- }
- }
- this.drop_over(e);
- },
-
- global_drag_dropmiss : function(e) {
- // drag:dropmiss does not have e.drag and e.drop properties
- // we substitute them for the ease of use. For e.drop we use,
- // this.lastdroptarget (ghost node we use for indicating where to drop)
- e.drag = e.target;
- e.drop = this.lastdroptarget;
- // Check that drag object belongs to correct group
- if (!this.in_group(e.drag)) {
- return;
- }
- // Check that drop object belong to correct group
- if (!e.drop || !e.drop.inGroup(this.groups)) {
- return;
- }
- this.drag_dropmiss(e);
- },
-
- global_drop_hit : function(e) {
- // Check that drop object belong to correct group
- if (!e.drop || !e.drop.inGroup(this.groups)) {
- return;
- }
- this.drop_hit(e);
- },
-
- /**
- * This is used to build the text for the heading of the keyboard
- * drag drop menu and the text for the nodes in the list.
- * @method find_element_text
- * @param {Node} n The node to start searching for a valid text node.
- * @returns {string} The text of the first text-like child node of n.
- */
- find_element_text : function(n) {
- // The valid node types to get text from.
- var nodes = n.all('h2, h3, h4, h5, span, p, div.no-overflow, div.dimmed_text');
- var text = '';
-
- nodes.each(function () {
- if (text == '') {
- if (Y.Lang.trim(this.get('text')) != '') {
- text = this.get('text');
- }
- }
- });
-
- if (text != '') {
- return text;
- }
- return M.util.get_string('emptydragdropregion', 'moodle');
- },
-
- /**
- * This is used to initiate a keyboard version of a drag and drop.
- * A dialog will open listing all the valid drop targets that can be selected
- * using tab, tab, tab, enter.
- * @method global_start_keyboard_drag
- * @param {Event} e The keydown / click event on the grab handle.
- * @param {Node} dragcontainer The resolved draggable node (an ancestor of the drag handle).
- * @param {Node} draghandle The node that triggered this action.
- */
- global_start_keyboard_drag : function(e, draghandle, dragcontainer) {
- M.core.dragdrop.keydragcontainer = dragcontainer;
- M.core.dragdrop.keydraghandle = draghandle;
-
- // Indicate to a screenreader the node that is selected for drag and drop.
- dragcontainer.setAttribute('aria-grabbed', 'true');
- // Get the name of the thing to move.
- var nodetitle = this.find_element_text(dragcontainer);
- var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
-
- // Build the list of drop targets.
- var droplist = Y.Node.create('<ul></ul>');
- droplist.addClass('dragdrop-keyboard-drag');
- var listitem;
- var listitemtext;
-
- // Search for possible drop targets.
- var droptargets = Y.all('.' + this.samenodeclass + ', .' + this.parentnodeclass);
-
- droptargets.each(function (node) {
- var validdrop = false, labelroot = node;
- if (node.drop && node.drop.inGroup(this.groups) && node.drop.get('node') != dragcontainer) {
- // This is a drag and drop target with the same class as the grabbed node.
- validdrop = true;
- } else {
- var elementgroups = node.getAttribute('data-draggroups').split(' ');
- var i, j;
- for (i = 0; i < elementgroups.length; i++) {
- for (j = 0; j < this.groups.length; j++) {
- if (elementgroups[i] == this.groups[j]) {
- // This is a parent node of the grabbed node (used for dropping in empty sections).
- validdrop = true;
- // This node will have no text - so we get the first valid text from the parent.
- labelroot = node.get('parentNode');
- break;
- }
- }
- if (validdrop) {
- break;
- }
- }
- }
-
- if (validdrop) {
- // It is a valid drop target - create a list item for it.
- listitem = Y.Node.create('<li></li>');
- listlink = Y.Node.create('<a></a>');
- nodetitle = this.find_element_text(labelroot);
-
- listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
- listlink.setContent(listitemtext);
-
- // Add a data attribute so we can get the real drop target.
- listlink.setAttribute('data-drop-target', node.get('id'));
- // Notify the screen reader this is a valid drop target.
- listlink.setAttribute('aria-dropeffect', 'move');
- // Allow tabbing to the link.
- listlink.setAttribute('tabindex', '0');
-
- // Set the event listeners for enter, space or click.
- listlink.on('click', this.global_keyboard_drop, this);
- listlink.on('key', this.global_keyboard_drop, 'down:enter,32', this);
-
- // Add to the list or drop targets.
- listitem.append(listlink);
- droplist.append(listitem);
- }
- }, this);
-
- // Create the dialog for the interaction.
- M.core.dragdrop.dropui = new M.core.dialogue({
- headerContent : dialogtitle,
- bodyContent : droplist,
- draggable : true,
- visible : true,
- centered : true
- });
-
- // Focus the first drop target.
- if (droplist.one('a')) {
- droplist.one('a').focus();
- }
- },
-
- /**
- * This is used as a simulated drag/drop event in order to prevent any
- * subtle bugs from creating a real instance of a drag drop event. This means
- * there are no state changes in the Y.DD.DDM and any undefined functions
- * will trigger an obvious and fatal error.
- * The end result is that we call all our drag/drop handlers but do not bubble the
- * event to anyone else.
- *
- * The functions/properties implemented in the wrapper are:
- * e.target
- * e.drag
- * e.drop
- * e.drag.get('node')
- * e.drop.get('node')
- * e.drag.addHandle()
- * e.drag.removeHandle()
- *
- * @class simulated_drag_drop_event
- * @param {Node} dragnode The drag container node
- * @param {Node} dropnode The node to initiate the drop on
- */
- simulated_drag_drop_event : function(dragnode, dropnode) {
-
- // Subclass for wrapping both drag and drop.
- var dragdropwrapper = function(node) {
- this.node = node;
- }
-
- // Method e.drag.get() - get the node.
- dragdropwrapper.prototype.get = function(param) {
- if (param == 'node' || param == 'dragNode' || param == 'dropNode') {
- return this.node;
- }
- return null;
- };
-
- // Method e.drag.inGroup() - we have already run the group checks before triggering the event.
- dragdropwrapper.prototype.inGroup = function() {
- return true;
- };
-
- // Method e.drag.addHandle() - we don't want to run this.
- dragdropwrapper.prototype.addHandle = function() {};
- // Method e.drag.removeHandle() - we don't want to run this.
- dragdropwrapper.prototype.removeHandle = function() {};
-
- // Create instances of the dragdropwrapper.
- this.drop = new dragdropwrapper(dropnode);
- this.drag = new dragdropwrapper(dragnode);
- this.target = this.drop;
- },
-
- /**
- * This is used to complete a keyboard version of a drag and drop.
- * A drop event will be simulated based on the drag and drop nodes.
- * @method global_keyboard_drop
- * @param {Event} e The keydown / click event on the proxy drop node.
- */
- global_keyboard_drop : function(e) {
- // The drag node was saved.
- var dragcontainer = M.core.dragdrop.keydragcontainer;
- dragcontainer.setAttribute('aria-grabbed', 'false');
- // The real drop node is stored in an attribute of the proxy.
- var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
-
- // Close the dialog.
- M.core.dragdrop.dropui.hide();
- // Cancel the event.
- e.preventDefault();
- // Convert to drag drop events.
- var dragevent = new this.simulated_drag_drop_event(dragcontainer, dragcontainer);
- var dropevent = new this.simulated_drag_drop_event(dragcontainer, droptarget);
- // Simulate the full sequence.
- this.drag_start(dragevent);
- this.global_drop_over(dropevent);
- this.global_drop_hit(dropevent);
- M.core.dragdrop.keydraghandle.focus();
- },
-
- /**
- * This is used to cancel a keyboard version of a drag and drop.
- *
- * @method global_cancel_keyboard_drag
- */
- global_cancel_keyboard_drag : function() {
- if (M.core.dragdrop.keydragcontainer) {
- M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
- M.core.dragdrop.keydraghandle.focus();
- M.core.dragdrop.keydragcontainer = null;
- }
- },
-
- /**
- * Process key events on the drag handles.
- * @method global_keydown
- * @param {Event} e The keydown / click event on the drag handle.
- */
- global_keydown : function(e) {
- var draghandle = e.target.ancestor('.' + MOVEICON.cssclass, true),
- dragcontainer,
- draggroups;
-
- if (draghandle === null) {
- // The element clicked did not have a a draghandle in it's lineage.
- return;
- }
-
- if (e.keyCode === 27 ) {
- // Escape to cancel from anywhere.
- this.global_cancel_keyboard_drag();
- e.preventDefault();
- return;
- }
-
- // Only process events on a drag handle.
- if (!draghandle.hasClass(MOVEICON.cssclass)) {
- return;
- }
-
- // Do nothing if not space or enter.
- if (e.keyCode !== 13 && e.keyCode !== 32 && e.type !== 'click') {
- return;
- }
-
- // Check the drag groups to see if we are the handler for this node.
- draggroups = draghandle.getAttribute('data-draggroups').split(' ');
- var i, j, validgroup = false;
-
- for (i = 0; i < draggroups.length; i++) {
- for (j = 0; j < this.groups.length; j++) {
- if (draggroups[i] === this.groups[j]) {
- validgroup = true;
- break;
- }
- }
- if (validgroup) {
- break;
- }
- }
- if (!validgroup) {
- return;
- }
-
- // Valid event - start the keyboard drag.
- dragcontainer = draghandle.ancestor('.yui3-dd-drop');
- this.global_start_keyboard_drag(e, draghandle, dragcontainer);
-
- e.preventDefault();
- },
-
- /*
- * Abstract functions definitions
- */
- drag_start : function(e) {},
- drag_end : function(e) {},
- drag_drag : function(e) {},
- drag_dropmiss : function(e) {},
- drop_over : function(e) {},
- drop_hit : function(e) {}
- }, {
- NAME : 'dragdrop',
- ATTRS : {}
- });
-
-M.core = M.core || {};
-M.core.dragdrop = DRAGDROP;
-
-}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'event-key', 'event-focus', 'moodle-core-notification']});
// See if we are missing either of block regions,
// if yes we need to add an empty one to use as target
- if (blockregionlist.size() != this.get('regions').length) {
+ if (blockregionlist.size() !== this.get('regions').length) {
var blockregion = Y.Node.create('<div></div>')
.addClass(CSS.BLOCKREGION);
var regioncontent = Y.Node.create('<div></div>')
// Setting blockregion as droptarget (the case when it is empty)
// The region-post (the right one)
// is very narrow, so add extra padding on the left to drop block on it.
- var tar = new Y.DD.Drop({
+ new Y.DD.Drop({
node: blockregionnode.one('div.'+CSS.REGIONCONTENT),
groups: this.groups,
padding: '40 240 40 240'
// Moving from empty region-content towards the opposite one,
// hide empty one (only for region-pre, region-post areas at the moment).
regionname = this.get_region_id(drop.ancestor('div.'+CSS.BLOCKREGION));
- if (this.dragsourceregion.all('.'+CSS.BLOCK).size() == 0 && this.dragsourceregion.get('id').match(/(region-pre|region-post)/i)) {
+ if (this.dragsourceregion.all('.'+CSS.BLOCK).size() === 0 && this.dragsourceregion.get('id').match(/(region-pre|region-post)/i)) {
if (!documentbody.hasClass('side-'+regionname+'-only')) {
documentbody.addClass('side-'+regionname+'-only');
}
--- /dev/null
+{
+ "name": "moodle-core-dragdrop",
+ "builds": {
+ "moodle-core-dragdrop": {
+ "jsfiles": [
+ "dragdrop.js"
+ ]
+ }
+ }
+}
--- /dev/null
+/**
+ * The core drag and drop module for Moodle which extends the YUI drag and
+ * drop functionality with additional features.
+ *
+ * @module moodle-core-dragdrop
+ */
+var MOVEICON = {
+ pix: "i/move_2d",
+ largepix: "i/dragdrop",
+ component: 'moodle',
+ cssclass: 'moodle-core-dragdrop-draghandle'
+};
+
+/**
+ * General DRAGDROP class, this should not be used directly,
+ * it is supposed to be extended by your class
+ *
+ * @class M.core.dragdrop
+ * @constructor
+ * @extends Base
+ */
+var DRAGDROP = function() {
+ DRAGDROP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(DRAGDROP, Y.Base, {
+ /**
+ * Whether the item is being moved upwards compared with the last
+ * location.
+ *
+ * @property goingup
+ * @type Boolean
+ * @default null
+ */
+ goingup: null,
+
+ /**
+ * Whether the item is being moved upwards compared with the start
+ * point.
+ *
+ * @property absgoingup
+ * @type Boolean
+ * @default null
+ */
+ absgoingup: null,
+
+ /**
+ * The class for the object.
+ *
+ * @property samenodeclass
+ * @type String
+ * @default null
+ */
+ samenodeclass: null,
+
+ /**
+ * The class on the parent of the item being moved.
+ *
+ * @property parentnodeclass
+ * @type String
+ * @default
+ */
+ parentnodeclass: null,
+
+ /**
+ * The groups for this instance.
+ *
+ * @property groups
+ * @type Array
+ * @default []
+ */
+ groups: [],
+
+ /**
+ * The previous drop location.
+ *
+ * @property lastdroptarget
+ * @type Node
+ * @default null
+ */
+ lastdroptarget: null,
+
+ /**
+ * The initializer which sets up the move action.
+ *
+ * @method initializer
+ * @protected
+ */
+ initializer: function() {
+ // Listen for all drag:start events.
+ Y.DD.DDM.on('drag:start', this.global_drag_start, this);
+
+ // Listen for all drag:end events.
+ Y.DD.DDM.on('drag:end', this.global_drag_end, this);
+
+ // Listen for all drag:drag events.
+ Y.DD.DDM.on('drag:drag', this.global_drag_drag, this);
+
+ // Listen for all drop:over events.
+ Y.DD.DDM.on('drop:over', this.global_drop_over, this);
+
+ // Listen for all drop:hit events.
+ Y.DD.DDM.on('drop:hit', this.global_drop_hit, this);
+
+ // Listen for all drop:miss events.
+ Y.DD.DDM.on('drag:dropmiss', this.global_drag_dropmiss, this);
+
+ // Add keybaord listeners for accessible drag/drop
+ Y.one(Y.config.doc.body).delegate('key', this.global_keydown,
+ 'down:32, enter, esc', '.' + MOVEICON.cssclass, this);
+
+ // Make the accessible drag/drop respond to a single click.
+ Y.one(Y.config.doc.body).delegate('click', this.global_keydown,
+ '.' + MOVEICON.cssclass , this);
+ },
+
+ /**
+ * Build a new drag handle Node.
+ *
+ * @method get_drag_handle
+ * @param {String} title The title on the drag handle
+ * @param {String} classname The name of the class to add to the node
+ * wrapping the drag icon
+ * @param {String} [iconclass] The class to add to the icon
+ * @param {Boolean} [large=false] whether to use the larger version of
+ * the drag icon
+ * @return Node The built drag handle.
+ */
+ get_drag_handle: function(title, classname, iconclass, large) {
+ var iconname = MOVEICON.pix;
+ if (large) {
+ iconname = MOVEICON.largepix;
+ }
+ var dragicon = Y.Node.create('<img />')
+ .setStyle('cursor', 'move')
+ .setAttrs({
+ 'src': M.util.image_url(iconname, MOVEICON.component),
+ 'alt': title
+ });
+ if (iconclass) {
+ dragicon.addClass(iconclass);
+ }
+
+ var dragelement = Y.Node.create('<span></span>')
+ .addClass(classname)
+ .setAttribute('title', title)
+ .setAttribute('tabIndex', 0)
+ .setAttribute('data-draggroups', this.groups)
+ .setAttribute('role', 'button')
+ .setAttribute('aria-grabbed', 'false');
+ dragelement.appendChild(dragicon);
+ dragelement.addClass(MOVEICON.cssclass);
+
+ return dragelement;
+ },
+
+ lock_drag_handle: function(drag, classname) {
+ drag.removeHandle('.'+classname);
+ },
+
+ unlock_drag_handle: function(drag, classname) {
+ drag.addHandle('.'+classname);
+ },
+
+ ajax_failure: function(response) {
+ var e = {
+ name: response.status+' '+response.statusText,
+ message: response.responseText
+ };
+ return new M.core.exception(e);
+ },
+
+ in_group: function(target) {
+ var ret = false;
+ Y.each(this.groups, function(v) {
+ if (target._groups[v]) {
+ ret = true;
+ }
+ }, this);
+ return ret;
+ },
+ /*
+ * Drag-dropping related functions
+ */
+ global_drag_start: function(e) {
+ // Get our drag object
+ var drag = e.target;
+ // Check that drag object belongs to correct group
+ if (!this.in_group(drag)) {
+ return;
+ }
+ // Set some general styles here
+ drag.get('node').setStyle('opacity', '.25');
+ drag.get('dragNode').setStyles({
+ opacity: '.75',
+ borderColor: drag.get('node').getStyle('borderColor'),
+ backgroundColor: drag.get('node').getStyle('backgroundColor')
+ });
+ drag.get('dragNode').empty();
+ this.drag_start(e);
+ },
+
+ global_drag_end: function(e) {
+ var drag = e.target;
+ // Check that drag object belongs to correct group
+ if (!this.in_group(drag)) {
+ return;
+ }
+ //Put our general styles back
+ drag.get('node').setStyles({
+ visibility: '',
+ opacity: '1'
+ });
+ this.drag_end(e);
+ },
+
+ global_drag_drag: function(e) {
+ var drag = e.target,
+ info = e.info;
+
+ // Check that drag object belongs to correct group
+ if (!this.in_group(drag)) {
+ return;
+ }
+
+ // Note, we test both < and > situations here. We don't want to
+ // effect a change in direction if the user is only moving side
+ // to side with no Y position change.
+
+ // Detect changes in the position relative to the start point.
+ if (info.start[1] < info.xy[1]) {
+ // We are going up if our final position is higher than our start position.
+ this.absgoingup = true;
+
+ } else if (info.start[1] > info.xy[1]) {
+ // Otherwise we're going down.
+ this.absgoingup = false;
+ }
+
+ // Detect changes in the position relative to the last movement.
+ if (info.delta[1] < 0) {
+ // We are going up if our final position is higher than our start position.
+ this.goingup = true;
+
+ } else if (info.delta[1] > 0) {
+ // Otherwise we're going down.
+ this.goingup = false;
+ }
+
+ this.drag_drag(e);
+ },
+
+ global_drop_over: function(e) {
+ // Check that drop object belong to correct group.
+ if (!e.drop || !e.drop.inGroup(this.groups)) {
+ return;
+ }
+
+ // Get a reference to our drag and drop nodes.
+ var drag = e.drag.get('node'),
+ drop = e.drop.get('node');
+
+ // Save last drop target for the case of missed target processing.
+ this.lastdroptarget = e.drop;
+
+ // Are we dropping within the same parent node?
+ if (drop.hasClass(this.samenodeclass)) {
+ var where;
+
+ if (this.goingup) {
+ where = "before";
+ } else {
+ where = "after";
+ }
+
+ // Add the node contents so that it's moved, otherwise only the drag handle is moved.
+ drop.insert(drag, where);
+ } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
+ // We are dropping on parent node and it is empty
+ if (this.goingup) {
+ drop.append(drag);
+ } else {
+ drop.prepend(drag);
+ }
+ }
+ this.drop_over(e);
+ },
+
+ global_drag_dropmiss: function(e) {
+ // drag:dropmiss does not have e.drag and e.drop properties
+ // we substitute them for the ease of use. For e.drop we use,
+ // this.lastdroptarget (ghost node we use for indicating where to drop)
+ e.drag = e.target;
+ e.drop = this.lastdroptarget;
+ // Check that drag object belongs to correct group
+ if (!this.in_group(e.drag)) {
+ return;
+ }
+ // Check that drop object belong to correct group
+ if (!e.drop || !e.drop.inGroup(this.groups)) {
+ return;
+ }
+ this.drag_dropmiss(e);
+ },
+
+ global_drop_hit: function(e) {
+ // Check that drop object belong to correct group
+ if (!e.drop || !e.drop.inGroup(this.groups)) {
+ return;
+ }
+ this.drop_hit(e);
+ },
+
+ /**
+ * This is used to build the text for the heading of the keyboard
+ * drag drop menu and the text for the nodes in the list.
+ * @method find_element_text
+ * @param {Node} n The node to start searching for a valid text node.
+ * @return {string} The text of the first text-like child node of n.
+ */
+ find_element_text: function(n) {
+ // The valid node types to get text from.
+ var nodes = n.all('h2, h3, h4, h5, span, p, div.no-overflow, div.dimmed_text');
+ var text = '';
+
+ nodes.each(function () {
+ if (text === '') {
+ if (Y.Lang.trim(this.get('text')) !== '') {
+ text = this.get('text');
+ }
+ }
+ });
+
+ if (text !== '') {
+ return text;
+ }
+ return M.util.get_string('emptydragdropregion', 'moodle');
+ },
+
+ /**
+ * This is used to initiate a keyboard version of a drag and drop.
+ * A dialog will open listing all the valid drop targets that can be selected
+ * using tab, tab, tab, enter.
+ * @method global_start_keyboard_drag
+ * @param {Event} e The keydown / click event on the grab handle.
+ * @param {Node} dragcontainer The resolved draggable node (an ancestor of the drag handle).
+ * @param {Node} draghandle The node that triggered this action.
+ */
+ global_start_keyboard_drag: function(e, draghandle, dragcontainer) {
+ M.core.dragdrop.keydragcontainer = dragcontainer;
+ M.core.dragdrop.keydraghandle = draghandle;
+
+ // Indicate to a screenreader the node that is selected for drag and drop.
+ dragcontainer.setAttribute('aria-grabbed', 'true');
+ // Get the name of the thing to move.
+ var nodetitle = this.find_element_text(dragcontainer);
+ var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
+
+ // Build the list of drop targets.
+ var droplist = Y.Node.create('<ul></ul>');
+ droplist.addClass('dragdrop-keyboard-drag');
+ var listitem;
+ var listitemtext;
+
+ // Search for possible drop targets.
+ var droptargets = Y.all('.' + this.samenodeclass + ', .' + this.parentnodeclass);
+
+ droptargets.each(function (node) {
+ var validdrop = false, labelroot = node;
+ if (node.drop && node.drop.inGroup(this.groups) && node.drop.get('node') !== dragcontainer) {
+ // This is a drag and drop target with the same class as the grabbed node.
+ validdrop = true;
+ } else {
+ var elementgroups = node.getAttribute('data-draggroups').split(' ');
+ var i, j;
+ for (i = 0; i < elementgroups.length; i++) {
+ for (j = 0; j < this.groups.length; j++) {
+ if (elementgroups[i] === this.groups[j]) {
+ // This is a parent node of the grabbed node (used for dropping in empty sections).
+ validdrop = true;
+ // This node will have no text - so we get the first valid text from the parent.
+ labelroot = node.get('parentNode');
+ break;
+ }
+ }
+ if (validdrop) {
+ break;
+ }
+ }
+ }
+
+ if (validdrop) {
+ // It is a valid drop target - create a list item for it.
+ listitem = Y.Node.create('<li></li>');
+ listlink = Y.Node.create('<a></a>');
+ nodetitle = this.find_element_text(labelroot);
+
+ listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
+ listlink.setContent(listitemtext);
+
+ // Add a data attribute so we can get the real drop target.
+ listlink.setAttribute('data-drop-target', node.get('id'));
+ // Notify the screen reader this is a valid drop target.
+ listlink.setAttribute('aria-dropeffect', 'move');
+ // Allow tabbing to the link.
+ listlink.setAttribute('tabindex', '0');
+
+ // Set the event listeners for enter, space or click.
+ listlink.on('click', this.global_keyboard_drop, this);
+ listlink.on('key', this.global_keyboard_drop, 'down:enter,32', this);
+
+ // Add to the list or drop targets.
+ listitem.append(listlink);
+ droplist.append(listitem);
+ }
+ }, this);
+
+ // Create the dialog for the interaction.
+ M.core.dragdrop.dropui = new M.core.dialogue({
+ headerContent: dialogtitle,
+ bodyContent: droplist,
+ draggable: true,
+ visible: true,
+ centered: true
+ });
+
+ // Focus the first drop target.
+ if (droplist.one('a')) {
+ droplist.one('a').focus();
+ }
+ },
+
+ /**
+ * This is used as a simulated drag/drop event in order to prevent any
+ * subtle bugs from creating a real instance of a drag drop event. This means
+ * there are no state changes in the Y.DD.DDM and any undefined functions
+ * will trigger an obvious and fatal error.
+ * The end result is that we call all our drag/drop handlers but do not bubble the
+ * event to anyone else.
+ *
+ * The functions/properties implemented in the wrapper are:
+ * e.target
+ * e.drag
+ * e.drop
+ * e.drag.get('node')
+ * e.drop.get('node')
+ * e.drag.addHandle()
+ * e.drag.removeHandle()
+ *
+ * @method simulated_drag_drop_event
+ * @param {Node} dragnode The drag container node
+ * @param {Node} dropnode The node to initiate the drop on
+ */
+ simulated_drag_drop_event: function(dragnode, dropnode) {
+
+ // Subclass for wrapping both drag and drop.
+ var DragDropWrapper = function(node) {
+ this.node = node;
+ };
+
+ // Method e.drag.get() - get the node.
+ DragDropWrapper.prototype.get = function(param) {
+ if (param === 'node' || param === 'dragNode' || param === 'dropNode') {
+ return this.node;
+ }
+ return null;
+ };
+
+ // Method e.drag.inGroup() - we have already run the group checks before triggering the event.
+ DragDropWrapper.prototype.inGroup = function() {
+ return true;
+ };
+
+ // Method e.drag.addHandle() - we don't want to run this.
+ DragDropWrapper.prototype.addHandle = function() {};
+ // Method e.drag.removeHandle() - we don't want to run this.
+ DragDropWrapper.prototype.removeHandle = function() {};
+
+ // Create instances of the DragDropWrapper.
+ this.drop = new DragDropWrapper(dropnode);
+ this.drag = new DragDropWrapper(dragnode);
+ this.target = this.drop;
+ },
+
+ /**
+ * This is used to complete a keyboard version of a drag and drop.
+ * A drop event will be simulated based on the drag and drop nodes.
+ * @method global_keyboard_drop
+ * @param {Event} e The keydown / click event on the proxy drop node.
+ */
+ global_keyboard_drop: function(e) {
+ // The drag node was saved.
+ var dragcontainer = M.core.dragdrop.keydragcontainer;
+ dragcontainer.setAttribute('aria-grabbed', 'false');
+ // The real drop node is stored in an attribute of the proxy.
+ var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
+
+ // Close the dialog.
+ M.core.dragdrop.dropui.hide();
+ // Cancel the event.
+ e.preventDefault();
+ // Convert to drag drop events.
+ var dragevent = new this.simulated_drag_drop_event(dragcontainer, dragcontainer);
+ var dropevent = new this.simulated_drag_drop_event(dragcontainer, droptarget);
+ // Simulate the full sequence.
+ this.drag_start(dragevent);
+ this.global_drop_over(dropevent);
+ this.global_drop_hit(dropevent);
+ M.core.dragdrop.keydraghandle.focus();
+ },
+
+ /**
+ * This is used to cancel a keyboard version of a drag and drop.
+ *
+ * @method global_cancel_keyboard_drag
+ */
+ global_cancel_keyboard_drag: function() {
+ if (M.core.dragdrop.keydragcontainer) {
+ M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
+ M.core.dragdrop.keydraghandle.focus();
+ M.core.dragdrop.keydragcontainer = null;
+ }
+ },
+
+ /**
+ * Process key events on the drag handles.
+ *
+ * @method global_keydown
+ * @param {EventFacade} e The keydown / click event on the drag handle.
+ */
+ global_keydown: function(e) {
+ var draghandle = e.target.ancestor('.' + MOVEICON.cssclass, true),
+ dragcontainer,
+ draggroups;
+
+ if (draghandle === null) {
+ // The element clicked did not have a a draghandle in it's lineage.
+ return;
+ }
+
+ if (e.keyCode === 27 ) {
+ // Escape to cancel from anywhere.
+ this.global_cancel_keyboard_drag();
+ e.preventDefault();
+ return;
+ }
+
+ // Only process events on a drag handle.
+ if (!draghandle.hasClass(MOVEICON.cssclass)) {
+ return;
+ }
+
+ // Do nothing if not space or enter.
+ if (e.keyCode !== 13 && e.keyCode !== 32 && e.type !== 'click') {
+ return;
+ }
+
+ // Check the drag groups to see if we are the handler for this node.
+ draggroups = draghandle.getAttribute('data-draggroups').split(' ');
+ var i, j, validgroup = false;
+
+ for (i = 0; i < draggroups.length; i++) {
+ for (j = 0; j < this.groups.length; j++) {
+ if (draggroups[i] === this.groups[j]) {
+ validgroup = true;
+ break;
+ }
+ }
+ if (validgroup) {
+ break;
+ }
+ }
+ if (!validgroup) {
+ return;
+ }
+
+ // Valid event - start the keyboard drag.
+ dragcontainer = draghandle.ancestor('.yui3-dd-drop');
+ this.global_start_keyboard_drag(e, draghandle, dragcontainer);
+
+ e.preventDefault();
+ },
+
+
+ // Abstract functions definitions.
+
+ /**
+ * Callback to use when dragging starts.
+ *
+ * @method drag_start
+ * @param {EventFacade} e
+ */
+ drag_start: function() {},
+
+ /**
+ * Callback to use when dragging ends.
+ *
+ * @method drag_end
+ * @param {EventFacade} e
+ */
+ drag_end: function() {},
+
+ /**
+ * Callback to use during dragging.
+ *
+ * @method drag_drag
+ * @param {EventFacade} e
+ */
+ drag_drag: function() {},
+
+ /**
+ * Callback to use when dragging ends and is not over a drop target.
+ *
+ * @method drag_dropmiss
+ * @param {EventFacade} e
+ */
+ drag_dropmiss: function() {},
+
+ /**
+ * Callback to use when a drop over event occurs.
+ *
+ * @method drop_over
+ * @param {EventFacade} e
+ */
+ drop_over: function() {},
+
+ /**
+ * Callback to use on drop:hit.
+ *
+ * @method drop_hit
+ * @param {EventFacade} e
+ */
+ drop_hit: function() {}
+}, {
+ NAME: 'dragdrop',
+ ATTRS: {}
+});
+
+M.core = M.core || {};
+M.core.dragdrop = DRAGDROP;
--- /dev/null
+{
+ "moodle-core-dragdrop": {
+ "requires": [
+ "base",
+ "node",
+ "io",
+ "dom",
+ "dd",
+ "event-key",
+ "event-focus",
+ "moodle-core-notification"
+ ]
+ }
+}
$this->log('Current plugin code location: '.$sourcelocation);
$this->log('Moving the current code into archive: '.$backuplocation);
- // We don't want to touch files unless we are pretty sure it would be all ok.
- if (!$this->move_directory_source_precheck($sourcelocation)) {
- throw new backup_folder_exception('Unable to backup the current version of the plugin (source precheck failed)');
- }
- if (!$this->move_directory_target_precheck($backuplocation)) {
- throw new backup_folder_exception('Unable to backup the current version of the plugin (backup precheck failed)');
- }
+ if (file_exists($sourcelocation)) {
+ // We don't want to touch files unless we are pretty sure it would be all ok.
+ if (!$this->move_directory_source_precheck($sourcelocation)) {
+ throw new backup_folder_exception('Unable to backup the current version of the plugin (source precheck failed)');
+ }
+ if (!$this->move_directory_target_precheck($backuplocation)) {
+ throw new backup_folder_exception('Unable to backup the current version of the plugin (backup precheck failed)');
+ }
- // Looking good, let's try it.
- if (!$this->move_directory($sourcelocation, $backuplocation, true)) {
- throw new backup_folder_exception('Unable to backup the current version of the plugin (moving failed)');
+ // Looking good, let's try it.
+ if (!$this->move_directory($sourcelocation, $backuplocation, true)) {
+ throw new backup_folder_exception('Unable to backup the current version of the plugin (moving failed)');
+ }
+
+ } else {
+ // Upgrading missing plugin - this happens often during upgrades.
+ if (!$this->create_directory_precheck($sourcelocation)) {
+ throw new filesystem_exception('Unable to prepare the plugin location (cannot create new directory)');
+ }
}
// Unzip the plugin package file into the target location.
return $rules;
}
+ /**
+ * Given a comment area, return the itemname that contains the itemid mappings.
+ *
+ * @param string $commentarea
+ * @return string
+ */
+ public function get_comment_mapping_itemname($commentarea) {
+ switch ($commentarea) {
+ case 'submission_comments':
+ $itemname = 'submission';
+ break;
+ default:
+ $itemname = parent::get_comment_mapping_itemname($commentarea);
+ break;
+ }
+
+ return $itemname;
+ }
}
$filename = $mainfile->get_filename();
$basepath = strstr($mainfilepath, $filename, true);
- $fullrelativefilepath = realpath($this->root_path.$basepath.$relativepath);
+ $fullrelativefilepath = realpath($this->get_rootpath().$basepath.$relativepath);
// Sanity check to make sure this path is inside this repository and the file exists.
- if (strpos($fullrelativefilepath, $this->root_path) === 0 && file_exists($fullrelativefilepath)) {
+ if (strpos($fullrelativefilepath, $this->get_rootpath()) === 0 && file_exists($fullrelativefilepath)) {
send_file($fullrelativefilepath, basename($relativepath), null, 0);
}
}
.file-picker textarea {
background-color: #EEE;
}
-/* Important z-index fixes
--------------------------*/
-.yui-skin-sam .yui-panel-container {
- z-index: 999999!important;
-}
-body#page-course-view-topics.path-course div.moodle-dialogue-base div.yui3-widget{
- z-index: 600!important;
-}
/* Moodle forms
----------------*/
bootstrapcollapse.js, bootstrapdropdown.js, bootstrapengine.js
--------------------------------------------------------------
-This theme uses YUI ports of the Twitter bootstrap jQuery based libs. These ported files are available on:
-
-https://github.com/jshirley/yui3-gallery/blob/master/src/gallery-bootstrap-collapse/js/bootstrap-collapse.js
-https://github.com/jshirley/yui3-gallery/blob/master/src/gallery-bootstrap-dropdown/js/bootstrap-dropdown.js
-https://github.com/jshirley/yui3-gallery/blob/master/src/gallery-bootstrap-engine/js/bootstrap-engine.js
-
-The content of these files are slightly modified to make sure all required YUI libraries are loaded. To achieve
-that the first and last line of each of these files has been modified.
+This theme uses YUI ports of the Twitter bootstrap jQuery based libs.
+
+Upgrade procedure:
+* git clone https://github.com/jshirley/yui-gallery.git
+* from that repository copy:
+** build/gallery-bootstrap-collapse/gallery-bootstrap-collapse-debug.js to js/gallery-bootstrapcollapse.js
+** build/gallery-bootstrap-dropdown/gallery-bootstrap-dropdown-debug.js to js/gallery-bootstrapdropdown.js
+** build/gallery-bootstrap-engine/gallery-bootstrap-engine-debug.js to js/gallery-bootstrapengine.js
+* apply patches from MDL-43152 to address linting issues
+* run shifter on this directory as required
+* update ../../../thirdpartylibs.xml
The YUI port of the Twitter bootstrap libs are now longer maintained. If you need all of the Bootstrap JavaScript
functionality consider switching to the original jQuery version of these file
-
-If you do want to update use these file locations:
-theme/bootstrapbase/yui/src/bootstrap/js/bootstrap-collapse.js
-theme/bootstrapbase/yui/src/bootstrap/js/bootstrap-dropdown.js
-theme/bootstrapbase/yui/src/bootstrap/js/bootstrap-engine.js
@class Bootstrap.Collapse
**/
-function CollapsePlugin(config) {
+function CollapsePlugin() {
CollapsePlugin.superclass.constructor.apply(this, arguments);
}
* <code>data-target</code> or <code>href</code> attribute.
*/
hide: function() {
- var showClass = this.config.showClass,
- hideClass = this.config.hideClass,
- node = this._getTarget();
+ var node = this._getTarget();
if ( this.transitioning ) {
return;
* <code>data-target</code> or <code>href</code> attribute.
*/
show: function() {
- var showClass = this.config.showClass,
- hideClass = this.config.hideClass,
- node = this._getTarget(),
+ var node = this._getTarget(),
host = this._node,
self = this,
parent,
}, '@VERSION@' ,{requires:['plugin','transition','event','event-delegate']});
-;
\ No newline at end of file
var NS = Y.namespace('Bootstrap');
-function DropdownPlugin(config) {
+function DropdownPlugin() {
DropdownPlugin.superclass.constructor.apply(this, arguments);
}
className = this.config.className;
target.toggleClass( className );
- target.once('clickoutside', function(e) {
+ target.once('clickoutside', function() {
target.toggleClass( className );
});
},
}, '@VERSION@' ,{requires:['plugin','event','event-outside']});
-;
}, '@VERSION@' ,{requires:['node','base-base']});
-;
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+// Get the HTML for the settings bits.
+$html = theme_clean_get_html_for_settings($OUTPUT, $PAGE);
+
echo $OUTPUT->doctype() ?>
<html <?php echo $OUTPUT->htmlattributes(); ?>>
<head>