Merge branch 'MDL-58952' of https://github.com/scyrma/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 20 Jun 2017 10:31:30 +0000 (11:31 +0100)
committerDan Poltawski <dan@moodle.com>
Tue, 20 Jun 2017 10:31:30 +0000 (11:31 +0100)
400 files changed:
.eslintrc
Gruntfile.js
admin/cli/mysql_collation.php
admin/registration/forms.php
admin/registration/lib.php
admin/registration/register.php
admin/settings/appearance.php
admin/settings/plugins.php
admin/settings/subsystems.php
admin/templates/setting_filetypes.mustache [new file with mode: 0644]
admin/tool/customlang/db/upgrade.php
admin/tool/log/backup/moodle2/restore_tool_log_logstore_subplugin.class.php
admin/tool/log/db/upgrade.php
admin/tool/log/store/database/db/upgrade.php
admin/tool/log/store/standard/db/upgrade.php
admin/tool/lp/amd/build/competencies.min.js
admin/tool/lp/amd/build/competencyactions.min.js
admin/tool/lp/amd/build/competencypicker.min.js
admin/tool/lp/amd/build/competencypicker_user_plans.min.js
admin/tool/lp/amd/build/competencyruleconfig.min.js
admin/tool/lp/amd/build/form-cohort-selector.min.js
admin/tool/lp/amd/build/form-user-selector.min.js
admin/tool/lp/amd/build/frameworks_datasource.min.js
admin/tool/lp/amd/build/planactions.min.js
admin/tool/lp/amd/build/user_competency_plan_popup.min.js
admin/tool/lp/amd/build/user_competency_workflow.min.js
admin/tool/lp/amd/build/user_evidence_actions.min.js
admin/tool/lp/amd/src/actionselector.js
admin/tool/lp/amd/src/competencies.js
admin/tool/lp/amd/src/competency_rule_points.js
admin/tool/lp/amd/src/competencyactions.js
admin/tool/lp/amd/src/competencypicker.js
admin/tool/lp/amd/src/competencypicker_user_plans.js
admin/tool/lp/amd/src/competencyruleconfig.js
admin/tool/lp/amd/src/evidence_delete.js
admin/tool/lp/amd/src/form-cohort-selector.js
admin/tool/lp/amd/src/form-user-selector.js
admin/tool/lp/amd/src/frameworks_datasource.js
admin/tool/lp/amd/src/parentcompetency_form.js
admin/tool/lp/amd/src/planactions.js
admin/tool/lp/amd/src/user_competency_plan_popup.js
admin/tool/lp/amd/src/user_competency_workflow.js
admin/tool/lp/amd/src/user_evidence_actions.js
admin/tool/lp/templates/competency_rule_config.mustache
admin/tool/monitor/db/upgrade.php
admin/tool/templatelibrary/amd/build/display.min.js
admin/tool/templatelibrary/amd/src/display.js
admin/tool/usertours/amd/src/usertours.js
admin/tool/usertours/db/upgrade.php
auth/cas/db/upgrade.php
auth/db/db/upgrade.php
auth/email/db/upgrade.php
auth/fc/db/upgrade.php
auth/imap/db/upgrade.php
auth/ldap/auth.php
auth/ldap/db/upgrade.php
auth/manual/db/upgrade.php
auth/mnet/db/upgrade.php
auth/nntp/db/upgrade.php
auth/none/db/upgrade.php
auth/oauth2/classes/api.php
auth/oauth2/classes/auth.php
auth/oauth2/db/upgrade.php
auth/pam/db/upgrade.php
auth/pop3/db/upgrade.php
auth/shibboleth/db/upgrade.php
availability/classes/info_section.php
availability/condition/date/classes/condition.php
backup/backup.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/backup_xml_transformer_test.php [new file with mode: 0644]
backup/util/helper/restore_structure_parser_processor.class.php
backup/util/helper/tests/restore_structure_parser_processor_test.php [new file with mode: 0644]
blocks/badges/db/upgrade.php
blocks/calendar_month/db/upgrade.php
blocks/calendar_upcoming/db/upgrade.php
blocks/community/db/upgrade.php
blocks/completionstatus/db/upgrade.php
blocks/completionstatus/details.php
blocks/course_summary/db/upgrade.php
blocks/html/db/upgrade.php
blocks/myoverview/amd/build/event_list.min.js
blocks/myoverview/amd/build/tab_preferences.min.js [new file with mode: 0644]
blocks/myoverview/amd/src/event_list.js
blocks/myoverview/amd/src/tab_preferences.js [new file with mode: 0644]
blocks/myoverview/block_myoverview.php
blocks/myoverview/classes/output/courses_view.php
blocks/myoverview/classes/output/main.php
blocks/myoverview/lang/en/block_myoverview.php
blocks/myoverview/lib.php [new file with mode: 0644]
blocks/myoverview/settings.php [new file with mode: 0644]
blocks/myoverview/templates/main.mustache
blocks/myoverview/tests/behat/block_myoverview_progress.feature
blocks/myoverview/version.php
blocks/navigation/amd/build/ajax_response_renderer.min.js
blocks/navigation/amd/src/ajax_response_renderer.js
blocks/navigation/db/upgrade.php
blocks/navigation/styles.css
blocks/quiz_results/db/upgrade.php
blocks/recent_activity/db/upgrade.php
blocks/rss_client/db/upgrade.php
blocks/section_links/db/upgrade.php
blocks/selfcompletion/db/upgrade.php
blocks/settings/db/upgrade.php
blocks/settings/styles.css
blocks/tags/lang/en/block_tags.php
blocks/tags/lang/en/deprecated.txt [deleted file]
cache/classes/loaders.php
cache/tests/cache_test.php
calendar/classes/local/event/container.php
calendar/classes/local/event/data_access/event_vault.php
calendar/event_form.php
calendar/export_execute.php
calendar/lib.php
calendar/tests/externallib_test.php
calendar/tests/rrule_manager_test.php
competency/classes/api.php
competency/classes/course_competency.php
competency/classes/user_competency_course.php
completion/completion_completion.php
composer.json
composer.lock
course/amd/build/actions.min.js
course/amd/src/actions.js
course/format/topics/db/upgrade.php
course/format/weeks/backup/moodle2/restore_format_weeks_plugin.class.php
course/format/weeks/db/upgrade.php
course/lib.php
course/tests/courselib_test.php
enrol/database/db/upgrade.php
enrol/flatfile/db/upgrade.php
enrol/guest/db/upgrade.php
enrol/imsenterprise/db/upgrade.php
enrol/lti/db/upgrade.php
enrol/manual/db/upgrade.php
enrol/mnet/db/upgrade.php
enrol/paypal/db/upgrade.php
enrol/self/db/upgrade.php
filter/mathjaxloader/db/upgrade.php
filter/mediaplugin/db/upgrade.php
filter/tex/db/upgrade.php
grade/edit/tree/item_form.php
grade/grading/form/guide/db/upgrade.php
grade/grading/form/rubric/db/upgrade.php
grade/report/grader/index.php
grade/report/grader/lib.php
grade/report/grader/module.js
grade/report/user/db/upgrade.php
group/assign.php
group/classes/output/index_page.php [new file with mode: 0644]
group/classes/output/renderer.php [new file with mode: 0644]
group/externallib.php
group/index.php
group/members.php
group/templates/index.mustache [new file with mode: 0644]
group/tests/externallib_test.php
install/lang/bi/langconfig.php [new file with mode: 0644]
install/lang/es/admin.php
install/lang/eu/install.php
install/lang/fa/error.php
install/lang/fa/install.php
install/lang/gl/install.php
install/lang/it/install.php
install/lang/ja/install.php
install/lang/nl/install.php
install/lang/pt/install.php
install/lang/zh_tw/admin.php
lang/en/admin.php
lang/en/cache.php
lang/en/completion.php
lang/en/deprecated.txt
lang/en/form.php
lang/en/grades.php
lang/en/hub.php
lang/en/moodle.php
lang/en/notes.php
lang/en/plugin.php
lang/en/role.php
lang/en/tag.php
lib/accesslib.php
lib/adminlib.php
lib/amd/build/ajax.min.js
lib/amd/build/fragment.min.js
lib/amd/build/templates.min.js
lib/amd/build/user_date.min.js
lib/amd/src/ajax.js
lib/amd/src/fragment.js
lib/amd/src/templates.js
lib/amd/src/user_date.js
lib/antivirus/clamav/db/upgrade.php
lib/classes/output/external.php
lib/classes/output/icon_system_fontawesome.php
lib/classes/plugininfo/repository.php
lib/completionlib.php
lib/cronlib.php
lib/db/caches.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/db/upgrade.php
lib/editor/atto/plugins/equation/db/upgrade.php
lib/editor/tinymce/db/upgrade.php
lib/editor/tinymce/plugins/spellchecker/db/upgrade.php
lib/filelib.php
lib/filestorage/zip_archive.php
lib/filterlib.php
lib/form/amd/build/filetypes.min.js [new file with mode: 0644]
lib/form/amd/src/filetypes.js [new file with mode: 0644]
lib/form/classes/external.php [new file with mode: 0644]
lib/form/classes/filetypes_util.php [new file with mode: 0644]
lib/form/filetypes.php [new file with mode: 0644]
lib/form/templates/filetypes-browser.mustache [new file with mode: 0644]
lib/form/templates/filetypes-descriptions.mustache [new file with mode: 0644]
lib/form/templates/filetypes-trigger.mustache [new file with mode: 0644]
lib/form/tests/external_test.php [new file with mode: 0644]
lib/form/tests/filetypes_util_test.php [new file with mode: 0644]
lib/formslib.php
lib/ldaplib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/php-css-parser/Parser.php
lib/php-css-parser/moodle_readme.txt
lib/phpunit/classes/util.php
lib/setuplib.php
lib/statslib.php
lib/tablelib.php
lib/templates/loginform.mustache [moved from lib/templates/login.mustache with 99% similarity]
lib/tests/accesslib_test.php
lib/tests/filelib_test.php
lib/tests/ldaplib_test.php
lib/tests/output_external_test.php [new file with mode: 0644]
lib/tests/useragent_test.php
lib/upgrade.txt
lib/upgradelib.php
media/player/videojs/amd/build/loader.min.js
media/player/videojs/amd/src/loader.js
media/player/videojs/classes/plugin.php
media/player/videojs/tests/player_test.php
message/amd/build/message_area_messages.min.js
message/amd/build/message_area_search.min.js
message/amd/build/message_repository.min.js
message/amd/src/message_area_messages.js
message/amd/src/message_area_search.js
message/amd/src/message_repository.js
message/output/email/db/upgrade.php
message/output/jabber/db/upgrade.php
message/output/popup/amd/build/message_popover_controller.min.js
message/output/popup/amd/build/notification_area_control_area.min.js
message/output/popup/amd/build/notification_popover_controller.min.js
message/output/popup/amd/build/notification_repository.min.js
message/output/popup/amd/src/message_popover_controller.js
message/output/popup/amd/src/notification_area_control_area.js
message/output/popup/amd/src/notification_popover_controller.js
message/output/popup/amd/src/notification_repository.js
message/output/popup/db/upgrade.php
mod/assign/amd/src/participant_selector.js
mod/assign/db/upgrade.php
mod/assign/feedback/comments/db/upgrade.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/comments/tests/behat/feedback_comments.feature [new file with mode: 0644]
mod/assign/feedback/editpdf/db/upgrade.php
mod/assign/feedback/file/db/upgrade.php
mod/assign/gradingtable.php
mod/assign/locallib.php
mod/assign/override_form.php
mod/assign/overrideedit.php
mod/assign/renderer.php
mod/assign/submission/comments/db/upgrade.php
mod/assign/submission/file/db/upgrade.php
mod/assign/submission/onlinetext/db/upgrade.php
mod/assign/tests/markerallocation_test.php [new file with mode: 0644]
mod/assign/version.php
mod/assignment/db/upgrade.php
mod/book/db/upgrade.php
mod/chat/db/upgrade.php
mod/chat/lib.php
mod/choice/db/upgrade.php
mod/choice/lib.php
mod/data/db/upgrade.php
mod/data/tests/behat/completion_condition_entries.feature
mod/feedback/classes/completion.php
mod/feedback/classes/external.php
mod/feedback/db/upgrade.php
mod/feedback/lib.php
mod/folder/db/upgrade.php
mod/forum/db/upgrade.php
mod/forum/lang/en/deprecated.txt
mod/forum/lang/en/forum.php
mod/glossary/db/upgrade.php
mod/imscp/db/upgrade.php
mod/label/classes/search/activity.php
mod/label/db/upgrade.php
mod/lesson/classes/external.php
mod/lesson/db/upgrade.php
mod/lesson/lang/en/deprecated.txt
mod/lesson/lang/en/lesson.php
mod/lesson/locallib.php
mod/lesson/tests/external_test.php
mod/lesson/upgrade.txt
mod/lti/amd/build/contentitem.min.js
mod/lti/amd/build/tool_card_controller.min.js
mod/lti/amd/src/contentitem.js
mod/lti/amd/src/tool_card_controller.js
mod/lti/db/services.php
mod/lti/db/upgrade.php
mod/lti/locallib.php
mod/page/db/upgrade.php
mod/quiz/comment.php
mod/quiz/db/upgrade.php
mod/quiz/mod_form.php
mod/quiz/module.js
mod/quiz/report/overview/db/upgrade.php
mod/quiz/report/statistics/db/upgrade.php
mod/quiz/tests/behat/editing_remove_multiple_questions.feature
mod/resource/db/upgrade.php
mod/scorm/db/upgrade.php
mod/scorm/lib.php
mod/scorm/mod_form.php
mod/scorm/player.php
mod/scorm/tests/lib_test.php
mod/survey/amd/build/validation.min.js
mod/survey/amd/src/validation.js
mod/survey/db/upgrade.php
mod/url/db/upgrade.php
mod/wiki/create.php
mod/wiki/db/upgrade.php
mod/wiki/locallib.php
mod/workshop/db/upgrade.php
mod/workshop/form/accumulative/db/upgrade.php
mod/workshop/form/comments/db/upgrade.php
mod/workshop/form/numerrors/db/upgrade.php
mod/workshop/form/rubric/db/upgrade.php
npm-shrinkwrap.json
package.json
portfolio/boxnet/db/upgrade.php
portfolio/download/file.php
portfolio/googledocs/db/upgrade.php
portfolio/picasa/db/upgrade.php
question/behaviour/manualgraded/db/upgrade.php
question/type/calculated/db/upgrade.php
question/type/calculated/edit_calculated_form.php
question/type/calculated/questiontype.php
question/type/calculated/tests/questiontype_test.php
question/type/calculatedmulti/edit_calculatedmulti_form.php
question/type/ddmarker/db/upgrade.php
question/type/essay/db/upgrade.php
question/type/match/db/upgrade.php
question/type/multianswer/db/upgrade.php
question/type/multichoice/classes/admin_setting_answernumbering.php [new file with mode: 0644]
question/type/multichoice/db/upgrade.php
question/type/multichoice/edit_multichoice_form.php
question/type/multichoice/lang/en/qtype_multichoice.php
question/type/multichoice/renderer.php
question/type/multichoice/settings.php [new file with mode: 0644]
question/type/numerical/db/upgrade.php
question/type/random/db/upgrade.php
question/type/randomsamatch/db/upgrade.php
question/type/shortanswer/db/upgrade.php
report/competency/amd/build/grading_popup.min.js
report/competency/amd/src/grading_popup.js
report/stats/locallib.php
report/stats/user.php
repository/boxnet/db/upgrade.php
repository/dropbox/db/upgrade.php
repository/googledocs/db/upgrade.php
repository/onedrive/db/upgrade.php
repository/picasa/db/upgrade.php
search/classes/engine.php
search/classes/manager.php
tag/manage.php
tag/tests/events_test.php
theme/boost/classes/output/core_renderer.php
theme/boost/cli/import-bootswatch.php
theme/boost/cli/readme_moodle.txt [deleted file]
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/drawer.scss
theme/boost/scss/moodle/forms.scss
theme/boost/templates/core/loginform.mustache [moved from theme/boost/templates/core/login.mustache with 99% similarity]
theme/boost/templates/core_form/element-filetypes.mustache [new file with mode: 0644]
theme/boost/templates/header.mustache
theme/boost/tests/behat/behat_theme_boost_behat_course.php
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/tool_usertours.less
theme/bootstrapbase/style/moodle.css
theme/bootstrapbase/templates/block_myoverview/main.mustache
theme/clean/classes/core_renderer.php
theme/more/db/upgrade.php
theme/styles.php
theme/upgrade.txt
user/externallib.php
user/files.php
user/profile/field/menu/field.class.php
user/tests/externallib_test.php
user/tests/userlib_test.php
version.php

index 3514759..c50d2bb 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,4 +1,7 @@
 {
+  '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()'
     }],
+
   }
 }
index 4cb9e9e..5f2302e 100644 (file)
@@ -338,7 +338,7 @@ module.exports = function(grunt) {
             opts: {stdio: 'inherit', env: process.env}
         }, function(error, result, code) {
             // Propagate the exit code.
-            done(code);
+            done(code === 0);
         });
     };
 
index adaadbb..ae3c709 100644 (file)
@@ -53,7 +53,7 @@ Options:
 -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'])) {
@@ -145,9 +145,22 @@ 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";
@@ -290,3 +303,26 @@ function mysql_get_column_collations($tablename) {
     $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;
+}
index c4373d1..6e0b8a9 100644 (file)
@@ -249,6 +249,11 @@ class site_registration_form extends moodleform {
         $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);
@@ -386,6 +391,21 @@ class site_registration_form extends moodleform {
         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'),
@@ -438,6 +458,28 @@ class site_registration_form extends moodleform {
                     " " . 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));
@@ -489,6 +531,28 @@ class site_registration_form extends moodleform {
                     " " . 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
index 5a521e1..1e48bc8 100644 (file)
@@ -280,6 +280,21 @@ class registration_manager {
         $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;
     }
index 86ac102..895ae5c 100644 (file)
@@ -68,7 +68,8 @@ if (!empty($fromform) and confirm_sesskey()) {
     // 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;
@@ -101,6 +102,10 @@ if (!empty($fromform) and confirm_sesskey()) {
     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 ////////
@@ -143,6 +148,10 @@ if (!empty($fromform) and empty($update) and confirm_sesskey()) {
         $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)
 
index 5a20af8..1a63f4f 100644 (file)
@@ -179,7 +179,8 @@ preferences,moodle|/user/preferences.php|preferences',
         '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));
index 1dfdd6a..e63f190 100644 (file)
@@ -232,7 +232,7 @@ if ($hassiteconfig) {
 
     // 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);
 
index 6f5ded5..f2b50d5 100644 (file)
@@ -55,9 +55,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $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));
 }
diff --git a/admin/templates/setting_filetypes.mustache b/admin/templates/setting_filetypes.mustache
new file mode 100644 (file)
index 0000000..7075ea2
--- /dev/null
@@ -0,0 +1,52 @@
+{{!
+    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>
index 60bce5a..d6fa182 100644 (file)
@@ -44,5 +44,8 @@ function xmldb_tool_customlang_upgrade($oldversion) {
     // 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;
 }
index 0c00f00..c48c2c8 100644 (file)
@@ -84,8 +84,7 @@ abstract class restore_tool_log_logstore_subplugin extends restore_subplugin {
             }
         }
 
-        // 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));
index 8590d90..3d7f4f9 100644 (file)
@@ -48,5 +48,8 @@ function xmldb_tool_log_upgrade($oldversion) {
     // 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;
 }
index ec98e95..789415e 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_logstore_database_upgrade($oldversion) {
     // 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;
 }
index 3313749..2614e0a 100644 (file)
@@ -59,5 +59,8 @@ function xmldb_logstore_standard_upgrade($oldversion) {
     // 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;
 }
index 6177d34..1f5ddbe 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js and b/admin/tool/lp/amd/build/competencies.min.js differ
index 594bc75..143c935 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyactions.min.js and b/admin/tool/lp/amd/build/competencyactions.min.js differ
index e4312d2..e32b080 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker.min.js and b/admin/tool/lp/amd/build/competencypicker.min.js differ
index bf54916..fd3bf2d 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker_user_plans.min.js and b/admin/tool/lp/amd/build/competencypicker_user_plans.min.js differ
index f5f7f43..db877a5 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyruleconfig.min.js and b/admin/tool/lp/amd/build/competencyruleconfig.min.js differ
index b582a15..fd26ffb 100644 (file)
Binary files a/admin/tool/lp/amd/build/form-cohort-selector.min.js and b/admin/tool/lp/amd/build/form-cohort-selector.min.js differ
index d228806..febb8d4 100644 (file)
Binary files a/admin/tool/lp/amd/build/form-user-selector.min.js and b/admin/tool/lp/amd/build/form-user-selector.min.js differ
index cfc5c70..9f2ec48 100644 (file)
Binary files a/admin/tool/lp/amd/build/frameworks_datasource.min.js and b/admin/tool/lp/amd/build/frameworks_datasource.min.js differ
index aeb8b9e..f63fbbc 100644 (file)
Binary files a/admin/tool/lp/amd/build/planactions.min.js and b/admin/tool/lp/amd/build/planactions.min.js differ
index 610c935..755a664 100644 (file)
Binary files a/admin/tool/lp/amd/build/user_competency_plan_popup.min.js and b/admin/tool/lp/amd/build/user_competency_plan_popup.min.js differ
index 821222f..deab0e1 100644 (file)
Binary files a/admin/tool/lp/amd/build/user_competency_workflow.min.js and b/admin/tool/lp/amd/build/user_competency_workflow.min.js differ
index a69a9d8..f8b28d7 100644 (file)
Binary files a/admin/tool/lp/amd/build/user_evidence_actions.min.js and b/admin/tool/lp/amd/build/user_evidence_actions.min.js differ
index 582478b..380e05a 100644 (file)
@@ -131,6 +131,7 @@ define(['jquery',
                 html,
                 self._afterRender.bind(self)
             );
+            return;
         }).fail(Notification.exception);
     };
 
@@ -156,6 +157,7 @@ define(['jquery',
         return self._render().then(function(html) {
             self._find('[data-region="action-selector"]').replaceWith(html);
             self._afterRender();
+            return;
         });
     };
 
index 79e1aa9..1b82116 100644 (file)
@@ -181,13 +181,13 @@ define(['jquery',
                     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);
             });
         }
 
index 5c4ec17..542cf09 100644 (file)
@@ -166,6 +166,7 @@ define(['jquery',
             // We're done, let's trigger a change.
             self._templateLoaded = true;
             self._triggerChange();
+            return;
         });
     };
 
index 522e827..5c433f4 100644 (file)
@@ -430,12 +430,13 @@ define(['jquery',
                 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);
             });
         }
 
@@ -472,7 +473,8 @@ define(['jquery',
                 relatedTarget.ruleconfig = config.ruleconfig;
                 renderCompetencySummary(relatedTarget);
             }
-        }, notification.exception);
+            return;
+        }).catch(notification.exception);
     };
 
     /**
@@ -692,28 +694,27 @@ define(['jquery',
                     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);
     };
 
     /**
@@ -776,16 +777,17 @@ define(['jquery',
             // 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();
index 1aebe05..23dce8a 100644 (file)
@@ -134,7 +134,7 @@ define(['jquery',
         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);
             });
         }
 
@@ -203,15 +203,15 @@ define(['jquery',
      */
     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);
     };
 
     /**
@@ -388,6 +388,7 @@ define(['jquery',
         return self._render().then(function(html) {
             self._find('[data-region="competencylinktree"]').replaceWith(html);
             self._afterRender();
+            return;
         });
     };
 
index 8d5b536..e621f32 100644 (file)
@@ -77,7 +77,8 @@ define(['jquery',
         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);
             });
         }
     };
index 3a956cb..b0e3db6 100644 (file)
@@ -165,14 +165,14 @@ define(['jquery',
         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);
     };
 
@@ -312,9 +312,9 @@ define(['jquery',
      */
     RuleConfig.prototype._initOutcomes = function() {
         var self = this;
-
         return Outcomes.getAll().then(function(outcomes) {
             self._outcomesOption = outcomes;
+            return;
         });
     };
 
@@ -328,11 +328,11 @@ define(['jquery',
     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);
@@ -518,13 +518,13 @@ define(['jquery',
             self._afterChange();
             return;
         }
-
         rule.injectTemplate(container).then(function() {
             container.show();
-        }, function() {
-            container.empty().hide();
+            return;
         }).always(function() {
             self._afterChange();
+        }).catch(function() {
+            container.empty().hide();
         });
     };
 
index ac59ad9..e695e12 100644 (file)
@@ -76,6 +76,7 @@ define(['jquery',
                         }]);
                         promise[0].then(function() {
                             parent.remove();
+                            return;
                         }).fail(Notification.exception);
                     }
                 );
index 486c25c..281c492 100644 (file)
@@ -51,7 +51,6 @@ define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
                     includes: includes
                 }
             }]);
-
             promise[0].then(function(results) {
                 var promises = [],
                     i = 0;
@@ -69,9 +68,10 @@ define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
                         i++;
                     });
                     success(results.cohorts);
+                    return;
                 });
 
-            }failure);
+            }).catch(failure);
         }
 
     };
index 8713d06..3d1e613 100644 (file)
@@ -79,9 +79,10 @@ define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
                         i++;
                     });
                     success(results.users);
+                    return;
                 });
 
-            }failure);
+            }).catch(failure);
         }
 
     };
index 3638830..c46d005 100644 (file)
@@ -35,20 +35,17 @@ define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notificat
          * @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);
         },
 
         /**
@@ -76,6 +73,7 @@ define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notificat
          * @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'),
@@ -84,11 +82,10 @@ define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notificat
             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);
         }
     };
 
index 705ab29..c42aece 100644 (file)
@@ -81,6 +81,7 @@ define(['jquery', 'core/ajax', 'core/str', 'tool_lp/competencypicker', 'core/tem
             Str.get_string('competencyframeworkroot', 'tool_lp').then(function(rootframework) {
                 $(self.staticElementSelector).html(rootframework);
                 $(self.inputHiddenSelector).val(data.competencyId);
+                return;
             }).fail(Notification.exception);
         }
     };
index 932318f..be39396 100644 (file)
@@ -110,15 +110,16 @@ define(['jquery',
      * 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;
+            });
     };
 
     /**
@@ -130,16 +131,15 @@ define(['jquery',
      */
     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);
     };
index 6600b1f..e7aff46 100644 (file)
@@ -58,7 +58,6 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/templates'
             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';
@@ -66,12 +65,11 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/templates'
             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);
     };
 
     /**
index f61c0f9..d6d6b71 100644 (file)
@@ -61,7 +61,7 @@ define(['jquery',
         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));
     };
@@ -106,7 +106,7 @@ define(['jquery',
         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));
     };
@@ -147,11 +147,10 @@ define(['jquery',
                 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));
     };
@@ -196,7 +195,7 @@ define(['jquery',
         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));
     };
index b9ef686..1c37728 100644 (file)
@@ -98,14 +98,15 @@ define(['jquery',
      * 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;
+            });
     };
 
     /**
@@ -117,7 +118,6 @@ define(['jquery',
      */
     UserEvidenceActions.prototype._callAndRefresh = function(calls, evidenceData) {
         var self = this;
-
         calls.push({
             methodname: self._contextMethod,
             args: self._getContextArgs(evidenceData)
@@ -126,7 +126,7 @@ define(['jquery',
         // 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);
     };
index b0e9db8..167a423 100644 (file)
         {{/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>
index b9982d5..1a47f3a 100644 (file)
@@ -97,5 +97,8 @@ function xmldb_tool_monitor_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017021300, 'tool', 'monitor');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 4b5103c..0c75544 100644 (file)
Binary files a/admin/tool/templatelibrary/amd/build/display.min.js and b/admin/tool/templatelibrary/amd/build/display.min.js differ
index 1c2e4de..3979366 100644 (file)
@@ -126,7 +126,8 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
             args: {
                     component: component,
                     template: name,
-                    themename: config.theme
+                    themename: config.theme,
+                    includecomments: true
             }
         }, {
             methodname: 'tool_templatelibrary_load_canonical_template',
index e0d877d..4541f23 100644 (file)
@@ -68,6 +68,7 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
                 templates.render('tool_usertours/tourstep', {})
             ).then(function(response, template) {
                 usertours.startBootstrapTour(tourId, template[0], response.tourconfig);
+                return;
             }).fail(notification.exception);
         },
 
@@ -213,6 +214,7 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
                 if (response.startTour) {
                     usertours.fetchTour(response.startTour);
                 }
+                return;
             }).fail(notification.exception);
         }
     };
index aa235b1..acd503d 100644 (file)
@@ -45,5 +45,8 @@ function xmldb_tool_usertours_upgrade($oldversion) {
     // 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;
 }
index 7c7c1c0..a17ef91 100644 (file)
@@ -66,5 +66,8 @@ function xmldb_auth_cas_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'cas');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 00e18b9..536eee8 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_db_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017032800, 'auth', 'db');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index cd0ed73..2fa9a44 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_email_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'email');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 61a9bab..e4f3b98 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_fc_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'fc');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index d26dac2..d33e70a 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_imap_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'imap');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 7e6fd3b..7f59282 100644 (file)
@@ -276,7 +276,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             }
             $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;
@@ -634,7 +634,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         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) {
@@ -1201,7 +1201,7 @@ class auth_plugin_ldap extends auth_plugin_base {
                 return false;
             }
 
-            $user_entry = array_change_key_case($user_entry[0], CASE_LOWER);
+            $user_entry = $user_entry[0];
 
             foreach ($attrmap as $key => $ldapkeys) {
                 $profilefield = '';
@@ -1369,7 +1369,7 @@ class auth_plugin_ldap extends auth_plugin_base {
                 $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
@@ -1844,7 +1844,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         }
 
         $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.
@@ -1889,17 +1889,17 @@ class auth_plugin_ldap extends auth_plugin_base {
         }
 
         $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.
@@ -1907,7 +1907,7 @@ class auth_plugin_ldap extends auth_plugin_base {
                 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];
                     }
index 355b719..a2fe48e 100644 (file)
@@ -66,5 +66,8 @@ function xmldb_auth_ldap_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'ldap');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index ca483c0..6a947d8 100644 (file)
@@ -54,5 +54,8 @@ function xmldb_auth_manual_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'manual');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 46786fe..c66ead0 100644 (file)
@@ -53,5 +53,8 @@ function xmldb_auth_mnet_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'mnet');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 8708487..bd869bc 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_nntp_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'nntp');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 56bf606..62516a6 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_none_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'none');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 689ad1c..9d678a8 100644 (file)
@@ -192,10 +192,10 @@ class api {
         ];
         $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.
@@ -303,9 +303,10 @@ class api {
         ];
         $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.
index fc230aa..812cf98 100644 (file)
@@ -247,6 +247,10 @@ class auth extends \auth_plugin_base {
         if (!empty($user->picture)) {
             return false;
         }
+        if (!empty($CFG->enablegravatar)) {
+            return false;
+        }
+
         $picture = $this->get_static_user_picture();
         if (empty($picture)) {
             return false;
index c64c633..358006a 100644 (file)
@@ -38,5 +38,8 @@ function xmldb_auth_oauth2_upgrade($oldversion) {
     // 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;
 }
index e69eb57..8fe55c2 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_pam_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'pam');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index e540c77..39bc594 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_pop3_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'pop3');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index 38cec6c..ae1566b 100644 (file)
@@ -42,5 +42,8 @@ function xmldb_auth_shibboleth_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2017020700, 'auth', 'shibboleth');
     }
 
+    // Automatically generated Moodle v3.3.0 release upgrade line.
+    // Put any upgrade step following this.
+
     return true;
 }
index d65729a..6ef4fc9 100644 (file)
@@ -62,8 +62,12 @@ class info_section extends info {
 
     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);
     }
 
     /**
index 722b024..63b61b4 100644 (file)
@@ -287,8 +287,12 @@ class condition extends \core_availability\condition {
 
             // 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;
             }
         }
index ff2d38c..d8f66f6 100644 (file)
@@ -140,7 +140,7 @@ abstract class backup implements checksumable {
     /**
      * Usually same than major release zero version, mainly for informative/historic purposes.
      */
-    const RELEASE = '3.3';
+    const RELEASE = '3.4';
 }
 
 /*
index acdf34b..2ea5248 100644 (file)
@@ -325,7 +325,7 @@ class backup_section_structure_step extends backup_structure_step {
 
         $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);
index bfd741b..f886496 100644 (file)
@@ -795,7 +795,8 @@ class restore_rebuild_course_cache extends restore_execution_step {
             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
             }
         }
@@ -1575,8 +1576,9 @@ class restore_section_structure_step extends restore_structure_step {
         $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;
@@ -1721,8 +1723,12 @@ class restore_section_structure_step extends restore_structure_step {
                     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);
         }
     }
 
@@ -2658,8 +2664,9 @@ class restore_calendarevents_structure_step extends restore_structure_step {
         $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;
         }
 
@@ -3082,7 +3089,8 @@ class restore_course_logs_structure_step extends restore_structure_step {
 
         $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;
@@ -3129,7 +3137,8 @@ class restore_activity_logs_structure_step extends restore_course_logs_structure
 
         $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();
@@ -4031,11 +4040,13 @@ class restore_module_structure_step extends restore_structure_step {
         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
@@ -4089,7 +4100,12 @@ class restore_module_structure_step extends restore_structure_step {
         } 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.)
diff --git a/backup/moodle2/tests/backup_xml_transformer_test.php b/backup/moodle2/tests/backup_xml_transformer_test.php
new file mode 100644 (file)
index 0000000..55af176
--- /dev/null
@@ -0,0 +1,99 @@
+<?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));
+    }
+
+}
index 500fa0c..add8d85 100644 (file)
@@ -60,17 +60,26 @@ class restore_structure_parser_processor extends grouped_parser_processor {
         } else if (strpos($cdata, '$@FILEPHP@$') === false) { // No $@FILEPHP@$, nothing to convert
             return $cdata;
         }
+
+        if ($CFG->slasharguments) {
+            $slash = '/';
+            $forcedownload = '?forcedownload=1';
+        } else {
+            $slash = '%2F';
+            $forcedownload = '&amp;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', '&amp;forcedownload=1');
-        }
+        $replace = array($slash, $forcedownload);
+
         return str_replace($search, $replace, $result);
     }
 
diff --git a/backup/util/helper/tests/restore_structure_parser_processor_test.php b/backup/util/helper/tests/restore_structure_parser_processor_test.php
new file mode 100644 (file)
index 0000000..069163a
--- /dev/null
@@ -0,0 +1,132 @@
+<?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&amp;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));
+    }
+
+}
index 09ca077..7d529ad 100644 (file)
@@ -88,5 +88,8 @@ function xmldb_block_badges_upgrade($oldversion, $block) {
     // 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;
 }
index f1c1a52..09f73a6 100644 (file)
@@ -88,5 +88,8 @@ function xmldb_block_calendar_month_upgrade($oldversion, $block) {
     // 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;
 }
index 7a20754..d867d08 100644 (file)
@@ -88,5 +88,8 @@ function xmldb_block_calendar_upcoming_upgrade($oldversion, $block) {
     // 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;
 }
index cbdb471..12ae568 100644 (file)
@@ -61,5 +61,8 @@ function xmldb_block_community_upgrade($oldversion) {
     // 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;
 }
index ec79d8a..a04db38 100644 (file)
@@ -63,5 +63,8 @@ function xmldb_block_completionstatus_upgrade($oldversion, $block) {
     // 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;
 }
index abb479b..33bbe77 100644 (file)
@@ -90,7 +90,7 @@ echo html_writer::start_tag('tbody');
 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');
@@ -99,7 +99,7 @@ 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('status'));
+echo html_writer::tag('b', get_string('status') . ' ');
 
 // Is course complete?
 $coursecomplete = $info->is_course_complete($user->id);
@@ -141,7 +141,7 @@ if (empty($completions)) {
 } 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();
@@ -214,7 +214,7 @@ if (empty($completions)) {
                     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;
             }
         }
index 3613226..3d972ce 100644 (file)
@@ -63,5 +63,8 @@ function xmldb_block_course_summary_upgrade($oldversion, $block) {
     // 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;
 }
index 3e57de1..2899efe 100644 (file)
@@ -48,5 +48,8 @@ function xmldb_block_html_upgrade($oldversion) {
     // 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;
 }
index cdc315e..0d04b59 100644 (file)
Binary files a/blocks/myoverview/amd/build/event_list.min.js and b/blocks/myoverview/amd/build/event_list.min.js differ
diff --git a/blocks/myoverview/amd/build/tab_preferences.min.js b/blocks/myoverview/amd/build/tab_preferences.min.js
new file mode 100644 (file)
index 0000000..da5bd97
Binary files /dev/null and b/blocks/myoverview/amd/build/tab_preferences.min.js differ
index 17e888f..5d1d42d 100644 (file)
@@ -350,34 +350,37 @@ define(['jquery', 'core/notification', 'core/templates',
 
         // 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() {
diff --git a/blocks/myoverview/amd/src/tab_preferences.js b/blocks/myoverview/amd/src/tab_preferences.js
new file mode 100644 (file)
index 0000000..25ac2ee
--- /dev/null
@@ -0,0 +1,61 @@
+// 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
+    };
+});
index f22ce15..8afd4a1 100644 (file)
@@ -50,7 +50,16 @@ class block_myoverview extends block_base {
             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();
@@ -68,4 +77,13 @@ class block_myoverview extends block_base {
     public function applicable_formats() {
         return array('my' => true);
     }
+
+    /**
+     * This block does contain a configuration settings.
+     *
+     * @return boolean
+     */
+    public function has_config() {
+        return true;
+    }
 }
index 31dc8c6..798eb7b 100644 (file)
@@ -63,8 +63,6 @@ class courses_view implements renderable, templatable {
      * @return array
      */
     public function export_for_template(renderer_base $output) {
-        $today = time();
-
         // Build courses view data structure.
         $coursesview = [
             'hascourses' => !empty($this->courses)
@@ -73,8 +71,6 @@ class courses_view implements renderable, templatable {
         // 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, [
@@ -84,14 +80,17 @@ class courses_view implements renderable, templatable {
             // 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);
 
@@ -100,7 +99,7 @@ class courses_view implements renderable, templatable {
                 $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);
 
index 6215a5a..2435f54 100644 (file)
@@ -29,6 +29,7 @@ use renderer_base;
 use templatable;
 use core_completion\progress;
 
+require_once($CFG->dirroot . '/blocks/myoverview/lib.php');
 require_once($CFG->libdir . '/completionlib.php');
 
 /**
@@ -39,6 +40,20 @@ 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.
      *
@@ -73,13 +88,24 @@ class main implements renderable, templatable {
         $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
         ];
     }
 }
index 4c464f5..99fb83f 100644 (file)
@@ -22,6 +22,8 @@
  * @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';
diff --git a/blocks/myoverview/lib.php b/blocks/myoverview/lib.php
new file mode 100644 (file)
index 0000000..a73db25
--- /dev/null
@@ -0,0 +1,52 @@
+<?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;
+}
diff --git a/blocks/myoverview/settings.php b/blocks/myoverview/settings.php
new file mode 100644 (file)
index 0000000..10f084d
--- /dev/null
@@ -0,0 +1,39 @@
+<?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));
+}
index 3a1a942..e9b21bd 100644 (file)
 }}
 
 <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}}
index 8bd3afe..e8d8692 100644 (file)
@@ -53,6 +53,7 @@ Feature: Course overview block show users their progress on courses
     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"
index fd45f77..a637f48 100644 (file)
@@ -24,6 +24,6 @@
 
 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).
index 4c69492..02578ec 100644 (file)
Binary files a/blocks/navigation/amd/build/ajax_response_renderer.min.js and b/blocks/navigation/amd/build/ajax_response_renderer.min.js differ
index d935742..99f2e28 100644 (file)
@@ -22,7 +22,7 @@
  * @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.
@@ -75,28 +75,12 @@ define(['jquery'], function($) {
                 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');
@@ -106,12 +90,8 @@ define(['jquery'], function($) {
             } 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');
@@ -120,6 +100,31 @@ define(['jquery'], function($) {
                 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);
 
index 3de8a6b..538c829 100644 (file)
@@ -70,5 +70,8 @@ function xmldb_block_navigation_upgrade($oldversion, $block) {
     // 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;
 }
index bb541f5..2369c1a 100644 (file)
@@ -71,6 +71,6 @@
     display: block;
 }
 
-.block_navigation .block_tree [aria-hidden="true"] {
+.block_navigation .block_tree [aria-hidden="true"]:not(.icon) {
     display: none;
 }
index 32ae28c..68dfa30 100644 (file)
@@ -113,5 +113,8 @@ function xmldb_block_quiz_results_upgrade($oldversion, $block) {
     // 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
index 45b9d65..186c752 100644 (file)
@@ -62,5 +62,8 @@ function xmldb_block_recent_activity_upgrade($oldversion, $block) {
     // 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;
 }
index 845a93c..78e0c0a 100644 (file)
@@ -59,5 +59,8 @@ function xmldb_block_rss_client_upgrade($oldversion) {
     // 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;
 }
index 6363ae0..397768a 100644 (file)
@@ -64,5 +64,8 @@ function xmldb_block_section_links_upgrade($oldversion, $block) {
     // 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;
 }
index 0f23a91..cc7971d 100644 (file)
@@ -63,5 +63,8 @@ function xmldb_block_selfcompletion_upgrade($oldversion, $block) {
     // 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;
 }
index 98bbed2..b48a648 100644 (file)
@@ -70,5 +70,8 @@ function xmldb_block_settings_upgrade($oldversion, $block) {
     // 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;
 }
index e0c94b2..3d7e1d1 100644 (file)
@@ -58,7 +58,7 @@
     display: block;
 }
 
-.block_settings .block_tree  [aria-hidden="true"] {
+.block_settings .block_tree  [aria-hidden="true"]:not(.icon) {
     display: none;
 }
 
index 642e147..5fce06e 100644 (file)
@@ -39,52 +39,3 @@ $string['taggeditemscontext_help'] = 'You can limit the tag cloud to the tags th
 $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';
diff --git a/blocks/tags/lang/en/deprecated.txt b/blocks/tags/lang/en/deprecated.txt
deleted file mode 100644 (file)
index 7c83a9d..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-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
index 9ea6df3..168124d 100644 (file)
@@ -1077,7 +1077,7 @@ class cache implements cache_loader {
                 $result = $data;
             }
         }
-        if ($result) {
+        if ($result !== false) {
             if ($this->perfdebug) {
                 cache_helper::record_cache_hit('** static acceleration **', $this->definition);
             }
@@ -2162,4 +2162,4 @@ class cache_session extends cache {
  */
 class cache_request extends cache {
     // This comment appeases code pre-checker ;) !
-}
\ No newline at end of file
+}
index ef47096..0a47e4f 100644 (file)
@@ -2177,6 +2177,39 @@ class core_cache_testcase extends advanced_testcase {
             $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);
index 20b39ec..9ac8cbf 100644 (file)
@@ -71,11 +71,6 @@ class container {
      */
     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.
      */
@@ -91,16 +86,6 @@ class container {
      */
     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
@@ -129,8 +114,8 @@ class container {
             );
 
             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)) {
@@ -183,6 +168,19 @@ class container {
         }
     }
 
+    /**
+     * 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.
      *
@@ -214,88 +212,74 @@ class container {
     }
 
     /**
-     * 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;
     }
 }
index c24c5f8..c6c7c68 100644 (file)
@@ -164,7 +164,7 @@ class event_vault implements event_vault_interface {
             $coursesfilter,
             $where,
             $params,
-            "e.timesort ASC, e.id ASC",
+            "COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
             $offset,
             $limitnum,
             $ignorehidden
index 6a1bdf9..cfff16f 100644 (file)
@@ -72,7 +72,8 @@ class event_form extends moodleform {
             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');
index a711b76..9952dee 100644 (file)
@@ -184,6 +184,7 @@ $events = calendar_get_legacy_events($timestart, $timeend, $users, $groups, arra
 
 $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);
@@ -194,10 +195,20 @@ foreach($events as $event) {
     $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
index 1d5c19b..55ad67f 100644 (file)
@@ -1481,7 +1481,7 @@ function calendar_get_mini($courses, $groups, $users, $calmonth = false, $calyea
                 $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']
index 9912bcc..fc0ecc2 100644 (file)
@@ -674,13 +674,15 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
      */
     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();
@@ -692,11 +694,12 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
         ];
 
         $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);
@@ -705,7 +708,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
         $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);
index 3c3e7c0..ca28508 100644 (file)
@@ -451,6 +451,9 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
      */
     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');
@@ -463,7 +466,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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;
@@ -552,8 +556,20 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
                 $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);
@@ -564,13 +580,14 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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);
@@ -676,17 +693,21 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     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);
@@ -828,24 +849,33 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     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);
@@ -890,7 +920,7 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         // 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);
@@ -917,18 +947,6 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
             $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();
@@ -1004,12 +1022,45 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         }
     }
 
+    /**
+     * 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);
@@ -1028,9 +1079,9 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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);
@@ -1050,6 +1101,9 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     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');
@@ -1147,14 +1201,19 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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');
 
@@ -1163,7 +1222,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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'));
@@ -1341,16 +1401,21 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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';
@@ -1358,7 +1423,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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'));
@@ -1704,25 +1770,28 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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'));
@@ -1878,22 +1947,29 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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'));
@@ -2063,18 +2139,22 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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'));
 
@@ -2110,18 +2190,22 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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'));
@@ -2156,18 +2240,21 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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');
 
@@ -2176,7 +2263,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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'));
@@ -2186,7 +2274,7 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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));
@@ -2211,18 +2299,21 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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'));
 
@@ -2235,14 +2326,15 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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));
@@ -2264,22 +2356,28 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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'));
@@ -2295,17 +2393,22 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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);
 
@@ -2314,7 +2417,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $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'));
@@ -2340,17 +2444,29 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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);
 
@@ -2417,23 +2533,27 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
      * 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(),
@@ -2454,10 +2574,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
             $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++;
         }
     }
 
@@ -2757,13 +2875,19 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * 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);
index 60adab4..0604f3e 100644 (file)
@@ -5154,9 +5154,12 @@ class api {
         $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) {
@@ -5165,24 +5168,23 @@ class api {
                 // 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.
index b929b67..3033d0d 100644 (file)
@@ -243,7 +243,9 @@ class course_competency extends persistent {
     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
index e0e7d4e..392e1c5 100644 (file)
@@ -243,6 +243,8 @@ class user_competency_course extends persistent {
 
         $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 = ?';