{
+ 'plugins': [
+ 'promise',
+ ],
'env': {
'browser': true,
'amd': true
'unicode-bom': 'error',
'wrap-regex': 'off',
+ // === Promises ===
+ 'promise/always-return': 'warn',
+ 'promise/no-return-wrap': 'warn',
+ 'promise/param-names': 'warn',
+ 'promise/catch-or-return': ['warn', {terminationMethod: ['catch', 'fail']}],
+ 'promise/no-native': 'warn',
+ 'promise/no-promise-in-callback': 'warn',
+ 'promise/no-callback-in-promise': 'warn',
+ 'promise/avoid-new': 'warn',
+
// === Deprecations ===
"no-restricted-properties": ['warn', {
'object': 'M',
'property': 'str',
'message': 'Use AMD module "core/str" or M.util.get_string()'
}],
+
}
}
opts: {stdio: 'inherit', env: process.env}
}, function(error, result, code) {
// Propagate the exit code.
- done(code);
+ done(code === 0);
});
};
-h, --help Print out this help
Example:
-\$ sudo -u www-data /usr/bin/php admin/cli/mysql_collation.php --collation=utf8_general_ci
+\$ sudo -u www-data /usr/bin/php admin/cli/mysql_collation.php --collation=utf8mb4_unicode_ci
";
if (!empty($options['collation'])) {
$skipped++;
} else {
- $DB->change_database_structure("ALTER TABLE $table->name DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation");
- echo "CONVERTED\n";
- $converted++;
+ try {
+ $DB->change_database_structure("ALTER TABLE $table->name CONVERT TO CHARACTER SET $charset COLLATE $collation");
+ echo "CONVERTED\n";
+ $converted++;
+ } catch (ddl_exception $e) {
+ $result = mysql_set_row_format($table->name, $charset, $collation, $engine);
+ if ($result) {
+ echo "CONVERTED\n";
+ $converted++;
+ } else {
+ // We don't know what the problem is. Stop the conversion.
+ cli_error("Error: Tried to convert $table->name, but there was a problem. Please check the details of this
+ table and try again.");
+ die();
+ }
+ }
}
$sql = "SHOW FULL COLUMNS FROM $table->name WHERE collation IS NOT NULL";
$rs->close();
return $collations;
}
+
+function mysql_set_row_format($tablename, $charset, $collation, $engine) {
+ global $DB;
+
+ $sql = "SELECT row_format
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE table_schema = DATABASE() AND table_name = ?";
+ $rs = $DB->get_record_sql($sql, array($tablename));
+ if ($rs) {
+ if ($rs->row_format == 'Compact' || $rs->row_format == 'Redundant') {
+ $rowformat = $DB->get_row_format_sql($engine, $collation);
+ // Try to convert to compressed format and then try updating the collation again.
+ $DB->change_database_structure("ALTER TABLE $tablename $rowformat");
+ $DB->change_database_structure("ALTER TABLE $tablename CONVERT TO CHARACTER SET $charset COLLATE $collation");
+ } else {
+ // Row format may not be the problem. Can not diagnose problem. Send fail reply.
+ return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
$mediancoursesize = get_config('hub', 'site_mediancoursesize_' . $cleanhuburl);
$participantnumberaveragecfg = get_config('hub', 'site_participantnumberaverage_' . $cleanhuburl);
$modulenumberaveragecfg = get_config('hub', 'site_modulenumberaverage_' . $cleanhuburl);
+ // Mobile related information.
+ $mobileservicesenabled = get_config('hub', 'site_mobileservicesenabled_' . $cleanhuburl);
+ $mobilenotificacionsenabled = get_config('hub', 'site_mobilenotificacionsenabled_' . $cleanhuburl);
+ $registereduserdevices = get_config('hub', 'site_registereduserdevices_' . $cleanhuburl);
+ $registeredactiveuserdevices = get_config('hub', 'site_registeredactiveuserdevices_' . $cleanhuburl);
//hidden parameters
$mform->addElement('hidden', 'huburl', $huburl);
require_once($CFG->libdir . '/badgeslib.php');
$badges = $DB->count_records_select('badge', 'status <> ' . BADGE_STATUS_ARCHIVED);
$issuedbadges = $DB->count_records('badge_issued');
+ // Mobile related information.
+ $ismobileenabled = false;
+ $aremobilenotificationsenabled = false;
+ $registereduserdevicescount = 0;
+ $registeredactiveuserdevicescount = 0;
+ if (!empty($CFG->enablewebservices) && !empty($CFG->enablemobilewebservice)) {
+ $ismobileenabled = true;
+ $registereduserdevicescount = $DB->count_records('user_devices');
+ $airnotifierextpath = $CFG->dirroot . '/message/output/airnotifier/externallib.php';
+ if (file_exists($airnotifierextpath)) { // Maybe some one uninstalled the plugin.
+ require_once($airnotifierextpath);
+ $aremobilenotificationsenabled = (bool) message_airnotifier_external::is_system_configured();
+ $registeredactiveuserdevicescount = $DB->count_records('message_airnotifier_devices', array('enable' => 1));
+ }
+ }
if (HUB_MOODLEORGHUBURL != $huburl) {
$mform->addElement('checkbox', 'courses', get_string('sendfollowinginfo', 'hub'),
" " . get_string('modulenumberaverage', 'hub', $modulenumberaverage));
$mform->setDefault('modulenumberaverage', $modulenumberaveragecfg != -1);
$mform->setType('modulenumberaverage', PARAM_FLOAT);
+
+ $mobileservicestatus = $ismobileenabled ? 'yes' : 'no';
+ $mform->addElement('checkbox', 'mobileservicesenabled', '',
+ " " . get_string('mobileservicesenabled', 'hub', $mobileservicestatus));
+ $mform->setDefault('mobileservicesenabled', $mobileservicesenabled != -1);
+ $mform->setType('mobileservicesenabled', PARAM_INT);
+
+ $mobilenotificationsstatus = $aremobilenotificationsenabled ? 'yes' : 'no';
+ $mform->addElement('checkbox', 'mobilenotificacionsenabled', '',
+ " " . get_string('mobilenotificacionsenabled', 'hub', $mobilenotificationsstatus));
+ $mform->setDefault('mobilenotificacionsenabled', $mobilenotificacionsenabled != -1);
+ $mform->setType('mobilenotificacionsenabled', PARAM_INT);
+
+ $mform->addElement('checkbox', 'registereduserdevices', '',
+ " " . get_string('registereduserdevices', 'hub', $registereduserdevicescount));
+ $mform->setDefault('registereduserdevices', $registereduserdevices != -1);
+ $mform->setType('registereduserdevices', PARAM_INT);
+
+ $mform->addElement('checkbox', 'registeredactiveuserdevices', '',
+ " " . get_string('registeredactiveuserdevices', 'hub', $registeredactiveuserdevicescount));
+ $mform->setDefault('registeredactiveuserdevices', $registeredactiveuserdevices != -1);
+ $mform->setType('registeredactiveuserdevices', PARAM_INT);
} else {
$mform->addElement('static', 'courseslabel', get_string('sendfollowinginfo', 'hub'),
" " . get_string('coursesnumber', 'hub', $coursecount));
" " . get_string('modulenumberaverage', 'hub', $modulenumberaverage));
$mform->addElement('hidden', 'modulenumberaverage', 1);
$mform->setType('modulenumberaverage', PARAM_FLOAT);
+
+ $mobileservicestatus = $ismobileenabled ? 'yes' : 'no';
+ $mform->addElement('static', 'mobileservicesenabledlabel', '',
+ " " . get_string('mobileservicesenabled', 'hub', $mobileservicestatus));
+ $mform->addElement('hidden', 'mobileservicesenabled', 1);
+ $mform->setType('mobileservicesenabled', PARAM_INT);
+
+ $mobilenotificationsstatus = $aremobilenotificationsenabled ? 'yes' : 'no';
+ $mform->addElement('static', 'mobilenotificacionsenabledlabel', '',
+ " " . get_string('mobilenotificacionsenabled', 'hub', $mobilenotificationsstatus));
+ $mform->addElement('hidden', 'mobilenotificacionsenabled', 1);
+ $mform->setType('mobilenotificacionsenabled', PARAM_INT);
+
+ $mform->addElement('static', 'registereduserdeviceslabel', '',
+ " " . get_string('registereduserdevices', 'hub', $registereduserdevicescount));
+ $mform->addElement('hidden', 'registereduserdevices', 1);
+ $mform->setType('registereduserdevices', PARAM_INT);
+
+ $mform->addElement('static', 'registeredactiveuserdeviceslabel', '',
+ " " . get_string('registeredactiveuserdevices', 'hub', $registeredactiveuserdevicescount));
+ $mform->addElement('hidden', 'registeredactiveuserdevices', 1);
+ $mform->setType('registeredactiveuserdevices', PARAM_INT);
}
//check if it's a first registration or update
$siteinfo['moodleversion'] = $CFG->version;
$siteinfo['moodlerelease'] = $CFG->release;
$siteinfo['url'] = $CFG->wwwroot;
+ // Mobile related information.
+ $siteinfo['mobileservicesenabled'] = 0;
+ $siteinfo['mobilenotificacionsenabled'] = 0;
+ $siteinfo['registereduserdevices'] = 0;
+ $siteinfo['registeredactiveuserdevices'] = 0;
+ if (!empty($CFG->enablewebservices) && !empty($CFG->enablemobilewebservice)) {
+ $siteinfo['mobileservicesenabled'] = 1;
+ $siteinfo['registereduserdevices'] = $DB->count_records('user_devices');
+ $airnotifierextpath = $CFG->dirroot . '/message/output/airnotifier/externallib.php';
+ if (file_exists($airnotifierextpath)) { // Maybe some one uninstalled the plugin.
+ require_once($airnotifierextpath);
+ $siteinfo['mobilenotificacionsenabled'] = message_airnotifier_external::is_system_configured();
+ $siteinfo['registeredactiveuserdevices'] = $DB->count_records('message_airnotifier_devices', array('enable' => 1));
+ }
+ }
return $siteinfo;
}
// Set to -1 all optional data marked as "don't send" by the admin.
// The function get_site_info() will not calculate the optional data if config is set to -1.
$inputnames = array('courses', 'users', 'roleassignments', 'posts', 'questions', 'resources',
- 'badges', 'issuedbadges', 'modulenumberaverage', 'participantnumberaverage');
+ 'badges', 'issuedbadges', 'modulenumberaverage', 'participantnumberaverage',
+ 'mobileservicesenabled', 'mobilenotificacionsenabled', 'registereduserdevices', 'registeredactiveuserdevices');
foreach ($inputnames as $inputname) {
if (empty($fromform->{$inputname})) {
$fromform->{$inputname} = -1;
set_config('site_issuedbadges_' . $cleanhuburl, $fromform->issuedbadges, 'hub');
set_config('site_modulenumberaverage_' . $cleanhuburl, $fromform->modulenumberaverage, 'hub');
set_config('site_participantnumberaverage_' . $cleanhuburl, $fromform->participantnumberaverage, 'hub');
+ set_config('site_mobileservicesenabled_' . $cleanhuburl, $fromform->mobileservicesenabled, 'hub');
+ set_config('site_mobilenotificacionsenabled_' . $cleanhuburl, $fromform->mobilenotificacionsenabled, 'hub');
+ set_config('site_registereduserdevices_' . $cleanhuburl, $fromform->registereduserdevices, 'hub');
+ set_config('site_registeredactiveuserdevices_' . $cleanhuburl, $fromform->registeredactiveuserdevices, 'hub');
}
/////// UPDATE ACTION ////////
$fromform->modulenumberaverage = $siteinfo['modulenumberaverage'];
$fromform->participantnumberaverage = $siteinfo['participantnumberaverage'];
$fromform->street = $siteinfo['street'];
+ $fromform->mobileservicesenabled = $siteinfo['mobileservicesenabled'];
+ $fromform->mobilenotificacionsenabled = $siteinfo['mobilenotificacionsenabled'];
+ $fromform->registereduserdevices = $siteinfo['registereduserdevices'];
+ $fromform->registeredactiveuserdevices = $siteinfo['registeredactiveuserdevices'];
$params = (array) $fromform; //we are using the form input as the redirection parameters (token, url and name)
'idnumber' => new lang_string('sort_idnumber', 'admin'),
);
$temp->add(new admin_setting_configselect('navsortmycoursessort', new lang_string('navsortmycoursessort', 'admin'), new lang_string('navsortmycoursessort_help', 'admin'), 'sortorder', $sortoptions));
- $temp->add(new admin_setting_configtext('navcourselimit',new lang_string('navcourselimit','admin'),new lang_string('confignavcourselimit', 'admin'),20,PARAM_INT));
+ $temp->add(new admin_setting_configtext('navcourselimit', new lang_string('navcourselimit', 'admin'),
+ new lang_string('confignavcourselimit', 'admin'), 10, PARAM_INT));
$temp->add(new admin_setting_configcheckbox('usesitenameforsitepages', new lang_string('usesitenameforsitepages', 'admin'), new lang_string('configusesitenameforsitepages', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('linkadmincategories', new lang_string('linkadmincategories', 'admin'), new lang_string('linkadmincategories_help', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('linkcoursesections', new lang_string('linkcoursesections', 'admin'), new lang_string('linkcoursesections_help', 'admin'), 0));
// Convert plugins.
$ADMIN->add('modules', new admin_category('fileconverterplugins', new lang_string('type_fileconverter_plural', 'plugin')));
- $temp = new admin_settingpage('managefileconverterplugins', new lang_string('type_fileconverter', 'plugin'));
+ $temp = new admin_settingpage('managefileconverterplugins', new lang_string('type_fileconvertermanage', 'plugin'));
$temp->add(new admin_setting_manage_fileconverter_plugins());
$ADMIN->add('fileconverterplugins', $temp);
$optionalsubsystems->add(new admin_setting_configcheckbox('enableglobalsearch', new lang_string('enableglobalsearch', 'admin'),
new lang_string('enableglobalsearch_desc', 'admin'), 0, 1, 0));
- $choices = array();
- $choices[0] = new lang_string('no');
- $choices[1] = new lang_string('yes');
- $optionalsubsystems->add(new admin_setting_configselect('allowstealth', new lang_string('allowstealthmodules'),
- new lang_string('allowstealthmodules_help'), 0, $choices));
+ $optionalsubsystems->add(new admin_setting_configcheckbox('allowstealth', new lang_string('allowstealthmodules'),
+ new lang_string('allowstealthmodules_help'), 0, 1, 0));
}
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template core_admin/setting_filetypes
+
+ Renders the admin_setting_filetypes setting element.
+
+ Context variables required for this template:
+ * id - element id
+ * name - form element name
+ * value - element value
+ * descriptions - data for the core_form/filetypes-descriptions template
+
+ Example context (json):
+ {
+ "id": "test0",
+ "name": "test",
+ "value": ".jpg,.gif",
+ "descriptions": {
+ "hasdescriptions": true,
+ "descriptions": [
+ {
+ "description": "Image (JPEG)",
+ "extensions": ".jpeg .jpe .jpg"
+ },
+ {
+ "description": "Image (GIF)",
+ "extensions": ".gif"
+ }
+ ]
+ }
+ }
+}}
+<div class="form-text defaultsnext">
+ <input type="text" name="{{name}}" value="{{value}}" size="30" id="{{id}}" class="text-ltr">
+ <span data-filetypesbrowser="{{id}}"></span>
+ <div data-filetypesdescriptions="{{id}}">{{#descriptions}}{{>core_form/filetypes-descriptions}}{{/descriptions}}</div>
+</div>
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
}
}
- // Roll dates.
- $data->timecreated = $this->apply_date_offset($data->timecreated);
+ // There is no need to roll dates. Logs are supposed to be immutable. See MDL-44961.
// Revert other to its original php way.
$data->other = unserialize(base64_decode($data->other));
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
html,
self._afterRender.bind(self)
);
+ return;
}).fail(Notification.exception);
};
return self._render().then(function(html) {
self._find('[data-region="action-selector"]').replaceWith(html);
self._afterRender();
+ return;
});
};
pagerender = 'tool_lp/plan_page';
pageregion = 'plan-page';
}
-
ajax.call(requests)[requests.length - 1].then(function(context) {
- return templates.render(pagerender, context).done(function(html, js) {
- $('[data-region="' + pageregion + '"]').replaceWith(html);
- templates.runTemplateJS(js);
- });
- }, notification.exception);
+ return templates.render(pagerender, context);
+ }).then(function(html, js) {
+ $('[data-region="' + pageregion + '"]').replaceWith(html);
+ templates.runTemplateJS(js);
+ return;
+ }).catch(notification.exception);
});
}
// We're done, let's trigger a change.
self._templateLoaded = true;
self._triggerChange();
+ return;
});
};
var promises = ajax.call(calls);
promises[calls.length - 1].then(function(context) {
- return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
- $('[data-region="relatedcompetencies"]').replaceWith(html);
- templates.runTemplateJS(js);
- updatedRelatedCompetencies();
- });
- }, notification.exception);
+ return templates.render('tool_lp/related_competencies', context);
+ }).then(function(html, js) {
+ $('[data-region="relatedcompetencies"]').replaceWith(html);
+ templates.runTemplateJS(js);
+ updatedRelatedCompetencies();
+ return;
+ }).catch(notification.exception);
});
}
relatedTarget.ruleconfig = config.ruleconfig;
renderCompetencySummary(relatedTarget);
}
- }, notification.exception);
+ return;
+ }).catch(notification.exception);
};
/**
type: strs[1]
};
}
- }).then(function() {
- return templates.render('tool_lp/competency_summary', context).then(function(html) {
- $('[data-region="competencyinfo"]').html(html);
- $('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
- });
- }).then(function() {
+ return context;
+ }).then(function(context) {
+ return templates.render('tool_lp/competency_summary', context);
+ }).then(function(html) {
+ $('[data-region="competencyinfo"]').html(html);
+ $('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
return templates.render('tool_lp/loading', {});
}).then(function(html, js) {
templates.replaceNodeContents('[data-region="relatedcompetencies"]', html, js);
- }).done(function() {
- ajax.call([{
+ return ajax.call([{
methodname: 'tool_lp_data_for_related_competencies_section',
- args: {competencyid: competency.id},
- done: function(context) {
- return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
- $('[data-region="relatedcompetencies"]').replaceWith(html);
- templates.runTemplateJS(js);
- updatedRelatedCompetencies();
- });
- }
- }]);
- }).fail(notification.exception);
+ args: {competencyid: competency.id}
+ }])[0];
+ }).then(function(context) {
+ return templates.render('tool_lp/related_competencies', context);
+ }).then(function(html, js) {
+ $('[data-region="relatedcompetencies"]').replaceWith(html);
+ templates.runTemplateJS(js);
+ updatedRelatedCompetencies();
+ return;
+ }).catch(notification.exception);
};
/**
// Log Competency viewed event.
triggerCompetencyViewedEvent(competency);
}
-
strSelectedTaxonomy(level).then(function(str) {
selectedTitle.text(str);
- });
+ return;
+ }).catch(notification.exception);
strAddTaxonomy(sublevel).then(function(str) {
btn.show()
.find('[data-region="term"]')
.text(str);
- });
+ return;
+ }).catch(notification.exception);
// We handled this event so consume it.
evt.preventDefault();
if (!self._singleFramework) {
self._find('[data-action="chooseframework"]').change(function(e) {
self._frameworkId = $(e.target).val();
- self._loadCompetencies().then(self._refresh.bind(self));
+ self._loadCompetencies().then(self._refresh.bind(self)).catch(Notification.exception);
});
}
*/
Picker.prototype.display = function() {
var self = this;
- return self._render().then(function(html) {
- return Str.get_string('competencypicker', 'tool_lp').then(function(title) {
- self._popup = new Dialogue(
- title,
- html,
- self._afterRender.bind(self)
- );
- });
- }).fail(Notification.exception);
+ return $.when(Str.get_string('competencypicker', 'tool_lp'), self._render())
+ .then(function(title, render) {
+ self._popup = new Dialogue(
+ title,
+ render[0],
+ self._afterRender.bind(self)
+ );
+ return;
+ }).catch(Notification.exception);
};
/**
return self._render().then(function(html) {
self._find('[data-region="competencylinktree"]').replaceWith(html);
self._afterRender();
+ return;
});
};
if (!self._singlePlan) {
self._find('[data-action="chooseplan"]').change(function(e) {
self._planId = $(e.target).val();
- self._loadCompetencies().then(self._refresh.bind(self));
+ self._loadCompetencies().then(self._refresh.bind(self))
+ .catch(Notification.exception);
});
}
};
if (!self._competency) {
return false;
}
- return self._render().then(function(html) {
- return Str.get_string('competencyrule', 'tool_lp').then(function(title) {
- self._popup = new Dialogue(
- title,
- html,
- self._afterRender.bind(self)
- );
- });
+ return $.when(Str.get_string('competencyrule', 'tool_lp'), self._render())
+ .then(function(title, render) {
+ self._popup = new Dialogue(
+ title,
+ render[0],
+ self._afterRender.bind(self)
+ );
+ return;
}).fail(Notification.exception);
};
*/
RuleConfig.prototype._initOutcomes = function() {
var self = this;
-
return Outcomes.getAll().then(function(outcomes) {
self._outcomesOption = outcomes;
+ return;
});
};
RuleConfig.prototype._initRules = function() {
var self = this,
promises = [];
-
$.each(self._rules, function(index, rule) {
var promise = rule.init().then(function() {
rule.setTargetCompetency(self._competency);
rule.on('change', self._afterRuleConfigChange.bind(self));
+ return;
}, function() {
// Upon failure remove the rule, and resolve the promise.
self._rules.splice(index, 1);
self._afterChange();
return;
}
-
rule.injectTemplate(container).then(function() {
container.show();
- }, function() {
- container.empty().hide();
+ return;
}).always(function() {
self._afterChange();
+ }).catch(function() {
+ container.empty().hide();
});
};
}]);
promise[0].then(function() {
parent.remove();
+ return;
}).fail(Notification.exception);
}
);
includes: includes
}
}]);
-
promise[0].then(function(results) {
var promises = [],
i = 0;
i++;
});
success(results.cohorts);
+ return;
});
- }, failure);
+ }).catch(failure);
}
};
i++;
});
success(results.users);
+ return;
});
- }, failure);
+ }).catch(failure);
}
};
* @return {Promise}
*/
list: function(contextId, options) {
- var promise,
- args = {
+ var args = {
context: {
contextid: contextId
}
};
$.extend(args, typeof options === 'undefined' ? {} : options);
- promise = Ajax.call([{
+ return Ajax.call([{
methodname: 'core_competency_list_competency_frameworks',
args: args
}])[0];
-
- return promise.fail(Notification.exception);
},
/**
* @param {String} query The query string.
* @param {Function} callback A callback function receiving an array of results.
*/
+ /* eslint-disable promise/no-callback-in-promise */
transport: function(selector, query, callback) {
var el = $(selector),
contextId = el.data('contextid'),
if (!contextId) {
throw new Error('The attribute data-contextid is required on ' + selector);
}
-
this.list(contextId, {
query: query,
onlyvisible: onlyVisible,
- }).then(callback);
+ }).then(callback).catch(Notification.exception);
}
};
Str.get_string('competencyframeworkroot', 'tool_lp').then(function(rootframework) {
$(self.staticElementSelector).html(rootframework);
$(self.inputHiddenSelector).val(data.competencyId);
+ return;
}).fail(Notification.exception);
}
};
* Callback to render the region template.
*
* @param {Object} context The context for the template.
+ * @return {Promise}
*/
PlanActions.prototype._renderView = function(context) {
var self = this;
- templates.render(self._template, context)
- .done(function(newhtml, newjs) {
+ return templates.render(self._template, context)
+ .then(function(newhtml, newjs) {
$(self._region).replaceWith(newhtml);
templates.runTemplateJS(newjs);
- })
- .fail(notification.exception);
+ return;
+ });
};
/**
*/
PlanActions.prototype._callAndRefresh = function(calls, planData) {
var self = this;
-
calls.push({
methodname: self._contextMethod,
args: self._getContextArgs(planData)
});
// Apply all the promises, and refresh when the last one is resolved.
- return $.when.apply($.when, ajax.call(calls))
+ return $.when.apply($, ajax.call(calls))
.then(function() {
- self._renderView(arguments[arguments.length - 1]);
+ return self._renderView(arguments[arguments.length - 1]);
})
.fail(notification.exception);
};
done: this._contextLoaded.bind(this),
fail: notification.exception
}]);
-
// Log the user competency viewed in plan event.
requests[0].then(function(result) {
var eventMethodName = 'core_competency_user_competency_viewed_in_plan';
if (result.plan.iscompleted) {
eventMethodName = 'core_competency_user_competency_plan_viewed';
}
- ajax.call([{
+ return ajax.call([{
methodname: eventMethodName,
- args: {competencyid: competencyId, userid: userId, planid: planId},
- fail: notification.exception
- }]);
- });
+ args: {competencyid: competencyId, userid: userId, planid: planId}
+ }])[0];
+ }).catch(notification.exception);
};
/**
Ajax.call([call])[0].then(function() {
this._trigger('review-request-cancelled', data);
this._trigger('status-changed', data);
- }.bind(this), function() {
+ }.bind(this)).catch(function() {
this._trigger('error-occured', data);
}.bind(this));
};
Ajax.call([call])[0].then(function() {
this._trigger('review-requested', data);
this._trigger('status-changed', data);
- }.bind(this), function() {
+ }.bind(this)).catch(function() {
this._trigger('error-occured', data);
}.bind(this));
};
competencyid: data.competencyid
}
};
-
Ajax.call([call])[0].then(function() {
this._trigger('review-started', data);
this._trigger('status-changed', data);
- }.bind(this), function() {
+ }.bind(this)).catch(function() {
this._trigger('error-occured', data);
}.bind(this));
};
Ajax.call([call])[0].then(function() {
this._trigger('review-stopped', data);
this._trigger('status-changed', data);
- }.bind(this), function() {
+ }.bind(this)).catch(function() {
this._trigger('error-occured', data);
}.bind(this));
};
* Callback to render the region template.
*
* @param {Object} context The context for the template.
+ * @return {Promise}
*/
UserEvidenceActions.prototype._renderView = function(context) {
var self = this;
- templates.render(self._template, context)
- .done(function(newhtml, newjs) {
+ return templates.render(self._template, context)
+ .then(function(newhtml, newjs) {
templates.replaceNode($(self._region), newhtml, newjs);
- })
- .fail(notification.exception);
+ return;
+ });
};
/**
*/
UserEvidenceActions.prototype._callAndRefresh = function(calls, evidenceData) {
var self = this;
-
calls.push({
methodname: self._contextMethod,
args: self._getContextArgs(evidenceData)
// Apply all the promises, and refresh when the last one is resolved.
return $.when.apply($.when, ajax.call(calls))
.then(function() {
- self._renderView(arguments[arguments.length - 1]);
+ return self._renderView(arguments[arguments.length - 1]);
})
.fail(notification.exception);
};
{{/config}}
</div>
- <div data-region="footer" class="pull-xs-right">
+ <div data-region="footer" class="pull-xs-right m-t-1">
{{#config}}
<input type="button" class="btn btn-primary" data-action="save" value="{{#str}}savechanges{{/str}}"/>
{{/config}}
<input type="button" class="btn btn-secondary" data-action="cancel" value="{{#str}}cancel{{/str}}"/>
</div>
+ <div class="clearfix"></div>
</div>
upgrade_plugin_savepoint(true, 2017021300, 'tool', 'monitor');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
args: {
component: component,
template: name,
- themename: config.theme
+ themename: config.theme,
+ includecomments: true
}
}, {
methodname: 'tool_templatelibrary_load_canonical_template',
templates.render('tool_usertours/tourstep', {})
).then(function(response, template) {
usertours.startBootstrapTour(tourId, template[0], response.tourconfig);
+ return;
}).fail(notification.exception);
},
if (response.startTour) {
usertours.fetchTour(response.startTour);
}
+ return;
}).fail(notification.exception);
}
};
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'cas');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017032800, 'auth', 'db');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'email');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'fc');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'imap');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
}
$ldapval = NULL;
foreach ($values as $value) {
- $entry = array_change_key_case($user_entry[0], CASE_LOWER);
+ $entry = $user_entry[0];
if (($value == 'dn') || ($value == 'distinguishedname')) {
$result[$key] = $user_dn;
continue;
if ($sr) {
$info = ldap_get_entries_moodle($ldapconnection, $sr);
if (!empty ($info)) {
- $info = array_change_key_case($info[0], CASE_LOWER);
+ $info = $info[0];
if (isset($info[$this->config->expireattr][0])) {
$expiretime = $this->ldap_expirationtime2unix($info[$this->config->expireattr][0], $ldapconnection, $user_dn);
if ($expiretime != 0) {
return false;
}
- $user_entry = array_change_key_case($user_entry[0], CASE_LOWER);
+ $user_entry = $user_entry[0];
foreach ($attrmap as $key => $ldapkeys) {
$profilefield = '';
$sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
if ($sr) {
$entry = ldap_get_entries_moodle($ldapconnection, $sr);
- $info = array_change_key_case($entry[0], CASE_LOWER);
+ $info = $entry[0];
$newattrs = array();
if (!empty($info[$this->config->expireattr][0])) {
// Set expiration time only if passwordExpirationInterval is defined
}
$entry = ldap_get_entries_moodle($ldapconn, $sr);
- $info = array_change_key_case($entry[0], CASE_LOWER);
+ $info = $entry[0];
$useraccountcontrol = $info['useraccountcontrol'][0];
if ($useraccountcontrol & UF_DONT_EXPIRE_PASSWD) {
// Password doesn't expire.
}
$entry = ldap_get_entries_moodle($ldapconn, $sr);
- $info = array_change_key_case($entry[0], CASE_LOWER);
+ $info = $entry[0];
$domaindn = $info['defaultnamingcontext'][0];
$sr = ldap_read ($ldapconn, $domaindn, '(objectClass=*)',
array('maxPwdAge'));
$entry = ldap_get_entries_moodle($ldapconn, $sr);
- $info = array_change_key_case($entry[0], CASE_LOWER);
+ $info = $entry[0];
$maxpwdage = $info['maxpwdage'][0];
if ($sr = ldap_read($ldapconn, $user_dn, '(objectClass=*)', array('msDS-ResultantPSO'))) {
if ($entry = ldap_get_entries_moodle($ldapconn, $sr)) {
- $info = array_change_key_case($entry[0], CASE_LOWER);
+ $info = $entry[0];
$userpso = $info['msds-resultantpso'][0];
// If a PSO exists, FGPP is being utilized.
if (!empty($userpso)) {
$sr = ldap_read($ldapconn, $userpso, '(objectClass=*)', array('msDS-MaximumPasswordAge'));
if ($entry = ldap_get_entries_moodle($ldapconn, $sr)) {
- $info = array_change_key_case($entry[0], CASE_LOWER);
+ $info = $entry[0];
// Default value of msds-maximumpasswordage is 42 and is always set.
$maxpwdage = $info['msds-maximumpasswordage'][0];
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'ldap');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'manual');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'mnet');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'nntp');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'none');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
];
$confirmationurl = new moodle_url('/auth/oauth2/confirm-linkedlogin.php', $params);
- // Remove data parameter just in case it was included in the confirmation so we can add it manually later.
- $data->link = $confirmationurl->out();
+ $data->link = $confirmationurl->out(false);
+ $message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
- $message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
+ $data->link = $confirmationurl->out();
$messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
$user->mailformat = 1; // Always send HTML version as well.
];
$confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params);
- $data->link = $confirmationurl->out();
+ $data->link = $confirmationurl->out(false);
+ $message = get_string('confirmaccountemail', 'auth_oauth2', $data);
- $message = get_string('confirmaccountemail', 'auth_oauth2', $data);
+ $data->link = $confirmationurl->out();
$messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
$user->mailformat = 1; // Always send HTML version as well.
if (!empty($user->picture)) {
return false;
}
+ if (!empty($CFG->enablegravatar)) {
+ return false;
+ }
+
$picture = $this->get_static_user_picture();
if (empty($picture)) {
return false;
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'pam');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'pop3');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'shibboleth');
}
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
protected function set_in_database($availability) {
global $DB;
- $DB->set_field('course_sections', 'availability', $availability,
- array('id' => $this->section->id));
+
+ $section = new \stdClass();
+ $section->id = $this->section->id;
+ $section->availability = $availability;
+ $section->timemodified = time();
+ $DB->update_record('course_sections', $section);
}
/**
// Save the updated course module.
if ($changed) {
- $DB->set_field('course_sections', 'availability', json_encode($tree->save()),
- array('id' => $section->id));
+ $updatesection = new \stdClass();
+ $updatesection->id = $section->id;
+ $updatesection->availability = json_encode($tree->save());
+ $updatesection->timemodified = time();
+ $DB->update_record('course_sections', $updatesection);
+
$anychanged = true;
}
}
/**
* Usually same than major release zero version, mainly for informative/historic purposes.
*/
- const RELEASE = '3.3';
+ const RELEASE = '3.4';
}
/*
$section = new backup_nested_element('section', array('id'), array(
'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible',
- 'availabilityjson'));
+ 'availabilityjson', 'timemodified'));
// attach format plugin structure to $section element, only one allowed
$this->add_plugin_structure('format', $section, false);
if (!$DB->record_exists('course_sections', array('course' => $this->get_courseid(), 'section' => $i))) {
$sectionrec = array(
'course' => $this->get_courseid(),
- 'section' => $i);
+ 'section' => $i,
+ 'timemodified' => time());
$DB->insert_record('course_sections', $sectionrec); // missing section created
}
}
$section = new stdclass();
$section->course = $this->get_courseid();
$section->section = $data->number;
+ $section->timemodified = isset($data->timemodified) ? $this->apply_date_offset($data->timemodified) : 0;
// Section doesn't exist, create it with all the info from backup
- if (!$secrec = $DB->get_record('course_sections', (array)$section)) {
+ if (!$secrec = $DB->get_record('course_sections', ['course' => $this->get_courseid(), 'section' => $data->number])) {
$section->name = $data->name;
$section->summary = $data->summary;
$section->summaryformat = $data->summaryformat;
array('id' => $availfield->coursesectionid), MUST_EXIST);
$newvalue = \core_availability\info::add_legacy_availability_field_condition(
$currentvalue, $availfield, $show);
- $DB->set_field('course_sections', 'availability', $newvalue,
- array('id' => $availfield->coursesectionid));
+
+ $section = new stdClass();
+ $section->id = $availfield->coursesectionid;
+ $section->availability = $newvalue;
+ $section->timemodified = time();
+ $DB->update_record('course_sections', $section);
}
}
$isuseroverride = !$data->courseid && $data->modulename && $data->instance;
// If we don't want to include user data and this record is a user override event
- // for an activity then we should not create it.
- if (!$this->task->get_setting_value('userinfo') && $isuseroverride) {
+ // for an activity then we should not create it. (Only activity events can be user override events - which must have this
+ // setting).
+ if ($isuseroverride && $this->task->setting_exists('userinfo') && !$this->task->get_setting_value('userinfo')) {
return;
}
$data = (object)($data);
- $data->time = $this->apply_date_offset($data->time);
+ // There is no need to roll dates. Logs are supposed to be immutable. See MDL-44961.
+
$data->userid = $this->get_mappingid('user', $data->userid);
$data->course = $this->get_courseid();
$data->cmid = 0;
$data = (object)($data);
- $data->time = $this->apply_date_offset($data->time);
+ // There is no need to roll dates. Logs are supposed to be immutable. See MDL-44961.
+
$data->userid = $this->get_mappingid('user', $data->userid);
$data->course = $this->get_courseid();
$data->cmid = $this->task->get_moduleid();
if (!$data->section) { // no sections in course, create section 0 and 1 and assign module to 1
$sectionrec = array(
'course' => $this->get_courseid(),
- 'section' => 0);
+ 'section' => 0,
+ 'timemodified' => time());
$DB->insert_record('course_sections', $sectionrec); // section 0
$sectionrec = array(
'course' => $this->get_courseid(),
- 'section' => 1);
+ 'section' => 1,
+ 'timemodified' => time());
$data->section = $DB->insert_record('course_sections', $sectionrec); // section 1
}
$data->groupingid= $this->get_mappingid('grouping', $data->groupingid); // grouping
} else {
$sequence = $newitemid;
}
- $DB->set_field('course_sections', 'sequence', $sequence, array('id' => $data->section));
+
+ $updatesection = new \stdClass();
+ $updatesection->id = $data->section;
+ $updatesection->sequence = $sequence;
+ $updatesection->timemodified = time();
+ $DB->update_record('course_sections', $updatesection);
// If there is the legacy showavailability data, store this for later use.
// (This data is not present when restoring 'new' backups.)
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for backup_xml_transformer class.
+ *
+ * @package core_backup
+ * @subpackage moodle2
+ * @category backup
+ * @copyright 2017 Dmitrii Metelkin (dmitriim@catalyst-au.net)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
+
+/**
+ * Tests for backup_xml_transformer.
+ *
+ * @package core_backup
+ * @copyright 2017 Dmitrii Metelkin (dmitriim@catalyst-au.net)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class backup_xml_transformer_testcase extends advanced_testcase {
+
+ /**
+ * Initial set up.
+ */
+ public function setUp() {
+ parent::setUp();
+
+ $this->resetAfterTest(true);
+ }
+
+ /**
+ * Data provider for ::test_filephp_links_replace.
+ *
+ * @return array
+ */
+ public function filephp_links_replace_data_provider() {
+ return array(
+ array('http://test.test/', 'http://test.test/'),
+ array('http://test.test/file.php/1', 'http://test.test/file.php/1'),
+ array('http://test.test/file.php/2/1.jpg', 'http://test.test/file.php/2/1.jpg'),
+ array('http://test.test/file.php/2', 'http://test.test/file.php/2'),
+ array('http://test.test/file.php/1/1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
+ array('http://test.test/file.php/1//1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
+ array('http://test.test/file.php?file=/1', '$@FILEPHP@$'),
+ array('http://test.test/file.php?file=/2/1.jpg', 'http://test.test/file.php?file=/2/1.jpg'),
+ array('http://test.test/file.php?file=/2', 'http://test.test/file.php?file=/2'),
+ array('http://test.test/file.php?file=/1/1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
+ array('http://test.test/file.php?file=/1//1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
+ array('http://test.test/file.php?file=%2f1', '$@FILEPHP@$'),
+ array('http://test.test/file.php?file=%2f2%2f1.jpg', 'http://test.test/file.php?file=%2f2%2f1.jpg'),
+ array('http://test.test/file.php?file=%2f2', 'http://test.test/file.php?file=%2f2'),
+ array('http://test.test/file.php?file=%2f1%2f1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
+ array('http://test.test/file.php?file=%2f1%2f%2f1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
+ array('http://test.test/file.php?file=%2F1', '$@FILEPHP@$'),
+ array('http://test.test/file.php?file=%2F2%2F1.jpg', 'http://test.test/file.php?file=%2F2%2F1.jpg'),
+ array('http://test.test/file.php?file=%2F2', 'http://test.test/file.php?file=%2F2'),
+ array('http://test.test/file.php?file=%2F1%2F1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
+ array('http://test.test/file.php?file=%2F1%2F%2F1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
+ );
+ }
+
+ /**
+ * Test that backup_xml_transformer replaces file php links to $@FILEPHP@$.
+ *
+ * @dataProvider filephp_links_replace_data_provider
+ * @param string $content Testing content.
+ * @param string $expected Expected result.
+ */
+ public function test_filephp_links_replace($content, $expected) {
+ global $CFG;
+
+ $CFG->wwwroot = 'http://test.test';
+
+ $transformer = new backup_xml_transformer(1);
+
+ $this->assertEquals($expected, $transformer->process($content));
+ }
+
+}
} else if (strpos($cdata, '$@FILEPHP@$') === false) { // No $@FILEPHP@$, nothing to convert
return $cdata;
}
+
+ if ($CFG->slasharguments) {
+ $slash = '/';
+ $forcedownload = '?forcedownload=1';
+ } else {
+ $slash = '%2F';
+ $forcedownload = '&forcedownload=1';
+ }
+
+ // We have to remove trailing slashes, otherwise file URLs will be restored with an extra slash.
+ $basefileurl = rtrim(moodle_url::make_legacyfile_url($this->courseid, null)->out(true), $slash);
// Decode file.php calls
$search = array ("$@FILEPHP@$");
- $replace = array(moodle_url::make_legacyfile_url($this->courseid, null));
+ $replace = array($basefileurl);
$result = str_replace($search, $replace, $cdata);
+
// Now $@SLASH@$ and $@FORCEDOWNLOAD@$ MDL-18799
$search = array('$@SLASH@$', '$@FORCEDOWNLOAD@$');
- if ($CFG->slasharguments) {
- $replace = array('/', '?forcedownload=1');
- } else {
- $replace = array('%2F', '&forcedownload=1');
- }
+ $replace = array($slash, $forcedownload);
+
return str_replace($search, $replace, $result);
}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for restore_structure_parser_processor class.
+ *
+ * @package core_backup
+ * @category test
+ * @copyright 2017 Dmitrii Metelkin (dmitriim@catalyst-au.net)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/helper/restore_structure_parser_processor.class.php');
+
+/**
+ * Tests for restore_structure_parser_processor class.
+ *
+ * @package core_backup
+ * @copyright 2017 Dmitrii Metelkin (dmitriim@catalyst-au.net)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class restore_structure_parser_processor_test extends advanced_testcase {
+
+ /**
+ * Initial set up.
+ */
+ public function setUp() {
+ parent::setUp();
+
+ $this->resetAfterTest(true);
+ }
+
+ /**
+ * Data provider for ::test_process_cdata.
+ *
+ * @return array
+ */
+ public function process_cdata_data_provider() {
+ return array(
+ array(null, null, true),
+ array("$@NULL@$", null, true),
+ array("$@NULL@$ ", "$@NULL@$ ", true),
+ array(1, 1, true),
+ array(" ", " ", true),
+ array("1", "1", true),
+ array("$@FILEPHP@$1.jpg", "$@FILEPHP@$1.jpg", true),
+ array(
+ "http://test.test/$@SLASH@$",
+ "http://test.test/$@SLASH@$",
+ true
+ ),
+ array(
+ "<a href='$@FILEPHP@$1.jpg'>Image</a>",
+ "<a href='http://test.test/file.php/11.jpg'>Image</a>",
+ true
+ ),
+ array(
+ "<a href='$@FILEPHP@$$@SLASH@$1.jpg'>Image</a>",
+ "<a href='http://test.test/file.php/1/1.jpg'>Image</a>",
+ true
+ ),
+ array(
+ "<a href='$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'>Image</a>",
+ "<a href='http://test.test/file.php/1//1.jpg'>Image</a>",
+ true
+ ),
+ array(
+ "<a href='$@FILEPHP@$1.jpg'>Image</a>",
+ "<a href='http://test.test/file.php?file=%2F11.jpg'>Image</a>",
+ false
+ ),
+ array(
+ "<a href='$@FILEPHP@$$@SLASH@$1.jpg'>Image</a>",
+ "<a href='http://test.test/file.php?file=%2F1%2F1.jpg'>Image</a>",
+ false
+ ),
+ array(
+ "<a href='$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'>Image</a>",
+ "<a href='http://test.test/file.php?file=%2F1%2F%2F1.jpg'>Image</a>",
+ false
+ ),
+ array(
+ "<a href='$@FILEPHP@$$@SLASH@$1.jpg$@FORCEDOWNLOAD@$'>Image</a>",
+ "<a href='http://test.test/file.php/1/1.jpg?forcedownload=1'>Image</a>",
+ true
+ ),
+ array(
+ "<a href='$@FILEPHP@$$@SLASH@$1.jpg$@FORCEDOWNLOAD@$'>Image</a>",
+ "<a href='http://test.test/file.php?file=%2F1%2F1.jpg&forcedownload=1'>Image</a>",
+ false
+ ),
+ );
+ }
+
+ /**
+ * Test that restore_structure_parser_processor replaces $@FILEPHP@$ to correct file php links.
+ *
+ * @dataProvider process_cdata_data_provider
+ * @param string $content Testing content.
+ * @param string $expected Expected result.
+ * @param bool $slasharguments A value for $CFG->slasharguments setting.
+ */
+ public function test_process_cdata($content, $expected, $slasharguments) {
+ global $CFG;
+
+ $CFG->slasharguments = $slasharguments;
+ $CFG->wwwroot = 'http://test.test';
+
+ $processor = new restore_structure_parser_processor(1, 1);
+
+ $this->assertEquals($expected, $processor->process_cdata($content));
+ }
+
+}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
if ($USER->id != $user->id) {
echo html_writer::start_tag('tr');
echo html_writer::start_tag('td', array('colspan' => '2'));
- echo html_writer::tag('b', get_string('showinguser', 'completion'));
+ echo html_writer::tag('b', get_string('showinguser', 'completion') . ' ');
$url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id));
echo html_writer::link($url, fullname($user));
echo html_writer::end_tag('td');
echo html_writer::start_tag('tr');
echo html_writer::start_tag('td', array('colspan' => '2'));
-echo html_writer::tag('b', get_string('status'));
+echo html_writer::tag('b', get_string('status') . ' ');
// Is course complete?
$coursecomplete = $info->is_course_complete($user->id);
} else {
echo html_writer::start_tag('tr');
echo html_writer::start_tag('td', array('colspan' => '2'));
- echo html_writer::tag('b', get_string('required'));
+ echo html_writer::tag('b', get_string('required') . ' ');
// Get overall aggregation method.
$overall = $info->get_aggregation_method();
echo core_text::strtolower(get_string('any', 'completion'));
}
- echo html_writer::end_tag('i') .core_text::strtolower(get_string('required')).')';
+ echo ' ' . html_writer::end_tag('i') .core_text::strtolower(get_string('required')).')';
$agg_type = false;
}
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Request data from the server.
return promise.then(function(result) {
- return result.events;
- }).then(function(calendarEvents) {
- if (!calendarEvents.length || (calendarEvents.length < limit)) {
- // We have no more events so mark the list as done.
+ if (!result.events.length) {
+ // No events, nothing to do.
setLoadedAll(root);
+ return 0;
}
- if (calendarEvents.length) {
- // Remember the last id we've seen.
- root.attr('data-last-id', calendarEvents[calendarEvents.length - 1].id);
-
- // Render the events.
- return render(root, calendarEvents).then(function(renderCount) {
- updateContentVisibility(root, calendarEvents.length);
-
- if (renderCount < calendarEvents.length) {
- // if the number of events that was rendered is less than
- // the number we sent for rendering we can assume that there
- // are no groups to add them in. Since the ordering of the
- // events is guaranteed it means that any future requests will
- // also yield events that can't be rendered, so let's not bother
- // sending any more requests.
- setLoadedAll(root);
- }
- });
- } else {
- updateContentVisibility(root, calendarEvents.length);
+ var calendarEvents = result.events;
+
+ // Remember the last id we've seen.
+ root.attr('data-last-id', calendarEvents[calendarEvents.length - 1].id);
+
+ if (calendarEvents.length < limit) {
+ // No more events to load, disable loading button.
+ setLoadedAll(root);
}
+
+ // Render the events.
+ return render(root, calendarEvents).then(function(renderCount) {
+ if (renderCount < calendarEvents.length) {
+ // if the number of events that was rendered is less than
+ // the number we sent for rendering we can assume that there
+ // are no groups to add them in. Since the ordering of the
+ // events is guaranteed it means that any future requests will
+ // also yield events that can't be rendered, so let's not bother
+ // sending any more requests.
+ setLoadedAll(root);
+ }
+ return calendarEvents.length;
+ });
+ }).then(function(eventCount) {
+ return updateContentVisibility(root, eventCount);
}).fail(
Notification.exception
).always(function() {
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Javascript used to save the user's tab preference.
+ *
+ * @package block_myoverview
+ * @copyright 2017 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery', 'core/ajax', 'core/custom_interaction_events',
+ 'core/notification'], function($, Ajax, CustomEvents, Notification) {
+
+ /**
+ * Registers an event that saves the user's tab preference when switching between them.
+ *
+ * @param {object} root The container element
+ */
+ var registerEventListeners = function(root) {
+ CustomEvents.define(root, [CustomEvents.events.activate]);
+ root.on(CustomEvents.events.activate, "[data-toggle='tab']", function(e) {
+ var tabname = $(e.currentTarget).data('tabname');
+ // Bootstrap does not change the URL when using BS tabs, so need to do this here.
+ // Also check to make sure the browser supports the history API.
+ if (typeof window.history.pushState === "function") {
+ window.history.pushState(null, null, '?myoverviewtab=' + tabname);
+ }
+ var request = {
+ methodname: 'core_user_update_user_preferences',
+ args: {
+ preferences: [
+ {
+ type: 'block_myoverview_last_tab',
+ value: tabname
+ }
+ ]
+ }
+ };
+
+ Ajax.call([request])[0]
+ .fail(Notification.exception);
+ });
+ };
+
+ return {
+ registerEventListeners: registerEventListeners
+ };
+});
return $this->content;
}
- $renderable = new \block_myoverview\output\main();
+ // Check if the tab to select wasn't passed in the URL, if so see if the user has any preference.
+ if (!$tab = optional_param('myoverviewtab', null, PARAM_ALPHA)) {
+ // Check if the user has no preference, if so get the site setting.
+ if (!$tab = get_user_preferences('block_myoverview_last_tab')) {
+ $config = get_config('block_myoverview');
+ $tab = $config->defaulttab;
+ }
+ }
+
+ $renderable = new \block_myoverview\output\main($tab);
$renderer = $this->page->get_renderer('block_myoverview');
$this->content = new stdClass();
public function applicable_formats() {
return array('my' => true);
}
+
+ /**
+ * This block does contain a configuration settings.
+ *
+ * @return boolean
+ */
+ public function has_config() {
+ return true;
+ }
}
* @return array
*/
public function export_for_template(renderer_base $output) {
- $today = time();
-
// Build courses view data structure.
$coursesview = [
'hascourses' => !empty($this->courses)
// How many courses we have per status?
$coursesbystatus = ['past' => 0, 'inprogress' => 0, 'future' => 0];
foreach ($this->courses as $course) {
- $startdate = $course->startdate;
- $enddate = $course->enddate;
$courseid = $course->id;
$context = \context_course::instance($courseid);
$exporter = new course_summary_exporter($course, [
// Convert summary to plain text.
$exportedcourse->summary = content_to_text($exportedcourse->summary, $exportedcourse->summaryformat);
+ $courseprogress = null;
+
+ $classified = course_classify_for_timeline($course);
+
if (isset($this->coursesprogress[$courseid])) {
- $coursecompleted = $this->coursesprogress[$courseid]['completed'];
$courseprogress = $this->coursesprogress[$courseid]['progress'];
$exportedcourse->hasprogress = !is_null($courseprogress);
$exportedcourse->progress = $courseprogress;
}
- if ((isset($coursecompleted) && $coursecompleted) || (!empty($enddate) && $enddate < $today)) {
+ if ($classified == COURSE_TIMELINE_PAST) {
// Courses that have already ended.
$pastpages = floor($coursesbystatus['past'] / $this::COURSES_PER_PAGE);
$coursesview['past']['pages'][$pastpages]['page'] = $pastpages + 1;
$coursesview['past']['haspages'] = true;
$coursesbystatus['past']++;
- } else if ($startdate > $today) {
+ } else if ($classified == COURSE_TIMELINE_FUTURE) {
// Courses that have not started yet.
$futurepages = floor($coursesbystatus['future'] / $this::COURSES_PER_PAGE);
use templatable;
use core_completion\progress;
+require_once($CFG->dirroot . '/blocks/myoverview/lib.php');
require_once($CFG->libdir . '/completionlib.php');
/**
*/
class main implements renderable, templatable {
+ /**
+ * @var string The tab to display.
+ */
+ public $tab;
+
+ /**
+ * Constructor.
+ *
+ * @param string $tab The tab to display.
+ */
+ public function __construct($tab) {
+ $this->tab = $tab;
+ }
+
/**
* Export this data so it can be used as the context for a mustache template.
*
$nocoursesurl = $output->image_url('courses', 'block_myoverview')->out();
$noeventsurl = $output->image_url('activities', 'block_myoverview')->out();
+ // Now, set the tab we are going to be viewing.
+ $viewingtimeline = false;
+ $viewingcourses = false;
+ if ($this->tab == BLOCK_MYOVERVIEW_TIMELINE_VIEW) {
+ $viewingtimeline = true;
+ } else {
+ $viewingcourses = true;
+ }
+
return [
'midnight' => usergetmidnight(time()),
'coursesview' => $coursesview->export_for_template($output),
'urls' => [
'nocourses' => $nocoursesurl,
'noevents' => $noeventsurl
- ]
+ ],
+ 'viewingtimeline' => $viewingtimeline,
+ 'viewingcourses' => $viewingcourses
];
}
}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['defaulttab'] = 'Default tab';
+$string['defaulttab_desc'] = 'This is the default tab that will be shown to a user.';
$string['future'] = 'Future';
$string['inprogress'] = 'In progress';
$string['morecourses'] = 'More courses';
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contains functions called by core.
+ *
+ * @package block_myoverview
+ * @copyright 2017 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The timeline view.
+ */
+define('BLOCK_MYOVERVIEW_TIMELINE_VIEW', 'timeline');
+
+/**
+ * The courses view.
+ */
+define('BLOCK_MYOVERVIEW_COURSES_VIEW', 'courses');
+
+/**
+ * Returns the name of the user preferences as well as the details this plugin uses.
+ *
+ * @return array
+ */
+function block_myoverview_user_preferences() {
+ $preferences = array();
+ $preferences['block_myoverview_last_tab'] = array(
+ 'type' => PARAM_ALPHA,
+ 'null' => NULL_NOT_ALLOWED,
+ 'default' => BLOCK_MYOVERVIEW_TIMELINE_VIEW,
+ 'choices' => array(BLOCK_MYOVERVIEW_TIMELINE_VIEW, BLOCK_MYOVERVIEW_COURSES_VIEW)
+ );
+
+ return $preferences;
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Settings for the overview block.
+ *
+ * @package block_myoverview
+ * @copyright 2017 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->dirroot . '/blocks/myoverview/lib.php');
+
+if ($ADMIN->fulltree) {
+
+ $options = [
+ BLOCK_MYOVERVIEW_TIMELINE_VIEW => get_string('timeline', 'block_myoverview'),
+ BLOCK_MYOVERVIEW_COURSES_VIEW => get_string('courses')
+ ];
+
+ $settings->add(new admin_setting_configselect('block_myoverview/defaulttab',
+ get_string('defaulttab', 'block_myoverview'),
+ get_string('defaulttab_desc', 'block_myoverview'), 'timeline', $options));
+}
}}
<div id="block-myoverview-{{uniqid}}" class="block-myoverview" data-region="myoverview">
- <ul class="nav nav-tabs" role="tablist">
+ <ul id="block-myoverview-view-choices-{{uniqid}}" class="nav nav-tabs" role="tablist">
<li class="nav-item">
- <a class="nav-link active" href="#myoverview_timeline_view" role="tab" data-toggle="tab">
+ <a class="nav-link {{#viewingtimeline}}active{{/viewingtimeline}}" href="#myoverview_timeline_view" role="tab" data-toggle="tab" data-tabname="timeline">
{{#str}} timeline, block_myoverview {{/str}}
</a>
</li>
<li class="nav-item">
- <a class="nav-link" href="#myoverview_courses_view" role="tab" data-toggle="tab">
+ <a class="nav-link {{#viewingcourses}}active{{/viewingcourses}}" href="#myoverview_courses_view" role="tab" data-toggle="tab" data-tabname="courses">
{{#str}} courses {{/str}}
</a>
</li>
</ul>
<div class="tab-content content-centred">
- <div role="tabpanel" class="tab-pane fade in active" id="myoverview_timeline_view">
+ <div role="tabpanel" class="tab-pane fade {{#viewingtimeline}}in active{{/viewingtimeline}}" id="myoverview_timeline_view">
{{> block_myoverview/timeline-view }}
</div>
- <div role="tabpanel" class="tab-pane fade" id="myoverview_courses_view">
+ <div role="tabpanel" class="tab-pane fade {{#viewingcourses}}in active{{/viewingcourses}}" id="myoverview_courses_view">
{{#coursesview}}
{{> block_myoverview/courses-view }}
{{/coursesview}}
</div>
</div>
</div>
+{{#js}}
+require(['jquery', 'block_myoverview/tab_preferences'], function($, TabPreferences) {
+ var root = $('#block-myoverview-view-choices-{{uniqid}}');
+ TabPreferences.registerEventListeners(root);
+});
+{{/js}}
And I am on "Course 1" course homepage
And I follow "Test choice 1"
And I follow "Dashboard" in the user menu
+ And I click on "Timeline" "link" in the "Course overview" "block"
And I click on "Sort by courses" "link" in the "Course overview" "block"
And I should see "100%" in the "Course overview" "block"
And I click on "Courses" "link" in the "Course overview" "block"
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017051500; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2017051502; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2017050500; // Requires this Moodle version.
$plugin->component = 'block_myoverview'; // Full name of the plugin (used for diagnostics).
* @copyright 2015 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define(['jquery'], function($) {
+define(['jquery', 'core/templates', 'core/notification', 'core/url'], function($, Templates, Notification, Url) {
// Mappings for the different types of nodes coming from the navigation.
// Copied from lib/navigationlib.php navigation_node constants.
p.addClass('branch');
}
- if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) {
- li.addClass('item_with_icon');
- p.addClass('hasicon');
-
- icon = $('<img/>');
- icon.attr('alt', node.icon.alt);
- icon.attr('title', node.icon.title);
- icon.attr('src', M.util.image_url(node.icon.pix, node.icon.component));
- $.each(node.icon.classes, function(index, className) {
- icon.addClass(className);
- });
- }
-
+ var eleToAddIcon = null;
if (node.link) {
var link = $('<a title="' + node.title + '" href="' + node.link + '"></a>');
- if (icon) {
- link.append(icon);
- link.append('<span class="item-content-wrap">' + node.name + '</span>');
- } else {
- link.append(node.name);
- }
+ eleToAddIcon = link;
+ link.append('<span class="item-content-wrap">' + node.name + '</span>');
if (node.hidden) {
link.addClass('dimmed');
} else {
var span = $('<span></span>');
- if (icon) {
- span.append(icon);
- span.append('<span class="item-content-wrap">' + node.name + '</span>');
- } else {
- span.append(node.name);
- }
+ eleToAddIcon = span;
+ span.append('<span class="item-content-wrap">' + node.name + '</span>');
if (node.hidden) {
span.addClass('dimmed');
p.append(span);
}
+ if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) {
+ li.addClass('item_with_icon');
+ p.addClass('hasicon');
+
+ if (node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE) {
+ icon = $('<img/>');
+ icon.attr('alt', node.icon.alt);
+ icon.attr('title', node.icon.title);
+ icon.attr('src', Url.imageUrl(node.icon.pix, node.icon.component));
+ $.each(node.icon.classes, function(index, className) {
+ icon.addClass(className);
+ });
+ eleToAddIcon.prepend(icon);
+ } else {
+ if (node.icon.component == 'moodle') {
+ node.icon.component = 'core';
+ }
+ Templates.renderPix(node.icon.pix, node.icon.component, node.icon.title).then(function(html) {
+ // Prepend.
+ eleToAddIcon.prepend(html);
+ return;
+ }).catch(Notification.exception);
+ }
+ }
+
li.append(p);
ul.append(li);
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
display: block;
}
-.block_navigation .block_tree [aria-hidden="true"] {
+.block_navigation .block_tree [aria-hidden="true"]:not(.icon) {
display: none;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
\ No newline at end of file
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
+ // Automatically generated Moodle v3.3.0 release upgrade line.
+ // Put any upgrade step following this.
+
return true;
}
display: block;
}
-.block_settings .block_tree [aria-hidden="true"] {
+.block_settings .block_tree [aria-hidden="true"]:not(.icon) {
display: none;
}
$string['tags:addinstance'] = 'Add a new tags block';
$string['tags:myaddinstance'] = 'Add a new tags block to Dashboard';
-// Deprecated since 3.0.
-
-$string['add'] = 'Add';
-$string['alltags'] = 'All tags:';
-$string['arrowtitle'] = 'Click here to enter the suggested text (grey letters).';
-$string['coursetags'] = 'Course tags:';
-$string['edit'] = 'edit...';
-$string['editdeletemytag'] = 'Delete tag from this course:';
-$string['editmytags'] = 'My tags - shortcuts to all your tagged courses.';
-$string['editmytagsfor'] = 'Edit my tags for {$a}';
-$string['editnopersonaltags'] = 'No personal tags have been created yet.';
-$string['edittags'] = 'Edit my tags...';
-$string['edittagthisunit'] = 'Add tag to this course:';
-$string['editthiscoursetags'] = 'My tags for this course are {$a}';
-$string['edittitle'] = 'My tags';
-$string['entries'] = 'entries';
-$string['entry'] = 'entry';
-$string['jserror1'] = 'Tags must have between one and 50 characters. Please adjust your tag.';
-$string['jserror2'] = 'Tags cannot contain these special characters - backslash, less than (<) or greater than (>). Please adjust your tag.';
-$string['login'] = 'log in';
-$string['more'] = 'more...';
-$string['moreorder'] = 'Order:';
-$string['moreorderalpha'] = 'Alphabetical';
-$string['moreorderdate'] = 'Date created';
-$string['moreorderpop'] = 'Popularity';
-$string['moreshow'] = 'Show:';
-$string['moreshowalltags'] = 'All tags';
-$string['moreshowcommtags'] = 'Non-official tags';
-$string['moreshowcoursetags'] = 'Tags for \'{$a}\'';
-$string['moreshowmytags'] = 'My tags';
-$string['moreshowofficialtags'] = 'Official tags';
-$string['moretags'] = 'Show and filter more tags';
-$string['moretitle'] = 'More tags';
-$string['morewelcome'] = 'Welcome to the \'More tags\' tag cloud.
-Tags are user created links to things. Tags allow you to categorise and link things
-like your favourite courses, your blogs or your profile with your own words.
-Different groups of tags may be displayed with the links on the \'Show:\' line,
-and may be reordered with the links on the \'Order:\' line.';
-$string['mycoursetags'] = 'My course tags:';
-$string['mytags'] = 'My tags:';
-$string['notagsyet'] = 'No tags yet';
-$string['please'] = 'Please';
-$string['select'] = 'Select...';
-$string['showcoursetags'] = 'Show course tags';
-$string['showcoursetagsdef'] = 'Display the course tagging features in the tags block, allowing students to tag courses.';
-$string['suggestedtagthisunit'] = 'Suggested tag to this course:';
-$string['tags'] = 'tags';
-$string['tagthisunit'] = 'Tag this course:';
-$string['tagunits'] = 'to tag your favourite courses';
+++ /dev/null
-add,block_tags
-alltags,block_tags
-arrowtitle,block_tags
-coursetags,block_tags
-edit,block_tags
-editdeletemytag,block_tags
-editmytags,block_tags
-editmytagsfor,block_tags
-editnopersonaltags,block_tags
-edittags,block_tags
-edittagthisunit,block_tags
-editthiscoursetags,block_tags
-edittitle,block_tags
-entries,block_tags
-entry,block_tags
-jserror1,block_tags
-jserror2,block_tags
-login,block_tags
-more,block_tags
-moreorder,block_tags
-moreorderalpha,block_tags
-moreorderdate,block_tags
-moreorderpop,block_tags
-moreshow,block_tags
-moreshowalltags,block_tags
-moreshowcommtags,block_tags
-moreshowcoursetags,block_tags
-moreshowmytags,block_tags
-moreshowofficialtags,block_tags
-moretags,block_tags
-moretitle,block_tags
-morewelcome,block_tags
-mytags,block_tags
-notagsyet,block_tags
-please,block_tags
-select,block_tags
-showcoursetags,block_tags
-showcoursetagsdef,block_tags
-suggestedtagthisunit,block_tags
-tags,block_tags
-tagthisunit,block_tags
-tagunits,block_tags
$result = $data;
}
}
- if ($result) {
+ if ($result !== false) {
if ($this->perfdebug) {
cache_helper::record_cache_hit('** static acceleration **', $this->definition);
}
*/
class cache_request extends cache {
// This comment appeases code pre-checker ;) !
-}
\ No newline at end of file
+}
$startstats[$requestid]['stores']['cachestore_static']['sets']);
}
+ public function test_static_cache() {
+ global $CFG;
+ $this->resetAfterTest(true);
+ $CFG->perfdebug = 15;
+
+ // Create cache store with static acceleration.
+ $instance = cache_config_testing::instance();
+ $applicationid = 'phpunit/applicationperf';
+ $instance->phpunit_add_definition($applicationid, array(
+ 'mode' => cache_store::MODE_APPLICATION,
+ 'component' => 'phpunit',
+ 'area' => 'applicationperf',
+ 'simplekeys' => true,
+ 'staticacceleration' => true,
+ 'staticaccelerationsize' => 3
+ ));
+
+ $application = cache::make('phpunit', 'applicationperf');
+
+ // Check that stores register sets.
+ $this->assertTrue($application->set('setMe1', 1));
+ $this->assertTrue($application->set('setMe2', 0));
+ $this->assertTrue($application->set('setMe3', array()));
+ $this->assertTrue($application->get('setMe1') !== false);
+ $this->assertTrue($application->get('setMe2') !== false);
+ $this->assertTrue($application->get('setMe3') !== false);
+
+ // Check that the static acceleration worked, even on empty arrays and the number 0.
+ $endstats = cache_helper::get_stats();
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['** static acceleration **']['misses']);
+ $this->assertEquals(3, $endstats[$applicationid]['stores']['** static acceleration **']['hits']);
+ }
+
public function test_performance_debug_off() {
global $CFG;
$this->resetAfterTest(true);
*/
protected static $eventretrievalstrategy;
- /**
- * @var array A list of callbacks to use.
- */
- protected static $callbacks = array();
-
/**
* @var \stdClass[] An array of cached courses to use with the event factory.
*/
*/
private static function init() {
if (empty(self::$eventfactory)) {
- // When testing the container's components, we need to make sure
- // the callback implementations in modules are not executed, since
- // we cannot control their output from PHPUnit. To do this we have
- // a set of 'testing' callbacks that the factory can use. This way
- // we know exactly how the factory behaves when being tested.
- $getcallback = function($which) {
- return self::$callbacks[PHPUNIT_TEST ? 'testing' : 'production'][$which];
- };
-
- self::initcallbacks();
self::$actionfactory = new action_factory();
self::$eventmapper = new event_mapper(
// The event mapper we return from here needs to know how to
);
self::$eventfactory = new event_factory(
- $getcallback('action'),
- $getcallback('visibility'),
+ [self::class, 'apply_component_provide_event_action'],
+ [self::class, 'apply_component_is_event_visible'],
function ($dbrow) {
// At present we only have a bail-out check for events in course modules.
if (empty($dbrow->modulename)) {
}
}
+ /**
+ * Reset all static caches, called between tests.
+ */
+ public static function reset_caches() {
+ self::$eventfactory = null;
+ self::$eventmapper = null;
+ self::$eventvault = null;
+ self::$actionfactory = null;
+ self::$eventretrievalstrategy = null;
+ self::$coursecache = [];
+ self::$modulecache = [];
+ }
+
/**
* Gets the event factory.
*
}
/**
- * Initialises the callbacks.
+ * Calls callback 'core_calendar_provide_event_action' from the component responsible for the event
*
- * There are two sets here, one is used during PHPUnit runs.
- * See the comment at the start of the init method for more
- * detail.
+ * If no callback is present or callback returns null, there is no action on the event
+ * and it will not be displayed on the dashboard.
+ *
+ * @param event_interface $event
+ * @return action_event|event_interface
*/
- private static function initcallbacks() {
- self::$callbacks = array(
- 'testing' => array(
- // Always return an action event.
- 'action' => function (event_interface $event) {
- return new action_event(
- $event,
- new \core_calendar\local\event\value_objects\action(
- 'test',
- new \moodle_url('http://example.com'),
- 420,
- true
- ));
- },
- // Always be visible.
- 'visibility' => function (event_interface $event) {
- return true;
- }
- ),
- 'production' => array(
- // This function has type event_interface -> event_interface.
- // This is enforced by the event_factory.
- 'action' => function (event_interface $event) {
- // Callbacks will get supplied a "legacy" version
- // of the event class.
- $mapper = self::$eventmapper;
- $action = null;
- if ($event->get_course_module()) {
- // TODO MDL-58866 Only activity modules currently support this callback.
- // Any other event will not be displayed on the dashboard.
- $action = component_callback(
- 'mod_' . $event->get_course_module()->get('modname'),
- 'core_calendar_provide_event_action',
- [
- $mapper->from_event_to_legacy_event($event),
- self::$actionfactory
- ]
- );
- }
+ public static function apply_component_provide_event_action(event_interface $event) {
+ // Callbacks will get supplied a "legacy" version
+ // of the event class.
+ $mapper = self::$eventmapper;
+ $action = null;
+ if ($event->get_course_module()) {
+ // TODO MDL-58866 Only activity modules currently support this callback.
+ // Any other event will not be displayed on the dashboard.
+ $action = component_callback(
+ 'mod_' . $event->get_course_module()->get('modname'),
+ 'core_calendar_provide_event_action',
+ [
+ $mapper->from_event_to_legacy_event($event),
+ self::$actionfactory
+ ]
+ );
+ }
- // If we get an action back, return an action event, otherwise
- // continue piping through the original event.
- //
- // If a module does not implement the callback, component_callback
- // returns null.
- return $action ? new action_event($event, $action) : $event;
- },
- // This function has type event_interface -> bool.
- // This is enforced by the event_factory.
- 'visibility' => function (event_interface $event) {
- $mapper = self::$eventmapper;
- $eventvisible = null;
- if ($event->get_course_module()) {
- // TODO MDL-58866 Only activity modules currently support this callback.
- $eventvisible = component_callback(
- 'mod_' . $event->get_course_module()->get('modname'),
- 'core_calendar_is_event_visible',
- [
- $mapper->from_event_to_legacy_event($event)
- ]
- );
- }
+ // If we get an action back, return an action event, otherwise
+ // continue piping through the original event.
+ //
+ // If a module does not implement the callback, component_callback
+ // returns null.
+ return $action ? new action_event($event, $action) : $event;
+ }
- // Do not display the event if there is nothing to action.
- if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
- return false;
- }
+ /**
+ * Calls callback 'core_calendar_is_event_visible' from the component responsible for the event
+ *
+ * The visibility callback is optional, if not present it is assumed as visible.
+ * If it is an actionable event but the get_item_count() returns 0 the visibility
+ * is set to false.
+ *
+ * @param event_interface $event
+ * @return bool
+ */
+ public static function apply_component_is_event_visible(event_interface $event) {
+ $mapper = self::$eventmapper;
+ $eventvisible = null;
+ if ($event->get_course_module()) {
+ // TODO MDL-58866 Only activity modules currently support this callback.
+ $eventvisible = component_callback(
+ 'mod_' . $event->get_course_module()->get('modname'),
+ 'core_calendar_is_event_visible',
+ [
+ $mapper->from_event_to_legacy_event($event)
+ ]
+ );
+ }
- // Module does not implement the callback, event should be visible.
- if (is_null($eventvisible)) {
- return true;
- }
+ // Do not display the event if there is nothing to action.
+ if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
+ return false;
+ }
+
+ // Module does not implement the callback, event should be visible.
+ if (is_null($eventvisible)) {
+ return true;
+ }
- return $eventvisible ? true : false;
- }
- ),
- );
+ return $eventvisible ? true : false;
}
}
$coursesfilter,
$where,
$params,
- "e.timesort ASC, e.id ASC",
+ "COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
$offset,
$limitnum,
$ignorehidden
if (!empty($eventtypes->groups) && is_array($eventtypes->groups)) {
$groupoptions = array();
foreach ($eventtypes->groups as $group) {
- $groupoptions[$group->id] = $group->name;
+ $groupoptions[$group->id] = format_string($group->name, true,
+ array('context' => context_course::instance($group->courseid)));
}
$mform->addElement('select', 'groupid', get_string('typegroup', 'calendar'), $groupoptions);
$mform->disabledIf('groupid', 'eventtype', 'noteq', 'group');
$ical = new iCalendar;
$ical->add_property('method', 'PUBLISH');
+$ical->add_property('prodid', '-//Moodle Pty Ltd//NONSGML Moodle Version ' . $CFG->version . '//EN');
foreach($events as $event) {
if (!empty($event->modulename)) {
$instances = get_fast_modinfo($event->courseid, $userid)->get_instances_of($event->modulename);
$hostaddress = str_replace('http://', '', $CFG->wwwroot);
$hostaddress = str_replace('https://', '', $hostaddress);
- $ev = new iCalendar_event;
+ $me = new calendar_event($event); // To use moodle calendar event services.
+ $ev = new iCalendar_event; // To export in ical format.
$ev->add_property('uid', $event->id.'@'.$hostaddress);
- $ev->add_property('summary', $event->name);
- $ev->add_property('description', clean_param($event->description, PARAM_NOTAGS));
+
+ // Set iCal event summary from event name.
+ $ev->add_property('summary', format_string($event->name, true, ['context' => $me->context]));
+
+ // Format the description text.
+ $description = format_text($me->description, $me->format, ['context' => $me->context]);
+ // Then convert it to plain text, since it's the only format allowed for the event description property.
+ // We use html_to_text in order to convert <br> and <p> tags to new line characters for descriptions in HTML format.
+ $description = html_to_text($description, 0);
+ $ev->add_property('description', $description);
+
$ev->add_property('class', 'PUBLIC'); // PUBLIC / PRIVATE / CONFIDENTIAL
$ev->add_property('last-modified', Bennu::timestamp_to_datetime($event->timemodified));
$ev->add_property('dtstamp', Bennu::timestamp_to_datetime()); // now
$class .= ' duration_finish';
}
$data = array(
- 'url' => $dayhref,
+ 'url' => $dayhref->out(false),
'day' => $day,
'content' => $popupdata['data-core_calendar-popupcontent'],
'title' => $popupdata['data-core_calendar-title']
*/
public function test_get_calendar_events_override() {
$user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$anotheruser = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$moduleinstance = $generator->create_instance(['course' => $course->id]);
- $this->getDataGenerator()->enrol_user($user->id, $course->id);
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
$this->resetAfterTest(true);
$this->setAdminUser();
];
$now = time();
+ // Create two events - one for everybody in the course and one only for the first student.
$event1 = $this->create_calendar_event('Base event', 0, 'due', 0, $now + DAYSECS, $params + ['courseid' => $course->id]);
$event2 = $this->create_calendar_event('User event', $user->id, 'due', 0, $now + 2*DAYSECS, $params + ['courseid' => 0]);
- // Retrieve course events for teacher - only one "Base event" is returned.
- $this->setUser($teacher);
+ // Retrieve course events for the second student - only one "Base event" is returned.
+ $this->setUser($user2);
$paramevents = array('courseids' => array($course->id));
$options = array ('siteevents' => true, 'userevents' => true);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$this->assertEquals(0, count($events['warnings']));
$this->assertEquals('Base event', $events['events'][0]['name']);
- // Retrieve events for user - both events are returned.
+ // Retrieve events for the first student - both events are returned.
$this->setUser($user);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
*/
public function test_every_300_days_forever() {
global $DB;
+
+ // Change the start date for forever events to 9am of the current date.
+ $this->change_event_startdate(date('Ymd\T090000'));
$startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
$interval = new DateInterval('P300D');
$mang = new rrule_manager($rrule);
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC', 0, 100);
$expecteddate = clone($startdatetime);
$first = true;
$expecteddate->add($interval);
}
}
+ }
+
+ /**
+ * Test recurrence rules for weekly frequency for RRULE with BYDAY rule set, recurring forever.
+ */
+ public function test_weekly_byday_forever() {
+ global $DB;
- // Forever event. This should generate events over time() + 10 year period, every 50th Monday.
+ // Set the next Monday as the starting date of this event.
+ $startdate = new DateTime('next Monday');
+ // Change the start date of the parent event.
+ $startdate = $this->change_event_startdate($startdate->format('Ymd\T090000'));
+
+ // Forever event. This should generate events over time() + 10 year period, every 50 weeks.
$rrule = 'FREQ=WEEKLY;BYDAY=MO;INTERVAL=50';
$mang = new rrule_manager($rrule);
$untildate->add(new DateInterval('P10Y'));
$until = $untildate->getTimestamp();
- $interval = new DateInterval('P50W');
$records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
- // First instance of this set of recurring events: Monday, 17-08-1998.
+ $interval = new DateInterval('P50W');
+
+ // First instance of this set of recurring events.
$expecteddate = clone($startdate);
- $expecteddate->modify('1998-08-17');
- $expecteddate->add($offsetinterval);
+
+ // Iterate over each record and increment the expected date accordingly.
foreach ($records as $record) {
$eventdateexpected = $expecteddate->format('Y-m-d H:i:s');
$eventdateactual = date('Y-m-d H:i:s', $record->timestart);
public function test_monthly_events_with_bymonthday_forever() {
global $DB;
+ // Change the start date for forever events to 9am of the 2nd day of the current month and year.
+ $this->change_event_startdate(date('Ym02\T090000'));
$startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
$startdate = new DateTime(date('Y-m-d', $this->event->timestart));
$offsetinterval = $startdatetime->diff($startdate, true);
$interval = new DateInterval('P12M');
- // Forever event. This should generate events over 10 year period, on 2nd day of every 12th month.
+ // Forever event. This should generate events over a 10-year period, on 2nd day of the month, every 12 months.
$rrule = "FREQ=MONTHLY;INTERVAL=12;BYMONTHDAY=2";
$mang = new rrule_manager($rrule);
- $until = time() + (YEARSECS * $mang::TIME_UNLIMITED_YEARS);
+ $untildate = new DateTime();
+ $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
+ $until = $untildate->getTimestamp();
$mang->parse_rrule();
$mang->create_events($this->event);
public function test_monthly_events_with_byday_forever() {
global $DB;
+ // Change the start date for forever events to 9am of the 2nd day of the current month and year.
+ $this->change_event_startdate(date('Ym02\T090000'));
+
$startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
$startdate = new DateTime(date('Y-m-d', $this->event->timestart));
$offsetinterval = $startdatetime->diff($startdate, true);
$interval = new DateInterval('P12M');
- // Forever event. This should generate events over 10 year period, on 2nd day of every 12th month.
+ // Forever event. This should generate events over a 10 year period, on 1st Monday of the month every 12 months.
$rrule = "FREQ=MONTHLY;INTERVAL=12;BYDAY=1MO";
$mang = new rrule_manager($rrule);
- $until = time() + (YEARSECS * $mang::TIME_UNLIMITED_YEARS);
+ $untildate = new DateTime();
+ $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
+ $until = $untildate->getTimestamp();
$mang->parse_rrule();
$mang->create_events($this->event);
$records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
-
- $expecteddate = new DateTime('first Monday of September 1998');
+ $expecteddate = new DateTime('first Monday of this month');
+ // Move to the next interval's first Monday if the calculated start date is after this month's first Monday.
+ if ($expecteddate->getTimestamp() < $startdatetime->getTimestamp()) {
+ $expecteddate->add($interval);
+ $expecteddate->modify('first Monday of this month');
+ }
foreach ($records as $record) {
$expecteddate->add($offsetinterval);
$this->assertLessThanOrEqual($until, $record->timestart);
// Create a yearly event, until the time limit is hit.
$until = strtotime('+20 day +10 years', $this->event->timestart);
$until = date('Ymd\THis\Z', $until);
- $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until"; // Forever event.
+ $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until";
$mang = new rrule_manager($rrule);
$mang->parse_rrule();
$mang->create_events($this->event);
$this->assertTrue($result);
}
- $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2"; // Forever event.
- $mang = new rrule_manager($rrule);
- $until = time() + (YEARSECS * $mang::TIME_UNLIMITED_YEARS);
- $mang->parse_rrule();
- $mang->create_events($this->event);
- for ($i = 0, $time = $this->event->timestart; $time < $until; $i++, $yoffset = $i * 2,
- $time = strtotime("+$yoffset years", $this->event->timestart)) {
- $result = $DB->record_exists('event', array('repeatid' => $this->event->id,
- 'timestart' => ($time)));
- $this->assertTrue($result);
- }
-
$rrule = "FREQ=YEARLY;COUNT=3;BYMONTH=9;BYDAY=1MO"; // This should generate 3 events in total.
$mang = new rrule_manager($rrule);
$mang->parse_rrule();
}
}
+ /**
+ * Test for rrule with FREQ=YEARLY and INTERVAL=2 with BYMONTH rule set, recurring forever.
+ */
+ public function test_yearly_september_every_two_years_forever() {
+ global $DB;
+
+ // Change the start date for forever events to 9am on the 2nd day of September of the current year.
+ $this->change_event_startdate(date('Y0902\T090000'));
+
+ $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2"; // Forever event.
+ $mang = new rrule_manager($rrule);
+ $untildate = new DateTime();
+ $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
+ $untiltimestamp = $untildate->getTimestamp();
+ $mang->parse_rrule();
+ $mang->create_events($this->event);
+
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+
+ $interval = new DateInterval('P2Y');
+ $expecteddate = new DateTime(date('Y0902\T090000'));
+ foreach ($records as $record) {
+ $this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
+ $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart));
+
+ // Go to the next expected date.
+ $expecteddate->add($interval);
+ }
+ }
+
/**
* Test for rrule with FREQ=YEARLY with BYMONTH and BYDAY rules set, recurring forever.
*/
public function test_yearly_bymonth_byday_forever() {
global $DB;
+ // Change the start date for forever events to the first day of September of the current year at 9am.
+ $this->change_event_startdate(date('Y0901\T090000'));
+
// Every 2 years on the first Monday of September.
$rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;BYDAY=1MO";
$mang = new rrule_manager($rrule);
$offsetinterval = $startdatetime->diff($startdate, true);
$interval = new DateInterval('P2Y');
- // First occurrence of this set of events is on the first Monday of September 1999.
+ // First occurrence of this set of events is on the first Monday of September.
$expecteddate = clone($startdatetime);
- $expecteddate->modify('first Monday of September 1999');
+ $expecteddate->modify('first Monday of September');
$expecteddate->add($offsetinterval);
foreach ($records as $record) {
$this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
public function test_yearly_forever() {
global $DB;
+ // Change the start date for forever events to 9am of the current date.
+ $this->change_event_startdate(date('Ymd\T090000'));
+
$startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
$interval = new DateInterval('P2Y');
/**
* Every other day - forever:
*
- * DTSTART;TZID=US-Eastern:19970902T090000
+ * DTSTART;TZID=US-Eastern:[Current date]T090000
* RRULE:FREQ=DAILY;INTERVAL=2
- * ==> (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;October 2,4,6...20,22,24
- * (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29;Dec 1,3,...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;October 2,4,6...20,22,24
+ * (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29;Dec 1,3,...
*/
public function test_every_other_day_forever() {
global $DB;
+ // Change the start date for forever events to 9am of the current date in US/Eastern time.
+ $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern');
+
$startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
$interval = new DateInterval('P2D');
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
/**
* Every other week - forever:
*
- * DTSTART;TZID=US-Eastern:19970902T090000
+ * DTSTART;TZID=US-Eastern:[Current date]T090000
* RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU
- * ==> (1997 9:00 AM EDT)September 2,16,30;October 14
- * (1997 9:00 AM EST)October 28;November 11,25;December 9,23
- * (1998 9:00 AM EST)January 6,20;February
- * ...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EDT)September 2,16,30;October 14
+ * (1997 9:00 AM EST)October 28;November 11,25;December 9,23
+ * (1998 9:00 AM EST)January 6,20;February
+ * ...
*/
public function test_every_other_week_forever() {
global $DB;
+ // Change the start date for forever events to 9am of the current date in US/Eastern time.
+ $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern');
+
$interval = new DateInterval('P2W');
$rrule = 'FREQ=WEEKLY;INTERVAL=2;WKST=SU';
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
/**
* Monthly on the third to the last day of the month, forever:
*
- * DTSTART;TZID=US-Eastern:19970928T090000
+ * DTSTART;TZID=US-Eastern:[Current year]0928T090000
* RRULE:FREQ=MONTHLY;BYMONTHDAY=-3
- * ==> (1997 9:00 AM EDT)September 28
- * (1997 9:00 AM EST)October 29;November 28;December 29
- * (1998 9:00 AM EST)January 29;February 26
- * ...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EDT)September 28
+ * (1997 9:00 AM EST)October 29;November 28;December 29
+ * (1998 9:00 AM EST)January 29;February 26
+ * ...
*/
public function test_third_to_the_last_day_of_the_month_forever() {
global $DB;
- // Change our event's date to 05-09-1997, based on the example from the RFC.
- $this->change_event_startdate('19970928T090000', 'US/Eastern');
+ // Change our event's date to 28 September of the current year, based on the example from the RFC.
+ $this->change_event_startdate(date('Y0928\T090000'), 'US/Eastern');
$rrule = 'FREQ=MONTHLY;BYMONTHDAY=-3';
$mang = new rrule_manager($rrule);
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
/**
* Every Tuesday, every other month:
*
- * DTSTART;TZID=US-Eastern:19970902T090000
+ * DTSTART;TZID=US-Eastern:[Next Tuesday]T090000
* RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU
- * ==> (1997 9:00 AM EDT)September 2,9,16,23,30
- * (1997 9:00 AM EST)November 4,11,18,25
- * (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31
- * ...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EDT)September 2,9,16,23,30
+ * (1997 9:00 AM EST)November 4,11,18,25
+ * (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31
+ * ...
*/
public function test_every_tuesday_every_other_month_forever() {
global $DB;
+ // Change the start date for forever events to 9am of the Tuesday on or before of the current date in US/Eastern time.
+ $nexttuesday = new DateTime('next Tuesday');
+ $this->change_event_startdate($nexttuesday->format('Ymd\T090000'), 'US/Eastern');
+
$rrule = 'FREQ=MONTHLY;INTERVAL=2;BYDAY=TU';
$mang = new rrule_manager($rrule);
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
/**
* Every 20th Monday of the year, forever:
*
- * DTSTART;TZID=US-Eastern:19970519T090000
+ * DTSTART;TZID=US-Eastern:[20th Monday of the current year]T090000
* RRULE:FREQ=YEARLY;BYDAY=20MO
- * ==> (1997 9:00 AM EDT)May 19
- * (1998 9:00 AM EDT)May 18
- * (1999 9:00 AM EDT)May 17
- * ...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EDT)May 19
+ * (1998 9:00 AM EDT)May 18
+ * (1999 9:00 AM EDT)May 17
+ * ...
*/
public function test_yearly_every_20th_monday_forever() {
global $DB;
- // Change our event's date to 19-05-1997, based on the example from the RFC.
- $startdatetime = $this->change_event_startdate('19970519T090000', 'US/Eastern');
+ // Change our event's date to the 20th Monday of the current year.
+ $twentiethmonday = new DateTime(date('Y-01-01'));
+ $twentiethmonday->modify('+20 Monday');
+ $startdatetime = $this->change_event_startdate($twentiethmonday->format('Ymd\T090000'), 'US/Eastern');
$startdate = new DateTime($startdatetime->format('Y-m-d'));
/**
* Monday of week number 20 (where the default start of the week is Monday), forever:
*
- * DTSTART;TZID=US-Eastern:19970512T090000
+ * DTSTART;TZID=US-Eastern:[1st day of the 20th week this year]T090000
* RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO
- * ==> (1997 9:00 AM EDT)May 12
- * (1998 9:00 AM EDT)May 11
- * (1999 9:00 AM EDT)May 17
- * ...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EDT)May 12
+ * (1998 9:00 AM EDT)May 11
+ * (1999 9:00 AM EDT)May 17
+ * ...
*/
public function test_yearly_byweekno_forever() {
global $DB;
- // Change our event's date to 12-05-1997, based on the example from the RFC.
- $startdatetime = $this->change_event_startdate('19970512T090000', 'US/Eastern');
+ // Change our event's date to the start of the 20th week of the current year.
+ $twentiethweek = new DateTime(date('Y-01-01'));
+ $twentiethweek->setISODate($twentiethweek->format('Y'), 20);
+ $startdatetime = $this->change_event_startdate($twentiethweek->format('Ymd\T090000'), 'US/Eastern');
$startdate = clone($startdatetime);
$startdate->modify($startdate->format('Y-m-d'));
/**
* Every Thursday in March, forever:
*
- * DTSTART;TZID=US-Eastern:19970313T090000
+ * DTSTART;TZID=US-Eastern:[First thursday of March of the current year]T090000
* RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH
- * ==> (1997 9:00 AM EST)March 13,20,27
- * (1998 9:00 AM EST)March 5,12,19,26
- * (1999 9:00 AM EST)March 4,11,18,25
- * ...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EST)March 13,20,27
+ * (1998 9:00 AM EST)March 5,12,19,26
+ * (1999 9:00 AM EST)March 4,11,18,25
+ * ...
*/
public function test_every_thursday_in_march_forever() {
global $DB;
- // Change our event's date to 12-05-1997, based on the example from the RFC.
- $startdatetime = $this->change_event_startdate('19970313T090000', 'US/Eastern');
+ // Change our event's date to the first Thursday of March of the current year at 9am US/Eastern time.
+ $firstthursdayofmarch = new DateTime('first Thursday of March');
+ $startdatetime = $this->change_event_startdate($firstthursdayofmarch->format('Ymd\T090000'), 'US/Eastern');
$interval = new DateInterval('P1Y');
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
$startdate = new DateTime($startdatetime->format('Y-m-d'));
$offsetinterval = $startdatetime->diff($startdate, true);
$expecteddate->setTimezone(new DateTimeZone(get_user_timezone()));
- $april1st = new DateTime('1997-04-01');
+ $april1st = new DateTime('April 1');
foreach ($records as $record) {
$this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
$this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart));
/**
* Every Thursday, but only during June, July, and August, forever:
*
- * DTSTART;TZID=US-Eastern:19970605T090000
+ * DTSTART;TZID=US-Eastern:[First Thursday of June of the current year]T090000
* RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8
- * ==> (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;August 7,14,21,28
- * (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;August 6,13,20,27
- * (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;August 5,12,19,26
- * ...
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;August 7,14,21,28
+ * (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;August 6,13,20,27
+ * (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;August 5,12,19,26
+ * ...
*/
public function test_every_thursday_june_july_august_forever() {
global $DB;
- // Change our event's date to 05-06-1997, based on the example from the RFC.
- $startdatetime = $this->change_event_startdate('19970605T090000', 'US/Eastern');
+ // Change our event's date to the first Thursday of June in the current year at 9am US/Eastern time.
+ $firstthursdayofjune = new DateTime('first Thursday of June');
+ $startdatetime = $this->change_event_startdate($firstthursdayofjune->format('Ymd\T090000'), 'US/Eastern');
$startdate = new DateTime($startdatetime->format('Y-m-d'));
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
$untiltimestamp = $untildate->getTimestamp();
$expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp()));
- $september1st = new DateTime('1997-09-01');
+ $september1st = new DateTime('September 1');
foreach ($records as $record) {
$this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
$this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart));
/**
* Every Friday the 13th, forever:
*
- * DTSTART;TZID=US-Eastern:19970902T090000
- * EXDATE;TZID=US-Eastern:19970902T090000
+ * DTSTART;TZID=US-Eastern:[Current date]T090000
* RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13
- * ==> (1998 9:00 AM EST)February 13;March 13;November 13
- * (1999 9:00 AM EDT)August 13
- * (2000 9:00 AM EDT)October 13
+ *
+ * Sample results (e.g. in the year 1997):
+ * (1998 9:00 AM EST)February 13;March 13;November 13
+ * (1999 9:00 AM EDT)August 13
+ * (2000 9:00 AM EDT)October 13
+ * ...
*/
public function test_friday_the_thirteenth_forever() {
global $DB;
+ // Change our event's date to the first Thursday of June in the current year at 9am US/Eastern time.
+ $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern');
+
$rrule = 'FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13';
$mang = new rrule_manager($rrule);
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
/**
* The first Saturday that follows the first Sunday of the month, forever:
*
- * DTSTART;TZID=US-Eastern:19970913T090000
+ * DTSTART;TZID=US-Eastern:[The Saturday after the month's first Sunday]T090000
* RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13
- * ==> (1997 9:00 AM EDT)September 13;October 11
- * (1997 9:00 AM EST)November 8;December 13
- * (1998 9:00 AM EST)January 10;February 7;March 7
- * (1998 9:00 AM EDT)April 11;May 9;June 13...
+ *
+ * Sample results (e.g. from 13 September 1997):
+ * (1997 9:00 AM EDT)September 13;October 11
+ * (1997 9:00 AM EST)November 8;December 13
+ * (1998 9:00 AM EST)January 10;February 7;March 7
+ * (1998 9:00 AM EDT)April 11;May 9;June 13...
*/
public function test_first_saturday_following_first_sunday_forever() {
global $DB;
- $startdatetime = $this->change_event_startdate('19970913T090000', 'US/Eastern');
+ // Change our event's date to the next Saturday after the first Sunday of the the current month at 9am US/Eastern time.
+ $firstsaturdayafterfirstsunday = new DateTime('first Sunday of this month');
+ $firstsaturdayafterfirstsunday->modify('next Saturday');
+ $startdatetime = $this->change_event_startdate($firstsaturdayafterfirstsunday->format('Ymd\T090000'), 'US/Eastern');
$startdate = new DateTime($startdatetime->format('Y-m-d'));
$offset = $startdatetime->diff($startdate, true);
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
$untildate = new DateTime();
$untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
/**
* Every four years, the first Tuesday after a Monday in November, forever (U.S. Presidential Election day):
*
- * DTSTART;TZID=US-Eastern:19961105T090000
+ * DTSTART;TZID=US-Eastern:[Most recent election date]T090000
* RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8
- * ==> (1996 9:00 AM EST)November 5
- * (2000 9:00 AM EST)November 7
- * (2004 9:00 AM EST)November 2
- * ...
+ *
+ * Sample results (e.g. from 05 November 1996):
+ * (1996 9:00 AM EST)November 5
+ * (2000 9:00 AM EST)November 7
+ * (2004 9:00 AM EST)November 2
+ * ...
*/
public function test_every_us_presidential_election_forever() {
global $DB;
- $startdatetime = $this->change_event_startdate('19961105T090000', 'US/Eastern');
+ // Calculate the most recent election date, starting from 1996 (e.g. today's 2017 so the most recent election was in 2016).
+ $currentyear = (int) date('Y');
+ $electionyear = 1996;
+ while ($electionyear + 4 < $currentyear) {
+ $electionyear += 4;
+ }
+ $electiondate = new DateTime('first Monday of November ' . $electionyear);
+ $electiondate->modify('+1 Tuesday');
+
+ // Use the most recent election date as the starting date of our recurring events.
+ $startdatetime = $this->change_event_startdate($electiondate->format('Ymd\T090000'), 'US/Eastern');
$startdate = new DateTime($startdatetime->format('Y-m-d'));
$offset = $startdatetime->diff($startdate, true);
* The 2nd to last weekday of the month:
*
* DTSTART;TZID=US-Eastern:19970929T090000
- * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2
+ * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2;COUNT=7
* ==> (1997 9:00 AM EDT)September 29
* (1997 9:00 AM EST)October 30;November 27;December 30
* (1998 9:00 AM EST)January 29;February 26;March 30
* ...
+ *
+ * (Original RFC example is set to recur forever. But we just want to verify that the results match the dates listed from
+ * the RFC example. So just limit the count to 7.)
*/
- public function test_second_to_the_last_weekday_of_the_month_forever() {
+ public function test_second_to_the_last_weekday_of_the_month() {
global $DB;
$this->change_event_startdate('19970929T090000', 'US/Eastern');
- $rrule = 'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2';
+ $rrule = 'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2;COUNT=7';
$mang = new rrule_manager($rrule);
$mang->parse_rrule();
$mang->create_events($this->event);
- $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+ // Get the first 7 samples. This should be enough to verify that we have generated the recurring events correctly.
+ $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 7);
$expecteddates = [
(new DateTime('1997-09-29 09:00:00 EDT'))->getTimestamp(),
$this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
// Confirm that the first 7 records correspond to the expected dates listed above.
- if ($i < 7) {
- $this->assertEquals($expecteddates[$i], $record->timestart);
- $i++;
- }
+ $this->assertEquals($expecteddates[$i], $record->timestart);
+ $i++;
}
}
/**
* Change the event's timestart (DTSTART) based on the test's needs.
*
- * @param string $datestr The date string. In YYYYmmddThhiiss format. e.g. 19990902T090000.
- * @param string $timezonestr A valid timezone string. e.g. 'US/Eastern'.
+ * @param string $datestr The date string. In 'Ymd\This' format. e.g. 19990902T090000.
+ * @param null|string $timezonestr A valid timezone string. e.g. 'US/Eastern'.
+ * If not provided, the default timezone will be used.
* @return bool|DateTime
*/
- protected function change_event_startdate($datestr, $timezonestr) {
- $timezone = new DateTimeZone($timezonestr);
- $newdatetime = DateTime::createFromFormat('Ymd\THis', $datestr, $timezone);
+ protected function change_event_startdate($datestr, $timezonestr = null) {
+ // Use default timezone if not provided.
+ if ($timezonestr === null) {
+ $newdatetime = DateTime::createFromFormat('Ymd\THis', $datestr);
+ } else {
+ $timezone = new DateTimeZone($timezonestr);
+ $newdatetime = DateTime::createFromFormat('Ymd\THis', $datestr, $timezone);
+ }
// Update the start date of the parent event.
$calevent = calendar_event::load($this->event->id);
$syscontext = context_system::instance();
$hassystem = has_capability($capability, $syscontext, $userid);
- $access = get_user_access_sitewide($userid);
+ $access = get_user_roles_sitewide_accessdata($userid);
// Build up a list of level 2 contexts (candidates to be user context).
$filtercontexts = array();
+ // Build list of roles to check overrides.
+ $roles = array();
+
foreach ($access['ra'] as $path => $role) {
$parts = explode('/', $path);
if (count($parts) == 3) {
// We know this is not a user context because there is another path with more than 2 levels.
unset($filtercontexts[$parts[2]]);
}
+ $roles = array_merge($roles, $role);
}
// Add all contexts in which a role may be overidden.
- foreach ($access['rdef'] as $pathandroleid => $def) {
- $matches = array();
- if (!isset($def[$capability])) {
- // The capability is not mentioned, we can ignore.
- continue;
- }
-
- list($contextpath, $roleid) = explode(':', $pathandroleid, 2);
- $parts = explode('/', $contextpath);
- if (count($parts) != 3) {
- // Only get potential user contexts, they only ever have 2 slashes /parentId/Id.
- continue;
+ $rdefs = get_role_definitions($roles);
+ foreach ($rdefs as $roledef) {
+ foreach ($roledef as $path => $caps) {
+ if (!isset($caps[$capability])) {
+ // The capability is not mentioned, we can ignore.
+ continue;
+ }
+ $parts = explode('/', $path);
+ if (count($parts) === 3) {
+ // Only get potential user contexts, they only ever have 2 slashes /parentId/Id.
+ $filtercontexts[$parts[2]] = $parts[2];
+ }
}
-
- $filtercontexts[$parts[2]] = $parts[2];
}
// No interesting contexts - return all or no results.
public static function list_courses($competencyid) {
global $DB;
- $results = $DB->get_records_sql('SELECT course.id, course.visible, course.shortname, course.idnumber, course.fullname
+ $results = $DB->get_records_sql('SELECT course.id, course.visible, course.shortname, course.idnumber,
+ course.fullname, course.summary, course.summaryformat, course.startdate,
+ course.enddate
FROM {course} course
JOIN {' . self::TABLE . '} coursecomp
ON coursecomp.courseid = course.id
$sql = 'SELECT COUNT(comp.id)
FROM {' . self::TABLE . '} usercoursecomp
+ JOIN {' . course_competency::TABLE . '} cc
+ ON usercoursecomp.competencyid = cc.competencyid AND cc.courseid = usercoursecomp.courseid
JOIN {' . competency::TABLE . '} comp
ON usercoursecomp.competencyid = comp.id
WHERE usercoursecomp.courseid = ? AND usercoursecomp.userid = ? AND usercoursecomp.proficiency = ?';