Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install
authorSara Arjona <sara@moodle.com>
Thu, 10 Jan 2019 16:51:23 +0000 (17:51 +0100)
committerSara Arjona <sara@moodle.com>
Thu, 10 Jan 2019 16:51:23 +0000 (17:51 +0100)
196 files changed:
.travis.yml
admin/environment.xml
admin/settings/subsystems.php
admin/tool/customlang/db/upgrade.php
admin/tool/dataprivacy/amd/build/effective_retention_period.min.js
admin/tool/dataprivacy/amd/src/effective_retention_period.js
admin/tool/log/db/upgrade.php
admin/tool/log/store/database/db/upgrade.php
admin/tool/log/store/standard/db/upgrade.php
admin/tool/monitor/db/upgrade.php
admin/tool/task/lang/en/tool_task.php
admin/tool/usertours/db/upgrade.php
admin/tool/xmldb/lang/en/tool_xmldb.php
auth/cas/db/upgrade.php
auth/db/db/upgrade.php
auth/email/db/upgrade.php
auth/ldap/db/upgrade.php
auth/ldap/lang/en/auth_ldap.php
auth/manual/db/upgrade.php
auth/mnet/db/upgrade.php
auth/mnet/lang/en/auth_mnet.php
auth/none/db/upgrade.php
auth/oauth2/classes/auth.php
auth/oauth2/db/upgrade.php
auth/shibboleth/db/upgrade.php
badges/backpack_form.php
blocks/badges/db/upgrade.php
blocks/badges/lang/en/block_badges.php
blocks/calendar_month/db/upgrade.php
blocks/calendar_upcoming/db/upgrade.php
blocks/community/db/upgrade.php
blocks/completionstatus/db/upgrade.php
blocks/course_list/lang/en/block_course_list.php
blocks/course_summary/db/upgrade.php
blocks/html/db/upgrade.php
blocks/navigation/db/upgrade.php
blocks/quiz_results/db/upgrade.php
blocks/recent_activity/db/upgrade.php
blocks/rss_client/db/upgrade.php
blocks/rss_client/lang/en/block_rss_client.php
blocks/section_links/db/upgrade.php
blocks/selfcompletion/db/upgrade.php
blocks/settings/db/upgrade.php
cache/stores/memcached/lang/en/cachestore_memcached.php
composer.json
composer.lock
course/renderer.php
enrol/database/db/upgrade.php
enrol/flatfile/db/upgrade.php
enrol/guest/db/upgrade.php
enrol/imsenterprise/db/upgrade.php
enrol/ldap/lang/en/enrol_ldap.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
files/converter/unoconv/lang/en/fileconverter_unoconv.php
filter/mathjaxloader/db/upgrade.php
filter/mediaplugin/db/upgrade.php
filter/tex/db/upgrade.php
grade/export/txt/tests/behat/export.feature
grade/export/xml/tests/behat/export.feature
grade/grading/form/guide/db/upgrade.php
grade/grading/form/rubric/db/upgrade.php
grade/grading/form/rubric/styles.css
grade/report/grader/lang/en/gradereport_grader.php
grade/report/user/db/upgrade.php
lang/en/admin.php
lang/en/error.php
lang/en/grades.php
lib/accesslib.php
lib/amd/build/modal.min.js
lib/amd/build/str.min.js
lib/amd/build/templates.min.js
lib/amd/src/modal.js
lib/amd/src/str.js
lib/amd/src/templates.js
lib/antivirus/clamav/db/upgrade.php
lib/classes/output/external.php
lib/classes/output/mustache_template_source_loader.php [new file with mode: 0644]
lib/classes/task/messaging_cleanup_task.php
lib/db/access.php
lib/db/services.php
lib/db/upgrade.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/gradelib.php
lib/moodlelib.php
lib/outputrenderers.php
lib/phpminimumversionlib.php
lib/questionlib.php
lib/statslib.php
lib/tests/accesslib_test.php
lib/tests/environment_test.php
lib/tests/mustache_template_source_loader_test.php [new file with mode: 0644]
lib/tests/output_external_test.php [deleted file]
lib/tests/questionlib_test.php
lib/tests/statslib_test.php
media/player/videojs/lang/en/media_videojs.php
message/amd/build/message_drawer_view_conversation_patcher.min.js
message/amd/src/message_drawer_view_conversation_patcher.js
message/classes/api.php
message/externallib.php
message/output/email/db/upgrade.php
message/output/email/message_output_email.php
message/output/jabber/db/upgrade.php
message/output/jabber/lang/en/message_jabber.php
message/output/popup/db/upgrade.php
mod/assign/db/upgrade.php
mod/assign/feedback/comments/db/upgrade.php
mod/assign/feedback/editpdf/db/upgrade.php
mod/assign/feedback/file/db/upgrade.php
mod/assign/styles.css
mod/assign/submission/comments/db/upgrade.php
mod/assign/submission/file/db/upgrade.php
mod/assign/submission/onlinetext/db/upgrade.php
mod/assign/tests/locallib_test.php
mod/assignment/db/upgrade.php
mod/book/db/upgrade.php
mod/chat/db/upgrade.php
mod/choice/db/upgrade.php
mod/data/db/upgrade.php
mod/feedback/db/upgrade.php
mod/folder/db/upgrade.php
mod/forum/db/upgrade.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/tests/behat/no_groups_in_course.feature
mod/forum/tests/mail_test.php
mod/glossary/db/upgrade.php
mod/imscp/db/upgrade.php
mod/label/db/upgrade.php
mod/lesson/db/upgrade.php
mod/lti/db/upgrade.php
mod/page/db/upgrade.php
mod/quiz/classes/question/bank/add_action_column.php
mod/quiz/db/upgrade.php
mod/quiz/lang/en/quiz.php
mod/quiz/report/overview/db/upgrade.php
mod/quiz/report/statistics/db/upgrade.php
mod/quiz/tests/behat/editing_add_from_question_bank.feature
mod/quiz/tests/quiz_question_bank_view_test.php [new file with mode: 0644]
mod/resource/db/upgrade.php
mod/scorm/db/upgrade.php
mod/survey/db/upgrade.php
mod/url/db/upgrade.php
mod/wiki/db/upgrade.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
portfolio/boxnet/db/upgrade.php
portfolio/googledocs/db/upgrade.php
portfolio/mahara/lang/en/portfolio_mahara.php
portfolio/picasa/db/upgrade.php
privacy/classes/local/request/moodle_content_writer.php
question/behaviour/manualgraded/db/upgrade.php
question/classes/bank/delete_action_column.php
question/classes/bank/preview_action_column.php
question/engine/bank.php
question/tests/bank_view_test.php
question/type/calculated/db/upgrade.php
question/type/ddimageortext/amd/build/question.min.js
question/type/ddimageortext/amd/src/question.js
question/type/ddmarker/db/upgrade.php
question/type/ddwtos/amd/build/ddwtos.min.js
question/type/ddwtos/amd/src/ddwtos.js
question/type/ddwtos/tests/behat/preview.feature
question/type/ddwtos/tests/helper.php
question/type/essay/db/upgrade.php
question/type/match/db/upgrade.php
question/type/multianswer/db/upgrade.php
question/type/multichoice/db/upgrade.php
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/performance/lang/en/report_performance.php
report/security/lang/en/report_security.php
repository/boxnet/db/upgrade.php
repository/dropbox/db/upgrade.php
repository/googledocs/db/upgrade.php
repository/picasa/db/upgrade.php
tag/tests/external_test.php
theme/boost/scss/moodle/forms.scss
theme/boost/style/moodle.css
theme/boost/templates/core_form/element-template.mustache
theme/boost/upgrade.txt
theme/bootstrapbase/templates/core_message/message_drawer_view_conversation_header_content_type_private.mustache
theme/more/db/upgrade.php
userpix/index.php
version.php

index aa0db16..ec1b68c 100644 (file)
@@ -14,7 +14,7 @@ language: php
 php:
     # We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
     - 7.2
-    - 7.0
+    - 7.1
 
 addons:
   postgresql: "9.6"
@@ -63,7 +63,7 @@ matrix:
         # Exclude it on all versions except for 7.2
 
         - env: DB=mysqli   TASK=PHPUNIT
-          php: 7.0
+          php: 7.1
 
 cache:
     directories:
index 40e66be..ca20d0c 100644 (file)
       </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
+  <MOODLE version="3.7" requires="3.2">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mariadb" version="5.5.31" />
+      <VENDOR name="mysql" version="5.6" />
+      <VENDOR name="postgres" version="9.4" />
+      <VENDOR name="mssql" version="10.0" />
+      <VENDOR name="oracle" version="11.2" />
+    </DATABASE>
+    <PHP version="7.1.0" level="required">
+    </PHP>
+    <PCREUNICODE level="optional">
+      <FEEDBACK>
+        <ON_CHECK message="pcreunicodewarning" />
+      </FEEDBACK>
+    </PCREUNICODE>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="opensslrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zlib" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="gdrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlreader" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="intlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="hash" level="required"/>
+      <PHP_EXTENSION name="fileinfo" level="required"/>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="96M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="opcache.enable" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opcacherecommended" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+    <CUSTOM_CHECKS>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbstorageengine" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="quizattemptsupgradedmessage" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="slashargumentswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unsupporteddbtablerowformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unoconvwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbfileformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbfilepertable" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddblargeprefix" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="ishttpswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="incompleteunicodesupport" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="sixtyfourbitswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+    </CUSTOM_CHECKS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index db26385..a108b42 100644 (file)
@@ -21,8 +21,28 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
         0)
     );
 
-    $options = array(DAYSECS=>new lang_string('secondstotime86400'), WEEKSECS=>new lang_string('secondstotime604800'), 2620800=>new lang_string('nummonths', 'moodle', 1), 15724800=>new lang_string('nummonths', 'moodle', 6),0=>new lang_string('never'));
-    $optionalsubsystems->add(new admin_setting_configselect('messagingdeletereadnotificationsdelay', new lang_string('messagingdeletereadnotificationsdelay', 'admin'), new lang_string('configmessagingdeletereadnotificationsdelay', 'admin'), 604800, $options));
+    $options = array(
+        DAYSECS => new lang_string('secondstotime86400'),
+        WEEKSECS => new lang_string('secondstotime604800'),
+        2620800 => new lang_string('nummonths', 'moodle', 1),
+        7862400 => new lang_string('nummonths', 'moodle', 3),
+        15724800 => new lang_string('nummonths', 'moodle', 6),
+        0 => new lang_string('never')
+    );
+    $optionalsubsystems->add(new admin_setting_configselect(
+        'messagingdeletereadnotificationsdelay',
+        new lang_string('messagingdeletereadnotificationsdelay', 'admin'),
+        new lang_string('configmessagingdeletereadnotificationsdelay', 'admin'),
+        604800,
+        $options)
+    );
+    $optionalsubsystems->add(new admin_setting_configselect(
+        'messagingdeleteallnotificationsdelay',
+        new lang_string('messagingdeleteallnotificationsdelay', 'admin'),
+        new lang_string('configmessagingdeleteallnotificationsdelay', 'admin'),
+        2620800,
+        $options)
+    );
 
     $optionalsubsystems->add(new admin_setting_configcheckbox('messagingallowemailoverride', new lang_string('messagingallowemailoverride', 'admin'), new lang_string('configmessagingallowemailoverride','admin'), 0));
 
index 5849311..53fa80a 100644 (file)
@@ -29,9 +29,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_customlang_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 48f7d3c..1fba508 100644 (file)
Binary files a/admin/tool/dataprivacy/amd/build/effective_retention_period.min.js and b/admin/tool/dataprivacy/amd/build/effective_retention_period.min.js differ
index 8d587ca..7e3a102 100644 (file)
@@ -26,8 +26,7 @@ define(['jquery'],
 
         var SELECTORS = {
             PURPOSE_SELECT: '#id_purposeid',
-            RETENTION_FIELD_BOOST: '#id_error_retention_current',
-            RETENTION_FIELD_CLEAN: '#fitem_id_retention_current [data-fieldtype=static]',
+            RETENTION_FIELD: '#fitem_id_retention_current [data-fieldtype=static]',
         };
 
         /**
@@ -65,18 +64,7 @@ define(['jquery'],
             $(SELECTORS.PURPOSE_SELECT).on('change', function(ev) {
                 var selected = $(ev.currentTarget).val();
                 var selectedPurpose = this.purposeRetentionPeriods[selected];
-
-                var cleanSelector = $(SELECTORS.RETENTION_FIELD_CLEAN);
-                if (cleanSelector.length > 0) {
-                    cleanSelector.text(selectedPurpose);
-                } else {
-                    var boostSelector = $(SELECTORS.RETENTION_FIELD_BOOST);
-                    var retentionField = boostSelector.siblings();
-                    if (retentionField.length > 0) {
-                        retentionField.text(selectedPurpose);
-                    }
-                }
-
+                $(SELECTORS.RETENTION_FIELD).text(selectedPurpose);
             }.bind(this));
         };
 
index 70a76cc..bbf1961 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_log_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 0804ce8..04137ab 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_logstore_database_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 14946e8..d6cd26d 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_logstore_standard_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index ee22b83..f8579c9 100644 (file)
@@ -33,26 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_monitor_upgrade($oldversion) {
     global $CFG, $DB;
 
-    $dbman = $DB->get_manager();
-
-    if ($oldversion < 2016052305) {
-
-        // Define field inactivedate to be added to tool_monitor_subscriptions.
-        $table = new xmldb_table('tool_monitor_subscriptions');
-        $field = new xmldb_field('inactivedate', XMLDB_TYPE_INTEGER, '10', null, true, null, 0, 'lastnotificationsent');
-
-        // Conditionally launch add field inactivedate.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Monitor savepoint reached.
-        upgrade_plugin_savepoint(true, 2016052305, 'tool', 'monitor');
-    }
-
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017021300) {
 
         // Delete "orphaned" subscriptions.
index b1172e0..8ea9c70 100644 (file)
@@ -34,7 +34,7 @@ $string['disabled'] = 'Disabled';
 $string['disabled_help'] = 'Disabled scheduled tasks are not executed from cron, however they can still be executed manually via the CLI tool.';
 $string['edittaskschedule'] = 'Edit task schedule: {$a}';
 $string['enablerunnow'] = 'Allow \'Run now\' for scheduled tasks';
-$string['enablerunnow_desc'] = 'Allows administrators to run a single scheduled task immediately, rather than waiting for it to run as scheduled. The task runs on the web server, so some sites may wish to disable this feature to avoid potential performance issues.';
+$string['enablerunnow_desc'] = 'Allows administrators to run a single scheduled task immediately, rather than waiting for it to run as scheduled. The feature requires \'Path to PHP CLI\' (pathtophp) to be set in System paths. The task runs on the web server, so you may wish to disable this feature to avoid potential performance issues.';
 $string['faildelay'] = 'Fail delay';
 $string['lastruntime'] = 'Last run';
 $string['nextruntime'] = 'Next run';
index 9bdf9ee..a8582de 100644 (file)
@@ -35,9 +35,6 @@ use tool_usertours\manager;
 function xmldb_tool_usertours_upgrade($oldversion) {
     global $CFG, $DB;
 
-    // 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.
 
index 9dd0838..d531a86 100644 (file)
@@ -215,8 +215,8 @@ $string['yesmissingindexesfound'] = '<p>Some missing indexes have been found in
 <p>After doing that, it\'s highly recommended to execute this utility again to check that no more missing indexes are found.</p>';
 $string['yeswrongdefaultsfound'] = '<p>Some inconsistent defaults have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to fix them all. Remember to backup your data first!</p>
 <p>After doing that, it\'s highly recommended to execute this utility again to check that no more inconsistent defaults are found.</p>';
-$string['yeswrongintsfound'] = '<p>Some wrong integers have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to create all them. Remember to backup your data first!</p>
-<p>After doing that, it\'s highly recommended to execute this utility again to check that no more wrong integers are found.</p>';
+$string['yeswrongintsfound'] = '<p>Some wrong integers have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to fix them. Remember to backup your data first!</p>
+<p>After fixing them, it is highly recommended to execute this utility again to check that no more wrong integers are found.</p>';
 $string['yeswrongoraclesemanticsfound'] = '<p>Some Oracle columns using BYTE semantics have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to create all them. Remember to backup your data first!</p>
 <p>After doing that, it\'s highly recommended to execute this utility again to check that no more wrong semantics are found.</p>';
 $string['privacy:metadata'] = 'The XMLDB editor plugin does not store any personal data.';
index 469661c..4df297c 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_cas_upgrade($oldversion) {
     global $CFG;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017020700) {
         // Convert info in config plugins from auth/cas to auth_cas.
         upgrade_fix_config_auth_plugin_names('cas');
index 7955ffe..f860220 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_db_upgrade($oldversion) {
     global $CFG, $DB;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017032800) {
         // Convert info in config plugins from auth/db to auth_db
         upgrade_fix_config_auth_plugin_names('db');
index f00705b..f636d6f 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_email_upgrade($oldversion) {
     global $CFG, $DB;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017020700) {
         // Convert info in config plugins from auth/email to auth_email.
         upgrade_fix_config_auth_plugin_names('email');
index 0a4c344..6c57255 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_ldap_upgrade($oldversion) {
     global $CFG;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017020700) {
         // Convert info in config plugins from auth/ldap to auth_ldap.
         upgrade_fix_config_auth_plugin_names('ldap');
index 6d92020..13f87ce 100644 (file)
@@ -48,16 +48,16 @@ $string['auth_ldap_expiration_warning_key'] = 'Expiry warning';
 $string['auth_ldap_expireattr_desc'] = 'Optional: Overrides the LDAP attribute that stores password expiry time.';
 $string['auth_ldap_expireattr_key'] = 'Expiry attribute';
 $string['auth_ldapextrafields'] = 'These fields are optional.  You can choose to pre-fill some Moodle user fields with information from the <b>LDAP fields</b> that you specify here. <p>If you leave these fields blank, then nothing will be transferred from LDAP and Moodle defaults will be used instead.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
-$string['auth_ldap_graceattr_desc'] = 'Optional: Overrides  gracelogin attribute';
+$string['auth_ldap_graceattr_desc'] = 'Optional: Overrides grace login attribute';
 $string['auth_ldap_gracelogin_key'] = 'Grace login attribute';
-$string['auth_ldap_gracelogins_desc'] = 'Enable LDAP gracelogin support. After password has expired user can login until gracelogin count is 0. Enabling this setting displays grace login message if password is expired.';
+$string['auth_ldap_gracelogins_desc'] = 'Enable LDAP grace login support. After password has expired, user can log in until grace login count is 0. Enabling this setting displays grace login message if password has expired.';
 $string['auth_ldap_gracelogins_key'] = 'Grace logins';
 $string['auth_ldap_groupecreators'] = 'List of groups or contexts whose members are allowed to create groups. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
 $string['auth_ldap_groupecreators_key'] = 'Group creators';
 $string['auth_ldap_host_url'] = 'Specify LDAP host in URL-form like \'ldap://ldap.myorg.com/\' or \'ldaps://ldap.myorg.com/\'. Separate multiple servers with \';\' to get failover support.';
 $string['auth_ldap_host_url_key'] = 'Host URL';
 $string['auth_ldap_changepasswordurl_key'] = 'Password-change URL';
-$string['auth_ldap_ldap_encoding'] = 'Specify encoding used by LDAP server. Most probably utf-8, MS AD v2 uses default platform encoding such as cp1252, cp1250, etc.';
+$string['auth_ldap_ldap_encoding'] = 'Encoding used by the LDAP server, most likely utf-8. If LDAP v2 is selected, Active Directory uses its configured encoding, such as cp1252 or cp1250.';
 $string['auth_ldap_ldap_encoding_key'] = 'LDAP encoding';
 $string['auth_ldap_login_settings'] = 'Login settings';
 $string['auth_ldap_memberattribute'] = 'Optional: Overrides user member attribute, when users belongs to a group. Usually \'member\'';
@@ -121,7 +121,7 @@ $string['didntfindexpiretime'] = 'password_expire() didn\'t find expiration time
 $string['didntgetusersfromldap'] = "Did not get any users from LDAP -- error? -- exiting\n";
 $string['gotcountrecordsfromldap'] = "Got {\$a} records from LDAP\n";
 $string['ldapnotconfigured'] = 'The LDAP host url is currently not configured';
-$string['morethanoneuser'] = 'Strange! More than one user record found in ldap. Only using the first one.';
+$string['morethanoneuser'] = 'More than one user record found in LDAP. Using only the first one.';
 $string['needbcmath'] = 'You need the BCMath extension to use expired password checking with Active Directory.';
 $string['needmbstring'] = 'You need the mbstring extension to change passwords in Active Directory';
 $string['nodnforusername'] = 'Error in user_update_password(). No DN for: {$a->username}';
@@ -152,7 +152,7 @@ $string['updateremfailamb'] = 'Failed to update LDAP with ambiguous field {$a->k
 $string['updateremfailfield'] = 'Failed to update LDAP with non-existent field (\'{$a->ldapkey}\'). Key ({$a->key}) - old Moodle value: \'{$a->ouvalue}\' new value: \'{$a->nuvalue}\'';
 $string['updatepasserror'] = 'Error in user_update_password(). Error code: {$a->errno}; Error string: {$a->errstring}';
 $string['updatepasserrorexpire'] = 'Error in user_update_password() when reading password expiry time. Error code: {$a->errno}; Error string: {$a->errstring}';
-$string['updatepasserrorexpiregrace'] = 'Error in user_update_password() when modifying expirationtime and/or gracelogins. Error code: {$a->errno}; Error string: {$a->errstring}';
+$string['updatepasserrorexpiregrace'] = 'Error in user_update_password() when modifying expiry time and/or grace logins. Error code: {$a->errno}; Error string: {$a->errstring}';
 $string['updateusernotfound'] = 'Could not find user while updating externally. Details follow: search base: \'{$a->userdn}\'; search filter: \'(objectClass=*)\'; search attributes: {$a->attribs}';
 $string['user_activatenotsupportusertype'] = 'auth: ldap user_activate() does not support selected usertype: {$a}';
 $string['user_disablenotsupportusertype'] = 'auth: ldap user_disable() does not support selected usertype: {$a}';
index d691552..9209e9d 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_manual_upgrade($oldversion) {
     global $CFG;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017020700) {
         // Convert info in config plugins from auth/manual to auth_manual.
         upgrade_fix_config_auth_plugin_names('manual');
index 7d4d88d..be29bcd 100644 (file)
@@ -32,8 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_mnet_upgrade($oldversion) {
     global $CFG;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
     if ($oldversion < 2017020700) {
         // Convert info in config plugins from auth/mnet to auth_mnet.
         upgrade_fix_config_auth_plugin_names('mnet');
index 06f36e7..a8c7ed3 100644 (file)
@@ -91,11 +91,11 @@ $string['privacy:metadata:mnet_log:remoteid'] = 'Remote ID of the user who carri
 $string['privacy:metadata:mnet_log:time'] = 'Time when the action occurred.';
 $string['privacy:metadata:mnet_log:url'] = 'Remote system URL where the action occurred.';
 $string['privacy:metadata:mnet_log:userid'] = 'Local ID of the user who carried out the action in the remote system.';
-$string['privacy:metadata:mnet_session'] = 'The details of each MNet user session in a remote system is stored temporarily.';
+$string['privacy:metadata:mnet_session'] = 'The details of each MNet user session in a remote system. The data is stored temporarily.';
 $string['privacy:metadata:mnet_session:expires'] = 'Time when the session expires.';
 $string['privacy:metadata:mnet_session:mnethostid'] = 'Remote system MNet ID.';
 $string['privacy:metadata:mnet_session:token'] = 'Unique session identifier';
-$string['privacy:metadata:mnet_session:useragent'] = 'String denoting the user agent being which is accessing the page.';
+$string['privacy:metadata:mnet_session:useragent'] = 'User agent used to access the remote system';
 $string['privacy:metadata:mnet_session:userid'] = 'ID of the user jumping to remote system.';
 $string['privacy:metadata:mnet_session:username'] = 'Username of the user jumping to remote system.';
 $string['unknownhost'] = 'Unknown host';
\ No newline at end of file
index 596740b..2d5fc97 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_none_upgrade($oldversion) {
     global $CFG, $DB;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017020700) {
         // Convert info in config plugins from auth/none to auth_none.
         upgrade_fix_config_auth_plugin_names('none');
index 19cc83c..1ecfca8 100644 (file)
@@ -305,18 +305,54 @@ class auth extends \auth_plugin_base {
      * Update user data according to data sent by authorization server.
      *
      * @param array $externaldata data from authorization server
-     * @param int $userid ID of the user to update
-     * @return stdClass The updated user record
+     * @param stdClass $userdata Current data of the user to be updated
+     * @return stdClass The updated user record, or the existing one if there's nothing to be updated.
      */
-    private function update_user(array $externaldata, int $userid) {
+    private function update_user(array $externaldata, $userdata) {
         $user = (object) [
-            'id' => $userid,
+            'id' => $userdata->id,
         ];
+
+        // We can only update if the default authentication type of the user is set to OAuth2 as well. Otherwise, we might mess
+        // up the user data of other users that use different authentication mechanisms (e.g. linked logins).
+        if ($userdata->auth !== $this->authtype) {
+            return $userdata;
+        }
+
+        // Go through each field from the external data.
         foreach ($externaldata as $fieldname => $value) {
-            $user->$fieldname = $value;
+            if (!in_array($fieldname, $this->userfields)) {
+                // Skip if this field doesn't belong to the list of fields that can be synced with the OAuth2 issuer.
+                continue;
+            }
+
+            if (!property_exists($userdata, $fieldname)) {
+                // Just in case this field is on the list, but not part of the user data. This shouldn't happen though.
+                continue;
+            }
+
+            // Get the old value.
+            $oldvalue = (string)$userdata->$fieldname;
+
+            // Get the lock configuration of the field.
+            $lockvalue = $this->config->{'field_lock_' . $fieldname};
+
+            // We should update fields that meet the following criteria:
+            // - Lock value set to 'unlocked'; or 'unlockedifempty', given the current value is empty.
+            // - The value has changed.
+            if ($lockvalue === 'unlocked' || ($lockvalue === 'unlockedifempty' && empty($oldvalue))) {
+                $value = (string)$value;
+                if ($oldvalue !== $value) {
+                    $user->$fieldname = $value;
+                }
+            }
         }
+        // Update the user data.
         user_update_user($user, false);
 
+        // Save user profile data.
+        profile_save_data($user);
+
         // Refresh user for $USER variable.
         return get_complete_user_data('id', $user->id);
     }
@@ -439,7 +475,7 @@ class auth extends \auth_plugin_base {
                 redirect(new moodle_url('/login/index.php'));
             } else if ($mappeduser && $mappeduser->confirmed) {
                 // Update user fields.
-                $userinfo = $this->update_user($userinfo, $mappeduser->id);
+                $userinfo = $this->update_user($userinfo, $mappeduser);
                 $userwasmapped = true;
             } else {
                 // Trigger login failed event.
@@ -497,7 +533,7 @@ class auth extends \auth_plugin_base {
                     exit();
                 } else {
                     \auth_oauth2\api::link_login($userinfo, $issuer, $moodleuser->id, true);
-                    $userinfo = $this->update_user($userinfo, $moodleuser->id);
+                    $userinfo = $this->update_user($userinfo, $moodleuser);
                     // No redirect, we will complete this login.
                 }
 
@@ -562,8 +598,7 @@ class auth extends \auth_plugin_base {
                 } else {
                     // Create a new confirmed account.
                     $newuser = \auth_oauth2\api::create_new_confirmed_account($userinfo, $issuer);
-                    // Update new user's fields.
-                    $userinfo = $this->update_user($userinfo, $newuser->id);
+                    $userinfo = get_complete_user_data('id', $newuser->id);
                     // No redirect, we will complete this login.
                 }
             }
index 8d0a6a6..7582cc9 100644 (file)
@@ -35,9 +35,6 @@ function xmldb_auth_oauth2_upgrade($oldversion) {
 
     $dbman = $DB->get_manager();
 
-    // 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.
 
index 416ac20..640a023 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_shibboleth_upgrade($oldversion) {
     global $CFG, $DB;
 
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017020700) {
         // Convert info in config plugins from auth/shibboleth to auth_shibboleth.
         upgrade_fix_config_auth_plugin_names('shibboleth');
index e1b5195..391b556 100644 (file)
@@ -50,9 +50,6 @@ class edit_backpack_form extends moodleform {
         $mform->addElement('hidden', 'userid', $USER->id);
         $mform->setType('userid', PARAM_INT);
 
-        $mform->addElement('hidden', 'backpackurl', BADGE_BACKPACKURL);
-        $mform->setType('backpackurl', PARAM_URL);
-
         if (isset($this->_customdata['email'])) {
             // Email will be passed in when we're in the process of verifying the user's email address,
             // so set the connection status, lock the email field, and provide options to resend the verification
@@ -94,7 +91,7 @@ class edit_backpack_form extends moodleform {
         // We don't need to verify the email address if we're clearing a pending email verification attempt.
         if (!isset($data['revertbutton'])) {
             $check = new stdClass();
-            $check->backpackurl = $data['backpackurl'];
+            $check->backpackurl = BADGE_BACKPACKURL;
             $check->email = $data['email'];
 
             $bp = new OpenBadgesBackpackHandler($check);
index 433e0c7..6948646 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_badges_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index d1cb144..90912a5 100644 (file)
@@ -26,6 +26,6 @@
 $string['pluginname'] = 'Latest badges';
 $string['numbadgestodisplay'] = 'Number of latest badges to display';
 $string['nothingtodisplay'] = 'You have no badges to display';
-$string['badges:addinstance'] = 'Add a new My latest badges block';
-$string['badges:myaddinstance'] = 'Add a new My latest badges block to Dashboard';
+$string['badges:addinstance'] = 'Add a new Latest badges block';
+$string['badges:myaddinstance'] = 'Add a new Latest badges block to Dashboard';
 $string['privacy:metadata'] = 'The Latest badges block only shows data stored in other locations.';
index 913ccca..3205127 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_calendar_month_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index b1e95d6..14100c8 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_calendar_upcoming_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index ecfdca9..0bdc51e 100644 (file)
@@ -46,9 +46,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_community_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 770a27b..8a9ec89 100644 (file)
@@ -48,9 +48,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_completionstatus_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index 1cd9244..54538b9 100644 (file)
@@ -24,7 +24,7 @@
 
 $string['adminview'] = 'Admin view';
 $string['allcourses'] = 'Admin user sees all courses';
-$string['configadminview'] = 'What should the admin see in the course list block?';
+$string['configadminview'] = 'Whether to display all courses in the Courses block, or only courses that the admin is enrolled in.';
 $string['confighideallcourseslink'] = 'Remove the \'All courses\' link under the list of courses. (This setting does not affect the admin view.)';
 $string['course_list:addinstance'] = 'Add a new courses block';
 $string['course_list:myaddinstance'] = 'Add a new courses block to Dashboard';
index f3d591b..fb0cbae 100644 (file)
@@ -48,9 +48,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_course_summary_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index 221dd38..4949b3f 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_html_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index fcd820a..97b1063 100644 (file)
@@ -55,9 +55,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_navigation_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index a9ef40e..60b035e 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_quiz_results_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index 148cd37..77e6f61 100644 (file)
@@ -47,9 +47,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_recent_activity_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index ee46406..878f198 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_rss_client_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 19a9ca5..afc0bab 100644 (file)
@@ -78,7 +78,7 @@ $string['rss_client:createprivatefeeds'] = 'Create private RSS feeds';
 $string['rss_client:createsharedfeeds'] = 'Create shared RSS feeds';
 $string['rss_client:manageanyfeeds'] = 'Manage any RSS feeds';
 $string['rss_client:manageownfeeds'] = 'Manage own RSS feeds';
-$string['rss_client:myaddinstance'] = 'Add a new RSS feeds block to Dashboard';
+$string['rss_client:myaddinstance'] = 'Add a new Remote RSS feeds block to Dashboard';
 $string['seeallfeeds'] = 'See all feeds';
 $string['sharedfeed'] = 'Shared feed';
 $string['shownumentrieslabel'] = 'Max number entries to show per block.';
index a8e7bfd..0a8e2b0 100644 (file)
@@ -49,9 +49,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_section_links_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index 34a9814..9b8d87b 100644 (file)
@@ -48,9 +48,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_selfcompletion_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index 3e93f8b..7b8618d 100644 (file)
@@ -55,9 +55,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_settings_upgrade($oldversion, $block) {
     global $CFG;
 
-    // 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.
 
index e697808..bbcf079 100644 (file)
@@ -77,7 +77,7 @@ servername:port:weight
 If *Enable clustered servers* is enabled below, there must be only one server listed here. This would usually be a name that always resolves to the local machine, like 127.0.0.1 or localhost.';
 $string['serversclusterinvalid'] = 'Exactly one server is required when clustering is enabled.';
 $string['setservers'] = 'Set Servers';
-$string['setservers_help'] = 'This is the list of servers that will updated when data is modified in the cache. Generally the fully qualified name of each server in the pool.
+$string['setservers_help'] = 'This is the list of servers that will be updated when data is modified in the cache. Generally the fully qualified name of each server in the pool.
 It **must** include the server listed in *Servers* above, even if by a different hostname.
 Servers should be defined one per line and consist of a server address and optionally a port.
 If no port is provided then the default port (11211) is used.
index 5b526cc..c5659ea 100644 (file)
@@ -7,7 +7,7 @@
     "require-dev": {
         "phpunit/phpunit": "6.5.*",
         "phpunit/dbUnit": "3.0.*",
-        "moodlehq/behat-extension": "3.36.0",
+        "moodlehq/behat-extension": "3.37.0",
         "mikey179/vfsStream": "^1.6"
     }
 }
index e8aaa2f..7179371 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "956ce0b653b805efb6a9a483f8c9a847",
+    "content-hash": "fafc7b770f55956d32d202645be66abf",
     "packages": [],
     "packages-dev": [
         {
         },
         {
             "name": "doctrine/instantiator",
-            "version": "1.0.5",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/instantiator.git",
-                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+                "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
-                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
+                "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3,<8.0-DEV"
+                "php": "^7.1"
             },
             "require-dev": {
                 "athletic/athletic": "~0.1.8",
                 "ext-pdo": "*",
                 "ext-phar": "*",
-                "phpunit/phpunit": "~4.0",
-                "squizlabs/php_codesniffer": "~2.0"
+                "phpunit/phpunit": "^6.2.3",
+                "squizlabs/php_codesniffer": "^3.0.2"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.2.x-dev"
                 }
             },
             "autoload": {
                 "constructor",
                 "instantiate"
             ],
-            "time": "2015-06-14T21:17:01+00:00"
+            "time": "2017-07-22T11:58:36+00:00"
         },
         {
             "name": "fabpot/goutte",
-            "version": "v3.2.2",
+            "version": "v3.2.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FriendsOfPHP/Goutte.git",
-                "reference": "395f61d7c2e15a813839769553a4de16fa3b3c96"
+                "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/395f61d7c2e15a813839769553a4de16fa3b3c96",
-                "reference": "395f61d7c2e15a813839769553a4de16fa3b3c96",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/3f0eaf0a40181359470651f1565b3e07e3dd31b8",
+                "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8",
                 "shasum": ""
             },
             "require": {
             "keywords": [
                 "scraper"
             ],
-            "time": "2017-11-19T08:45:40+00:00"
+            "time": "2018-06-29T15:13:57+00:00"
         },
         {
             "name": "guzzlehttp/guzzle",
         },
         {
             "name": "guzzlehttp/psr7",
-            "version": "1.4.2",
+            "version": "1.5.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/psr7.git",
-                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
+                "reference": "9f83dded91781a01c63574e387eaa769be769115"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
-                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
+                "reference": "9f83dded91781a01c63574e387eaa769be769115",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.4.0",
-                "psr/http-message": "~1.0"
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5"
             },
             "provide": {
                 "psr/http-message-implementation": "1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.0"
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.4-dev"
+                    "dev-master": "1.5-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "http",
                 "message",
+                "psr-7",
                 "request",
                 "response",
                 "stream",
                 "uri",
                 "url"
             ],
-            "time": "2017-03-20T17:10:46+00:00"
+            "time": "2018-12-04T20:46:45+00:00"
         },
         {
             "name": "instaclick/php-webdriver",
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.36.0",
+            "version": "v3.37.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
         },
         {
             "name": "myclabs/deep-copy",
-            "version": "1.7.0",
+            "version": "1.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
+                "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
-                "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8",
+                "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.6 || ^7.0"
+                "php": "^7.1"
+            },
+            "replace": {
+                "myclabs/deep-copy": "self.version"
             },
             "require-dev": {
                 "doctrine/collections": "^1.0",
                 "doctrine/common": "^2.6",
-                "phpunit/phpunit": "^4.1"
+                "phpunit/phpunit": "^7.1"
             },
             "type": "library",
             "autoload": {
                 "object",
                 "object graph"
             ],
-            "time": "2017-10-19T19:58:43+00:00"
+            "time": "2018-06-11T23:09:50+00:00"
         },
         {
             "name": "phar-io/manifest",
         },
         {
             "name": "phpspec/prophecy",
-            "version": "1.7.6",
+            "version": "1.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712"
+                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
-                "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
+                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "phpspec/phpspec": "^2.5|^3.2",
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.7.x-dev"
+                    "dev-master": "1.8.x-dev"
                 }
             },
             "autoload": {
                 "spy",
                 "stub"
             ],
-            "time": "2018-04-18T13:57:24+00:00"
+            "time": "2018-08-05T17:53:17+00:00"
         },
         {
             "name": "phpunit/dbunit",
                 "testing",
                 "xunit"
             ],
+            "abandoned": true,
             "time": "2018-01-23T13:32:26+00:00"
         },
         {
         },
         {
             "name": "phpunit/phpunit",
-            "version": "6.5.8",
+            "version": "6.5.13",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b"
+                "reference": "0973426fb012359b2f18d3bd1e90ef1172839693"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b",
-                "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693",
+                "reference": "0973426fb012359b2f18d3bd1e90ef1172839693",
                 "shasum": ""
             },
             "require": {
                 "phpunit/php-file-iterator": "^1.4.3",
                 "phpunit/php-text-template": "^1.2.1",
                 "phpunit/php-timer": "^1.0.9",
-                "phpunit/phpunit-mock-objects": "^5.0.5",
+                "phpunit/phpunit-mock-objects": "^5.0.9",
                 "sebastian/comparator": "^2.1",
                 "sebastian/diff": "^2.0",
                 "sebastian/environment": "^3.1",
                 "testing",
                 "xunit"
             ],
-            "time": "2018-04-10T11:38:34+00:00"
+            "time": "2018-09-08T15:10:43+00:00"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
-            "version": "5.0.7",
+            "version": "5.0.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce"
+                "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3eaf040f20154d27d6da59ca2c6e28ac8fd56dce",
-                "reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f",
+                "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f",
                 "shasum": ""
             },
             "require": {
                 "phpunit/phpunit": "<6.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.5"
+                "phpunit/phpunit": "^6.5.11"
             },
             "suggest": {
                 "ext-soap": "*"
                 "mock",
                 "xunit"
             ],
-            "time": "2018-05-29T13:50:43+00:00"
+            "time": "2018-08-09T05:50:03+00:00"
         },
         {
             "name": "psr/container",
         },
         {
             "name": "psr/log",
-            "version": "1.0.2",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
+                "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
-                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
+                "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
                 "shasum": ""
             },
             "require": {
                 "psr",
                 "psr-3"
             ],
-            "time": "2016-10-10T12:19:37+00:00"
+            "time": "2018-11-20T15:27:04+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "2.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
+                "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~3.7.0",
+                "satooshi/php-coveralls": ">=1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "time": "2016-02-11T07:05:27+00:00"
         },
         {
             "name": "sebastian/code-unit-reverse-lookup",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v3.4.11",
+            "version": "v4.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "840bb6f0d5b3701fd768b68adf7193c2d0f98f79"
+                "reference": "db7e59fec9c82d45e745eb500e6ede2d96f4a6e9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/840bb6f0d5b3701fd768b68adf7193c2d0f98f79",
-                "reference": "840bb6f0d5b3701fd768b68adf7193c2d0f98f79",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/db7e59fec9c82d45e745eb500e6ede2d96f4a6e9",
+                "reference": "db7e59fec9c82d45e745eb500e6ede2d96f4a6e9",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8",
-                "symfony/dom-crawler": "~2.8|~3.0|~4.0"
+                "php": "^7.1.3",
+                "symfony/dom-crawler": "~3.4|~4.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.8|~3.0|~4.0",
-                "symfony/process": "~2.8|~3.0|~4.0"
+                "symfony/css-selector": "~3.4|~4.0",
+                "symfony/process": "~3.4|~4.0"
             },
             "suggest": {
                 "symfony/process": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.4-dev"
+                    "dev-master": "4.2-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2018-03-19T22:32:39+00:00"
+            "time": "2018-11-26T11:49:31+00:00"
         },
         {
             "name": "symfony/class-loader",
-            "version": "v3.4.11",
+            "version": "v3.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/class-loader.git",
-                "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e"
+                "reference": "420458095cf60025eb0841276717e0da7f75e50e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/class-loader/zipball/e63c12699822bb3b667e7216ba07fbcc3a3e203e",
-                "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/420458095cf60025eb0841276717e0da7f75e50e",
+                "reference": "420458095cf60025eb0841276717e0da7f75e50e",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony ClassLoader Component",
             "homepage": "https://symfony.com",
-            "time": "2018-01-03T07:37:34+00:00"
+            "time": "2018-11-11T19:48:54+00:00"
         },
         {
             "name": "symfony/config",
-            "version": "v3.4.11",
+            "version": "v3.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "73e055cf2e6467715f187724a0347ea32079967c"
+                "reference": "8a660daeb65dedbe0b099529f65e61866c055081"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/73e055cf2e6467715f187724a0347ea32079967c",
-                "reference": "73e055cf2e6467715f187724a0347ea32079967c",
+                "url": "https://api.github.com/repos/symfony/config/zipball/8a660daeb65dedbe0b099529f65e61866c055081",
+                "reference": "8a660daeb65dedbe0b099529f65e61866c055081",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2018-05-14T16:49:53+00:00"
+            "time": "2018-11-26T10:17:44+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v3.3.17",
+            "version": "v3.3.18",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.4.11",
+            "version": "v3.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "d2ce52290b648ae33b5301d09bc14ee378612914"
+                "reference": "345b9a48595d1ab9630db791dbc3e721bf0233e8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/d2ce52290b648ae33b5301d09bc14ee378612914",
-                "reference": "d2ce52290b648ae33b5301d09bc14ee378612914",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/345b9a48595d1ab9630db791dbc3e721bf0233e8",
+                "reference": "345b9a48595d1ab9630db791dbc3e721bf0233e8",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2018-05-16T12:49:49+00:00"
+            "time": "2018-11-11T19:48:54+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v3.4.11",
+            "version": "v3.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "b28fd73fefbac341f673f5efd707d539d6a19f68"
+                "reference": "a2233f555ddf55e5600f386fba7781cea1cb82d3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/b28fd73fefbac341f673f5efd707d539d6a19f68",
-                "reference": "b28fd73fefbac341f673f5efd707d539d6a19f68",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/a2233f555ddf55e5600f386fba7781cea1cb82d3",
+                "reference": "a2233f555ddf55e5600f386fba7781cea1cb82d3",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2018-05-16T14:03:39+00:00"
+            "time": "2018-11-27T12:43:10+00:00"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v3.3.17",
+            "version": "v3.3.18",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v3.4.11",
+            "version": "v4.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "201b210fafcdd193c1e45b2994bf7133fb6263e8"
+                "reference": "7438a32108fdd555295f443605d6de2cce473159"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/201b210fafcdd193c1e45b2994bf7133fb6263e8",
-                "reference": "201b210fafcdd193c1e45b2994bf7133fb6263e8",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7438a32108fdd555295f443605d6de2cce473159",
+                "reference": "7438a32108fdd555295f443605d6de2cce473159",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8",
+                "php": "^7.1.3",
                 "symfony/polyfill-ctype": "~1.8",
                 "symfony/polyfill-mbstring": "~1.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.8|~3.0|~4.0"
+                "symfony/css-selector": "~3.4|~4.0"
             },
             "suggest": {
                 "symfony/css-selector": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.4-dev"
+                    "dev-master": "4.2-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2018-05-01T22:53:27+00:00"
+            "time": "2018-11-26T10:55:26+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v3.4.11",
+            "version": "v3.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8"
+                "reference": "cc35e84adbb15c26ae6868e1efbf705a917be6b5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fdd5abcebd1061ec647089c6c41a07ed60af09f8",
-                "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc35e84adbb15c26ae6868e1efbf705a917be6b5",
+                "reference": "cc35e84adbb15c26ae6868e1efbf705a917be6b5",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2018-04-06T07:35:25+00:00"
+            "time": "2018-11-30T18:07:24+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v3.4.11",
+            "version": "v4.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0"
+                "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0",
-                "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/2f4c8b999b3b7cadb2a69390b01af70886753710",
+                "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5.9|>=7.0.8",
+                "php": "^7.1.3",
                 "symfony/polyfill-ctype": "~1.8"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.4-dev"
+                    "dev-master": "4.2-dev"
                 }
             },
             "autoload": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2018-05-16T08:49:21+00:00"
+            "time": "2018-11-11T19:52:12+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.8.0",
+            "version": "v1.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae"
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
-                "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3"
             },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.8-dev"
+                    "dev-master": "1.9-dev"
                 }
             },
             "autoload": {
                 "polyfill",
                 "portable"
             ],
-            "time": "2018-04-30T19:57:29+00:00"
+            "time": "2018-08-06T14:22:27+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.8.0",
+            "version": "v1.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "3296adf6a6454a050679cde90f95350ad604b171"
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171",
-                "reference": "3296adf6a6454a050679cde90f95350ad604b171",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.8-dev"
+                    "dev-master": "1.9-dev"
                 }
             },
             "autoload": {
                 "portable",
                 "shim"
             ],
-            "time": "2018-04-26T10:06:28+00:00"
+            "time": "2018-09-21T13:07:52+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.41",
+            "version": "v2.8.49",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "713952f2ccbcc8342ecdbe1cb313d3e2da8aad28"
+                "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/713952f2ccbcc8342ecdbe1cb313d3e2da8aad28",
-                "reference": "713952f2ccbcc8342ecdbe1cb313d3e2da8aad28",
+                "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8",
+                "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2018-05-15T21:17:45+00:00"
+            "time": "2018-11-11T11:18:13+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v3.3.17",
+            "version": "v3.3.18",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.3.17",
+            "version": "v3.3.18",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
index ad00ffb..4321d51 100644 (file)
@@ -2425,9 +2425,8 @@ class core_course_renderer extends plugin_renderer_base {
                     if (!empty($mycourseshtml)) {
                         $output .= $this->frontpage_part('skipmycourses', 'frontpage-course-list',
                             get_string('mycourses'), $mycourseshtml);
-                        break;
                     }
-                    // No "break" here. If there are no enrolled courses - continue to 'Available courses'.
+                    break;
 
                 case FRONTPAGEALLCOURSELIST:
                     $availablecourseshtml = $this->frontpage_available_courses();
index 9ba3261..ca2733c 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_database_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index adc61cb..c286910 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_flatfile_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index e86cfff..a4f9d25 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_guest_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 857eb62..d89f296 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_imsenterprise_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 7b4db43..a346a99 100644 (file)
@@ -103,7 +103,7 @@ $string['phpldap_noextension'] = '<em>The PHP LDAP module does not seem to be pr
 $string['pluginname'] = 'LDAP enrolments';
 $string['pluginname_desc'] = '<p>You can use an LDAP server to control your enrolments. It is assumed your LDAP tree contains groups that map to the courses, and that each of those groups/courses will have membership entries to map to students.</p><p>It is assumed that courses are defined as groups in LDAP, with each group having multiple membership fields (<em>member</em> or <em>memberUid</em>) that contain a uniqueidentification of the user.</p><p>To use LDAP enrolment, your users <strong>must</strong> to have a valid  idnumber field. The LDAP groups must have that idnumber in the member fields for a user to be enrolled in the course. This will usually work well if you are already using LDAP Authentication.</p><p>Enrolments will be updated when the user logs in. You can also run a script to keep enrolments in synch. Look in <em>enrol/ldap/cli/sync.php</em>.</p><p>This plugin can also be set to automatically create new courses when new groups appear in LDAP.</p>';
 $string['pluginnotenabled'] = 'Plugin not enabled!';
-$string['role_mapping'] = '<p>For each role that you want to assign from LDAP, you need to specify the list of contexts where the role courses\'s groups are located. Separate different contexts with \';\'.</p><p>You also need to specify the attribute your LDAP server uses to hold the members of a group. Usually \'member\' or \'memberUid\'</p>';
+$string['role_mapping'] = '<p>For each role, you need to specify all LDAP contexts where the groups that represent the courses are located. Separate different contexts with a semicolon (;).</p><p>You also need to specify the attribute your LDAP server uses to hold the members of a group. This is usually \'member\' or \'memberUid\'.</p>';
 $string['role_mapping_attribute'] = 'LDAP member attribute for {$a}';
 $string['role_mapping_context'] = 'LDAP contexts for {$a}';
 $string['role_mapping_key'] = 'Map roles from LDAP ';
index cccd6ae..7b5736b 100644 (file)
@@ -41,210 +41,6 @@ function xmldb_enrol_lti_upgrade($oldversion) {
 
     $dbman = $DB->get_manager();
 
-    if ($oldversion < 2016052303) {
-
-        // Define table enrol_lti_lti2_consumer to be created.
-        $table = new xmldb_table('enrol_lti_lti2_consumer');
-
-        // Adding fields to table enrol_lti_lti2_consumer.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('name', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('consumerkey256', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('consumerkey', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $table->add_field('secret', XMLDB_TYPE_CHAR, '1024', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('ltiversion', XMLDB_TYPE_CHAR, '10', null, null, null, null);
-        $table->add_field('consumername', XMLDB_TYPE_CHAR, '255', null, null, null, null);
-        $table->add_field('consumerversion', XMLDB_TYPE_CHAR, '255', null, null, null, null);
-        $table->add_field('consumerguid', XMLDB_TYPE_CHAR, '1024', null, null, null, null);
-        $table->add_field('profile', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $table->add_field('toolproxy', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $table->add_field('settings', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $table->add_field('protected', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('enablefrom', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
-        $table->add_field('enableuntil', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
-        $table->add_field('lastaccess', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
-        $table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_lti2_consumer.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-
-        // Adding indexes to table enrol_lti_lti2_consumer.
-        $table->add_index('consumerkey256_uniq', XMLDB_INDEX_UNIQUE, array('consumerkey256'));
-
-        // Conditionally launch create table for enrol_lti_lti2_consumer.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Define table enrol_lti_lti2_tool_proxy to be created.
-        $table = new xmldb_table('enrol_lti_lti2_tool_proxy');
-
-        // Adding fields to table enrol_lti_lti2_tool_proxy.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('toolproxykey', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('toolproxy', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
-        $table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_lti2_tool_proxy.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $table->add_key('toolproxykey_uniq', XMLDB_KEY_UNIQUE, array('toolproxykey'));
-        $table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
-
-        // Conditionally launch create table for enrol_lti_lti2_tool_proxy.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Define table enrol_lti_lti2_context to be created.
-        $table = new xmldb_table('enrol_lti_lti2_context');
-
-        // Adding fields to table enrol_lti_lti2_context.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('lticontextkey', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('settings', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_lti2_context.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
-
-        // Conditionally launch create table for enrol_lti_lti2_context.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Define table enrol_lti_lti2_nonce to be created.
-        $table = new xmldb_table('enrol_lti_lti2_nonce');
-
-        // Adding fields to table enrol_lti_lti2_nonce.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('value', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('expires', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_lti2_nonce.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
-
-        // Conditionally launch create table for enrol_lti_lti2_nonce.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Define table enrol_lti_lti2_resource_link to be created.
-        $table = new xmldb_table('enrol_lti_lti2_resource_link');
-
-        // Adding fields to table enrol_lti_lti2_resource_link.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('contextid', XMLDB_TYPE_INTEGER, '11', null, null, null, null);
-        $table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, null, null, null);
-        $table->add_field('ltiresourcelinkkey', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('settings', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $table->add_field('primaryresourcelinkid', XMLDB_TYPE_INTEGER, '11', null, null, null, null);
-        $table->add_field('shareapproved', XMLDB_TYPE_INTEGER, '1', null, null, null, null);
-        $table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_lti2_resource_link.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $table->add_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'enrol_lti_lti2_context', array('id'));
-        $table->add_key('primaryresourcelinkid', XMLDB_KEY_FOREIGN, array('primaryresourcelinkid'),
-            'enrol_lti_lti2_resource_link', array('id'));
-        $table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
-
-        // Conditionally launch create table for enrol_lti_lti2_resource_link.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Define table enrol_lti_lti2_share_key to be created.
-        $table = new xmldb_table('enrol_lti_lti2_share_key');
-
-        // Adding fields to table enrol_lti_lti2_share_key.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('sharekey', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('resourcelinkid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('autoapprove', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('expires', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_lti2_share_key.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $table->add_key('sharekey', XMLDB_KEY_UNIQUE, array('sharekey'));
-        $table->add_key('resourcelinkid', XMLDB_KEY_FOREIGN_UNIQUE, array('resourcelinkid'),
-            'enrol_lti_lti2_resource_link', array('id'));
-
-        // Conditionally launch create table for enrol_lti_lti2_share_key.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Define table enrol_lti_lti2_user_result to be created.
-        $table = new xmldb_table('enrol_lti_lti2_user_result');
-
-        // Adding fields to table enrol_lti_lti2_user_result.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('resourcelinkid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('ltiuserkey', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('ltiresultsourcedid', XMLDB_TYPE_CHAR, '1024', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_lti2_user_result.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $table->add_key('resourcelinkid', XMLDB_KEY_FOREIGN, array('resourcelinkid'),
-            'enrol_lti_lti2_resource_link', array('id'));
-
-        // Conditionally launch create table for enrol_lti_lti2_user_result.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Define table enrol_lti_tool_consumer_map to be created.
-        $table = new xmldb_table('enrol_lti_tool_consumer_map');
-
-        // Adding fields to table enrol_lti_tool_consumer_map.
-        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        $table->add_field('toolid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
-        $table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
-
-        // Adding keys to table enrol_lti_tool_consumer_map.
-        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $table->add_key('toolid', XMLDB_KEY_FOREIGN, array('toolid'), 'enrol_lti_tools', array('id'));
-        $table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
-
-        // Conditionally launch create table for enrol_lti_tool_consumer_map.
-        if (!$dbman->table_exists($table)) {
-            $dbman->create_table($table);
-        }
-
-        // Lti savepoint reached.
-        upgrade_plugin_savepoint(true, 2016052303, 'enrol', 'lti');
-    }
-
-    if ($oldversion < 2016052304) {
-
-        // Define field type to be added to enrol_lti_lti2_context.
-        $table = new xmldb_table('enrol_lti_lti2_context');
-        $field = new xmldb_field('type', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'lticontextkey');
-
-        // Conditionally launch add field type.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Lti savepoint reached.
-        upgrade_plugin_savepoint(true, 2016052304, 'enrol', 'lti');
-    }
-
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2017011300) {
 
         // Changing precision of field value on table enrol_lti_lti2_nonce to (64).
index 9d3b967..48dfc69 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_manual_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index e42f78f..f6d9efb 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_mnet_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 5732fe2..93ebd50 100644 (file)
@@ -47,9 +47,6 @@ function xmldb_enrol_paypal_upgrade($oldversion) {
 
     $dbman = $DB->get_manager();
 
-    // 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.
 
index 6436e7c..cb255e8 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 function xmldb_enrol_self_upgrade($oldversion) {
-    global $CFG, $DB;
-
-    if ($oldversion < 2016052301) {
-        // Get roles with manager archetype.
-        $managerroles = get_archetype_roles('manager');
-        if (!empty($managerroles)) {
-            // Remove wrong CAP_PROHIBIT from self:holdkey.
-            foreach ($managerroles as $role) {
-                $DB->execute("DELETE
-                                FROM {role_capabilities}
-                               WHERE roleid = ? AND capability = ? AND permission = ?",
-                        array($role->id, 'enrol/self:holdkey', CAP_PROHIBIT));
-            }
-        }
-        upgrade_plugin_savepoint(true, 2016052301, 'enrol', 'self');
-    }
-
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
+    global $CFG;
 
     // Automatically generated Moodle v3.3.0 release upgrade line.
     // Put any upgrade step following this.
index 1b2a99b..10994e7 100644 (file)
@@ -33,7 +33,7 @@ $string['test_unoconvdoesnotexist'] = 'The unoconv path does not point to the un
 $string['test_unoconvdownload'] = 'Download the converted pdf test file.';
 $string['test_unoconvempty'] = 'The unoconv path is not set. Please review your path settings.';
 $string['test_unoconvisdir'] = 'The unoconv path points to a folder, please include the unoconv program in the path you specify';
-$string['test_unoconvnotestfile'] = 'The test document to be coverted into a PDF is missing';
+$string['test_unoconvnotestfile'] = 'The test document to be converted to PDF is missing.';
 $string['test_unoconvnotexecutable'] = 'The unoconv path points to a file that is not executable';
 $string['test_unoconvok'] = 'The unoconv path appears to be properly configured.';
 $string['test_unoconvversionnotsupported'] = 'The version of unoconv you have installed is not supported.';
index 754152c..e4bb953 100644 (file)
@@ -33,35 +33,6 @@ function xmldb_filter_mathjaxloader_upgrade($oldversion) {
 
     require_once($CFG->dirroot . '/filter/mathjaxloader/db/upgradelib.php');
 
-    if ($oldversion < 2016080200) {
-        // We are consolodating the two settings for http and https url into only the https
-        // setting. Since it is preferably to always load the secure resource.
-
-        $httpurl = get_config('filter_mathjaxloader', 'httpurl');
-        if ($httpurl !== 'http://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js' &&
-            $httpurl !== 'http://cdn.mathjax.org/mathjax/2.6.1/MathJax.js') {
-            // If the http setting has been changed, we make the admin choose the https setting because
-            // it indicates some sort of custom setup. This will be supported by the release notes.
-            unset_config('httpsurl', 'filter_mathjaxloader');
-        }
-
-        // The seperate http setting has been removed. We always use the secure resource.
-        unset_config('httpurl', 'filter_mathjaxloader');
-
-        upgrade_plugin_savepoint(true, 2016080200, 'filter', 'mathjaxloader');
-    }
-
-    if ($oldversion < 2016102500) {
-        $httpsurl = get_config('filter_mathjaxloader', 'httpsurl');
-        if ($httpsurl === "https://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js") {
-            set_config('httpsurl', 'https://cdn.mathjax.org/mathjax/2.7-latest/MathJax.js', 'filter_mathjaxloader');
-        }
-        upgrade_plugin_savepoint(true, 2016102500, 'filter', 'mathjaxloader');
-    }
-
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-    //
     if ($oldversion < 2017040300) {
 
         $httpsurl = get_config('filter_mathjaxloader', 'httpsurl');
index 09391c3..a8a43ac 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_filter_mediaplugin_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index da0d43a..ec40486 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_filter_tex_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 1965c9a..5fee948 100644 (file)
@@ -32,7 +32,7 @@ Feature: I need to export grades as text
     When I navigate to "Export > Plain text file" in the course gradebook
     And I expand all fieldsets
     And I click on "Course total" "checkbox"
-    And I set the field "Grade export decimal points" to "1"
+    And I set the field "Grade export decimal places" to "1"
     And I press "Download"
     Then I should see "Student,1"
     And I should see "80.0"
index d1ca5aa..38f6d78 100644 (file)
@@ -30,7 +30,7 @@ Feature: I need to export grades as xml
   Scenario: Export grades as text
     When I navigate to "Export > XML file" in the course gradebook
     And I expand all fieldsets
-    And I set the field "Grade export decimal points" to "1"
+    And I set the field "Grade export decimal places" to "1"
     And I press "Download"
     Then I should see "s1"
     And I should see "a1"
index e781e99..290350d 100644 (file)
@@ -37,9 +37,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_gradingform_guide_upgrade($oldversion) {
     global $DB;
 
-    // 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.
 
index a48635c..e7e0bdf 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_gradingform_rubric_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 1aef803..5dbbfec 100644 (file)
 }
 
 .gradingform_rubric {
-    overflow: auto;
     padding-bottom: 1.5em;
-    max-width: 720px;
-    position: relative;
 }
 
 .gradingform_rubric.editor .criterion .controls,
@@ -89,6 +86,9 @@
 
 .gradingform_rubric .criteria {
     height: 100%;
+    display: flex;
+    width: 100%;
+    overflow: auto;
 }
 
 .gradingform_rubric .criterion {
     margin: 0;
     position: relative;
     float: right;
-}
\ No newline at end of file
+}
index 2cd5dbb..8d7d702 100644 (file)
@@ -34,13 +34,13 @@ $string['grader:view'] = 'View the grader report';
 $string['pluginname'] = 'Grader report';
 $string['preferences'] = 'Grader report preferences';
 $string['privacy:metadata:preference:grade_report_aggregationposition'] = 'Whether the category and course total columns are displayed first or last in the gradebook reports';
-$string['privacy:metadata:preference:grade_report_averagesdecimalpoints'] = 'The number of decimal points to display for each average or whether the overall decimal points setting for the category or grade item is used (inherit).';
+$string['privacy:metadata:preference:grade_report_averagesdecimalpoints'] = 'The number of decimal places to display for each average or whether the overall decimal places setting for the category or grade item is used (inherit).';
 $string['privacy:metadata:preference:grade_report_averagesdisplaytype'] = 'Whether the average (mean) is displayed as real grades, percentages or letters, or whether the display type for the category or grade item is used (inherit).';
 $string['privacy:metadata:preference:grade_report_enableajax'] = 'Whether to add a layer of AJAX functionality to the grader report, simplifying and speeding up common operations';
 $string['privacy:metadata:preference:grade_report_grader_collapsed_categories'] = 'List of gradebook categories to be collapsed';
 $string['privacy:metadata:preference:grade_report_meanselection'] = 'Whether cells with no grade should be included when calculating the average (mean) for each category or grade item';
 $string['privacy:metadata:preference:grade_report_quickgrading'] = 'Whether to display a text input box for each grade, allowing many grades to be edited at the same time';
-$string['privacy:metadata:preference:grade_report_rangesdecimalpoints'] = 'The number of decimal points to display for each range or whether the overall decimal points setting for the category or grade item is used (inherit)';
+$string['privacy:metadata:preference:grade_report_rangesdecimalpoints'] = 'The number of decimal places to display for each range or whether the overall decimal places setting for the category or grade item is used (inherit)';
 $string['privacy:metadata:preference:grade_report_rangesdisplaytype'] = 'Whether the range is displayed as real grades, percentages or letters, or whether the display type for the category or grade item is used (inherit)';
 $string['privacy:metadata:preference:grade_report_showactivityicons'] = 'Whether to show the activity icons next to activity names';
 $string['privacy:metadata:preference:grade_report_showanalysisicon'] = 'Whether to show grade analysis icon by default. If the activity module supports it, the grade analysis icon links to a page with more detailed explanation of the grade and how it was obtained.';
index a2d2496..bc436ed 100644 (file)
@@ -29,9 +29,6 @@
 function xmldb_gradereport_user_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 243344f..c2aad42 100644 (file)
@@ -280,6 +280,7 @@ $string['configmaxevents'] = 'Events to Lookahead';
 $string['configmessaging'] = 'If enabled, users can send messages to other users on the site.';
 $string['configmessagingallowemailoverride'] = 'Allow users to have email message notifications sent to an email address other than the email address in their profile';
 $string['configmessagingdeletereadnotificationsdelay'] = 'Read notifications can be deleted to save space. How long after a notification is read can it be deleted?';
+$string['configmessagingdeleteallnotificationsdelay'] = 'Read and unread notifications can be deleted to save space. How long after a notification is created can it be deleted?';
 $string['configmessagingallusers'] = 'If enabled, users can view the list of all users on the site when selecting someone to message, and their message preferences include the option to accept messages from anyone on the site. If disabled, users can only view the list of users in their courses, and they have just two options in message preferences - to accept messages from their contacts only, or their contacts and anyone in their courses.';
 $string['configminpassworddigits'] = 'Passwords must have at least these many digits.';
 $string['configminpasswordlength'] = 'Passwords must be at least these many characters long.';
@@ -769,6 +770,7 @@ $string['messaging'] = 'Enable messaging system';
 $string['messagingallowemailoverride'] = 'Notification email override';
 $string['messagingallusers'] = 'Allow site-wide messaging';
 $string['messagingdeletereadnotificationsdelay'] = 'Delete read notifications';
+$string['messagingdeleteallnotificationsdelay'] = 'Delete all notifications';
 $string['minpassworddigits'] = 'Digits';
 $string['minpasswordlength'] = 'Password length';
 $string['minpasswordlower'] = 'Lowercase letters';
index 95b9450..2317fb2 100644 (file)
@@ -430,7 +430,7 @@ $string['nologinas'] = 'You are not allowed to log in as that user';
 $string['nonmeaningfulcontent'] = 'Non meaningful content';
 $string['noparticipants'] = 'No participants found for this course';
 $string['noparticipatorycms'] = 'Sorry, but you have no participatory course modules to report on';
-$string['nopermissions'] = 'Sorry, but you do not currently have permissions to do that ({$a})';
+$string['nopermissions'] = 'Sorry, but you do not currently have permissions to do that ({$a}).';
 $string['nopermissiontocomment'] = 'You can\'t add comments';
 $string['nopermissiontodelentry'] = 'You can\'t delete other people\'s entries!';
 $string['nopermissiontoeditcomment'] = 'You can\'t edit other people\'s comments!';
@@ -441,7 +441,7 @@ $string['nopermissiontomanagegroup'] = 'You do not have the required permissions
 $string['nopermissiontorate'] = 'Rating of items not allowed!';
 $string['nopermissiontoshow'] = 'No permission to see this!';
 $string['nopermissiontounlock'] = 'No permission to unlock!';
-$string['nopermissiontoupdatecalendar'] = 'Sorry, but you do not currently have permissions to update calendar event';
+$string['nopermissiontoupdatecalendar'] = 'Sorry, but you do not currently have permissions to update a calendar event.';
 $string['nopermissiontoviewgrades'] = 'Can not view grades.';
 $string['nopermissiontoviewletergrade'] = 'Missing permission to view letter grades';
 $string['nopermissiontoviewpage'] = 'You are not allowed to look at this page';
index 7beca40..8bea3d3 100644 (file)
@@ -93,7 +93,7 @@ $string['autosort'] = 'Auto-sort';
 $string['availableidnumbers'] = 'Available ID numbers';
 $string['average'] = 'Average';
 $string['averagesdecimalpoints'] = 'Decimals in column averages';
-$string['averagesdecimalpoints_help'] = 'This setting determines the number of decimal points to display for each average or whether the overall decimal points setting for the category or grade item is used (inherit).';
+$string['averagesdecimalpoints_help'] = 'This setting determines the number of decimal places to display for each average or whether the overall decimal places setting for the category or grade item is used (inherit).';
 $string['averagesdisplaytype'] = 'Column averages display type';
 $string['averagesdisplaytype_help'] = 'This setting determines whether the average (mean) is displayed as real grades, percentages or letters, or whether the display type for the category or grade item is used (inherit).';
 $string['backupwithoutgradebook'] = 'Backup does not contain gradebook configuration';
@@ -142,8 +142,8 @@ $string['creatinggradebooksettings'] = 'Creating gradebook settings';
 $string['csv'] = 'CSV';
 $string['currentparentaggregation'] = 'Current parent aggregation';
 $string['curveto'] = 'Curve to';
-$string['decimalpoints'] = 'Overall decimal points';
-$string['decimalpoints_help'] = 'This setting determines the number of decimal points to display for each grade. It has no effect on grade calculations, which are made with an accuracy of 5 decimal places.';
+$string['decimalpoints'] = 'Overall decimal places';
+$string['decimalpoints_help'] = 'This setting determines the number of decimal places to display for each grade. It has no effect on grade calculations, which are made with an accuracy of 5 decimal places.';
 $string['default'] = 'Default';
 $string['defaultprev'] = 'Default ({$a})';
 $string['deletecategory'] = 'Delete category';
@@ -274,8 +274,8 @@ $string['gradeexport'] = 'Grade export';
 $string['gradeexportcolumntype'] = '{$a->name} ({$a->extra})';
 $string['gradeexportcustomprofilefields'] = 'Grade export custom profile fields';
 $string['gradeexportcustomprofilefields_desc'] = 'Include these custom profile fields in the grade export, separated by commas.';
-$string['gradeexportdecimalpoints'] = 'Grade export decimal points';
-$string['gradeexportdecimalpoints_desc'] = 'The number of decimal points to display for export. This can be overridden during export.';
+$string['gradeexportdecimalpoints'] = 'Grade export decimal places';
+$string['gradeexportdecimalpoints_desc'] = 'The number of decimal places to display for export. This can be overridden during export.';
 $string['gradeexportdisplaytype'] = 'Grade export display type';
 $string['gradeexportdisplaytype_desc'] = 'Grades can be shown as real grades, as percentages (in reference to the minimum and maximum grades) or as letters (A, B, C etc..) during export. This can be overridden during export.';
 $string['gradeexportdisplaytypes'] = 'Grade export display types';
@@ -666,7 +666,7 @@ $string['quickgrading_help'] = 'If enabled, when editing is turned on, a text in
 Note that when a grade is edited in the grader report, an overridden flag is set, meaning that the grade can no longer be changed from within the related activity.';
 $string['range'] = 'Range';
 $string['rangesdecimalpoints'] = 'Decimals shown in ranges';
-$string['rangesdecimalpoints_help'] = 'This setting determines the number of decimal points to display for each range or whether the overall decimal points setting for the category or grade item is used (inherit).';
+$string['rangesdecimalpoints_help'] = 'This setting determines the number of decimal places to display for each range or whether the overall decimal places setting for the category or grade item is used (inherit).';
 $string['rangesdisplaytype'] = 'Range display type';
 $string['rangesdisplaytype_help'] = 'This setting determines whether the range is displayed as real grades, percentages or letters, or whether the display type for the category or grade item is used (inherit).';
 $string['rank'] = 'Rank';
@@ -735,8 +735,8 @@ $string['showrange'] = 'Show ranges';
 $string['showrange_help'] = 'Whether to show a column for the range.';
 $string['showweight'] = 'Show weightings';
 $string['showweight_help'] = 'Whether to show a column for the grade weight.';
-$string['rangedecimals'] = 'Range decimal points';
-$string['rangedecimals_help'] = 'The number of decimal points to display for range.';
+$string['rangedecimals'] = 'Range decimal places';
+$string['rangedecimals_help'] = 'The number of decimal places to display for the range.';
 $string['showactivityicons'] = 'Show activity icons';
 $string['showactivityicons_help'] = 'If enabled, activity icons are shown next to activity names.';
 $string['showallhidden'] = 'Show hidden';
index 9d6dc4f..43131c2 100644 (file)
@@ -5434,6 +5434,9 @@ abstract class context extends stdClass implements IteratorAggregate {
             return array();
         }
 
+        // Preload the contexts to reduce DB calls.
+        context_helper::preload_contexts_by_id($contextids);
+
         $result = array();
         foreach ($contextids as $contextid) {
             $parent = context::instance_by_id($contextid, MUST_EXIST);
@@ -5870,6 +5873,34 @@ class context_helper extends context {
          context::preload_from_record($rec);
      }
 
+    /**
+     * Preload a set of contexts using their contextid.
+     *
+     * @param   array $contextids
+     */
+    public static function preload_contexts_by_id(array $contextids) {
+        global $DB;
+
+        // Determine which contexts are not already cached.
+        $tofetch = [];
+        foreach ($contextids as $contextid) {
+            if (!self::cache_get_by_id($contextid)) {
+                $tofetch[] = $contextid;
+            }
+        }
+
+        if (count($tofetch) > 1) {
+            // There are at least two to fetch.
+            // There is no point only fetching a single context as this would be no more efficient than calling the existing code.
+            list($insql, $inparams) = $DB->get_in_or_equal($tofetch, SQL_PARAMS_NAMED);
+            $ctxs = $DB->get_records_select('context', "id {$insql}", $inparams, '',
+                    \context_helper::get_preload_record_columns_sql('{context}'));
+            foreach ($ctxs as $ctx) {
+                self::preload_from_record($ctx);
+            }
+        }
+    }
+
     /**
      * Preload all contexts instances from course.
      *
index 661071e..096b837 100644 (file)
Binary files a/lib/amd/build/modal.min.js and b/lib/amd/build/modal.min.js differ
index d357c17..9e03a10 100644 (file)
Binary files a/lib/amd/build/str.min.js and b/lib/amd/build/str.min.js differ
index 3357ba4..b5be0b1 100644 (file)
Binary files a/lib/amd/build/templates.min.js and b/lib/amd/build/templates.min.js differ
index be98211..799b3dc 100644 (file)
@@ -723,7 +723,12 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
             // If the click wasn't inside the modal element then we should
             // hide the modal.
             if (!$(e.target).closest(SELECTORS.MODAL).length) {
-                this.hide();
+                // The check above fails to detect the click was inside the modal when the DOM tree is already changed.
+                // So, we check if we can still find the container element or not. If not, then the DOM tree is changed.
+                // It's best not to hide the modal in that case.
+                if ($(e.target).closest(SELECTORS.CONTAINER).length) {
+                    this.hide();
+                }
             }
         }.bind(this));
 
index eac5c54..286fb48 100644 (file)
@@ -176,6 +176,42 @@ define(['jquery', 'core/ajax', 'core/localstorage'], function($, ajax, storage)
             }
 
             return deferred.promise();
+        },
+        /**
+         * Add a list of strings to the caches.
+         *
+         * @method cache_strings
+         * @param {Object[]} strings Array of { key: key, component: component, lang: lang, value: value }
+         */
+         // eslint-disable-next-line camelcase
+        cache_strings: function(strings) {
+            var defaultLang = $('html').attr('lang').replace(/-/g, '_');
+            strings.forEach(function(string) {
+                var lang = !(lang in string) ? defaultLang : string.lang;
+                var key = string.key;
+                var component = string.component;
+                var value = string.value;
+                var cacheKey = ['core_str', key, component, lang].join('/');
+
+                // Check M.str caching.
+                if (!(component in M.str) || !(key in M.str[component])) {
+                    if (!(component in M.str)) {
+                        M.str[component] = {};
+                    }
+
+                    M.str[component][key] = value;
+                }
+
+                // Check local storage.
+                if (!storage.get(cacheKey)) {
+                    storage.set(cacheKey, value);
+                }
+
+                // Check the promises cache.
+                if (!(cacheKey in promiseCache)) {
+                    promiseCache[cacheKey] = $.Deferred().resolve(value).promise();
+                }
+            });
         }
     };
 });
index 514f210..748cea5 100644 (file)
@@ -59,6 +59,191 @@ define([
     /** @var {Object} iconSystem - Object extending core/iconsystem */
     var iconSystem = {};
 
+    /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */
+    var loadTemplateBuffer = [];
+
+    /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */
+    var isLoadingTemplates = false;
+
+    /**
+     * Search the various caches for a template promise for the given search key.
+     * The search key should be in the format <theme>/<component>/<template> e.g. boost/core/modal.
+     *
+     * If the template is found in any of the caches it will populate the other caches with
+     * the same data as well.
+     *
+     * @param {String} searchKey The template search key in the format <theme>/<component>/<template> e.g. boost/core/modal
+     * @return {Object} jQuery promise resolved with the template source
+     */
+    var getTemplatePromiseFromCache = function(searchKey) {
+        // First try the cache of promises.
+        if (searchKey in templatePromises) {
+            return templatePromises[searchKey];
+        }
+
+        // Check the module cache.
+        if (searchKey in templateCache) {
+            // Add this to the promises cache for future.
+            templatePromises[searchKey] = $.Deferred().resolve(templateCache[searchKey]).promise();
+            return templatePromises[searchKey];
+        }
+
+        // Now try local storage.
+        var cached = storage.get('core_template/' + searchKey);
+        if (cached) {
+            // Add this to the module cache for future.
+            templateCache[searchKey] = cached;
+            // Add this to the promises cache for future.
+            templatePromises[searchKey] = $.Deferred().resolve(cached).promise();
+            return templatePromises[searchKey];
+        }
+
+        return null;
+    };
+
+    /**
+     * Take all of the templates waiting in the buffer and load them from the server
+     * or from the cache.
+     *
+     * All of the templates that need to be loaded from the server will be batched up
+     * and sent in a single network request.
+     */
+    var processLoadTemplateBuffer = function() {
+        if (!loadTemplateBuffer.length) {
+            return;
+        }
+
+        if (isLoadingTemplates) {
+            return;
+        }
+
+        isLoadingTemplates = true;
+        // Grab any templates waiting in the buffer.
+        var templatesToLoad = loadTemplateBuffer.slice();
+        // This will be resolved with the list of promises for the server request.
+        var serverRequestsDeferred = $.Deferred();
+        var requests = [];
+        // Get a list of promises for each of the templates we need to load.
+        var templatePromises = templatesToLoad.map(function(templateData) {
+            var component = templateData.component;
+            var name = templateData.name;
+            var searchKey = templateData.searchKey;
+            var theme = templateData.theme;
+            var templateDeferred = templateData.deferred;
+            var promise = null;
+
+            // Double check to see if this template happened to have landed in the
+            // cache as a dependency of an earlier template.
+            var cachedPromise = getTemplatePromiseFromCache(searchKey);
+            if (cachedPromise) {
+                // We've seen this template so immediately resolve the existing promise.
+                promise = cachedPromise;
+            } else {
+                // We haven't seen this template yet so we need to request it from
+                // the server.
+                requests.push({
+                    methodname: 'core_output_load_template_with_dependencies',
+                    args: {
+                        component: component,
+                        template: name,
+                        themename: theme
+                    }
+                });
+                // Remember the index in the requests list for this template so that
+                // we can get the appropriate promise back.
+                var index = requests.length - 1;
+
+                // The server deferred will be resolved with a list of all of the promises
+                // that were sent in the order that they were added to the requests array.
+                promise = serverRequestsDeferred.promise()
+                    .then(function(promises) {
+                        // The promise for this template will be the one that matches the index
+                        // for it's entry in the requests array.
+                        //
+                        // Make sure the promise is added to the promises cache for this template
+                        // search key so that we don't request it again.
+                        templatePromises[searchKey] = promises[index].then(function(response) {
+                            var templateSource = null;
+
+                            // Process all of the template dependencies for this template and add
+                            // them to the caches so that we don't request them again later.
+                            response.templates.forEach(function(data) {
+                                // Generate the search key for this template in the response so that we
+                                // can add it to the caches.
+                                var tempSearchKey = [theme, data.component, data.name].join('/');
+                                // Cache all of the dependent templates because we'll need them to render
+                                // the requested template.
+                                templateCache[tempSearchKey] = data.value;
+                                storage.set('core_template/' + tempSearchKey, data.value);
+
+                                if (data.component == component && data.name == name) {
+                                    // This is the original template that was requested so remember it to return.
+                                    templateSource = data.value;
+                                }
+                            });
+
+                            if (response.strings.length) {
+                                // If we have strings that the template needs then warm the string cache
+                                // with them now so that we don't need to re-fetch them.
+                                str.cache_strings(response.strings.map(function(data) {
+                                    return {
+                                        component: data.component,
+                                        key: data.name,
+                                        value: data.value
+                                    };
+                                }));
+                            }
+
+                            // Return the original template source that the user requested.
+                            return templateSource;
+                        });
+
+                        return templatePromises[searchKey];
+                    });
+            }
+
+            return promise
+                .then(function(source) {
+                    // When we've successfully loaded the template then resolve the deferred
+                    // in the buffer so that all of the calling code can proceed.
+                    return templateDeferred.resolve(source);
+                })
+                .catch(function(error) {
+                    // If there was an error loading the template then reject the deferred
+                    // in the buffer so that all of the calling code can proceed.
+                    templateDeferred.reject(error);
+                    // Rethrow for anyone else listening.
+                    throw error;
+                });
+        });
+
+        if (requests.length) {
+            // We have requests to send so resolve the deferred with the promises.
+            serverRequestsDeferred.resolve(ajax.call(requests, true, false));
+        } else {
+            // Nothing to load so we can resolve our deferred.
+            serverRequestsDeferred.resolve();
+        }
+
+        // Once we've finished loading all of the templates then recurse to process
+        // any templates that may have been added to the buffer in the time that we
+        // were fetching.
+        $.when.apply(null, templatePromises)
+            .then(function() {
+                // Remove the templates we've loaded from the buffer.
+                loadTemplateBuffer.splice(0, templatesToLoad.length);
+                isLoadingTemplates = false;
+                processLoadTemplateBuffer();
+                return;
+            })
+            .catch(function() {
+                // Remove the templates we've loaded from the buffer.
+                loadTemplateBuffer.splice(0, templatesToLoad.length);
+                isLoadingTemplates = false;
+                processLoadTemplateBuffer();
+            });
+    };
+
     /**
      * Constructor
      *
@@ -85,7 +270,7 @@ define([
     Renderer.prototype.currentThemeName = '';
 
     /**
-     * Load a template from the cache or local storage or ajax request.
+     * Load a template.
      *
      * @method getTemplate
      * @private
@@ -95,44 +280,44 @@ define([
      * @return {Promise} JQuery promise object resolved when the template has been fetched.
      */
     Renderer.prototype.getTemplate = function(templateName) {
-        var parts = templateName.split('/');
-        var component = parts.shift();
-        var name = parts.shift();
-
-        var searchKey = this.currentThemeName + '/' + templateName;
+        var currentTheme = this.currentThemeName;
+        var searchKey = currentTheme + '/' + templateName;
 
-        // First try request variables.
-        if (searchKey in templatePromises) {
-            return templatePromises[searchKey];
+        // If we haven't already seen this template then buffer it.
+        var cachedPromise = getTemplatePromiseFromCache(searchKey);
+        if (cachedPromise) {
+            return cachedPromise;
         }
 
-        // Now try local storage.
-        var cached = storage.get('core_template/' + searchKey);
-
-        if (cached) {
-            templateCache[searchKey] = cached;
-            templatePromises[searchKey] = $.Deferred().resolve(cached).promise();
-            return templatePromises[searchKey];
+        // Check the buffer to seee if this template has already been added.
+        var existingBufferRecords = loadTemplateBuffer.filter(function(record) {
+            return record.searchKey == searchKey;
+        });
+        if (existingBufferRecords.length) {
+            // This template is already in the buffer so just return the existing
+            // promise. No need to add it to the buffer again.
+            return existingBufferRecords[0].deferred.promise();
         }
 
-        // Oh well - load via ajax.
-        var promises = ajax.call([{
-            methodname: 'core_output_load_template',
-            args: {
-                component: component,
-                template: name,
-                themename: this.currentThemeName
-            }
-        }], true, false);
+        // This is the first time this has been requested so let's add it to the buffer
+        // to be loaded.
+        var parts = templateName.split('/');
+        var component = parts.shift();
+        var name = parts.shift();
+        var deferred = $.Deferred();
+
+        // Add this template to the buffer to be loaded.
+        loadTemplateBuffer.push({
+            component: component,
+            name: name,
+            theme: currentTheme,
+            searchKey: searchKey,
+            deferred: deferred
+        });
 
-        templatePromises[searchKey] = promises[0].then(
-            function(templateSource) {
-                templateCache[searchKey] = templateSource;
-                storage.set('core_template/' + searchKey, templateSource);
-                return templateSource;
-            }
-        );
-        return templatePromises[searchKey];
+        // We know there is at least one thing in the buffer so kick off a processing run.
+        processLoadTemplateBuffer();
+        return deferred.promise();
     };
 
     /**
index 2ff3f9f..493f0fd 100644 (file)
@@ -32,25 +32,6 @@ defined('MOODLE_INTERNAL') || die();
  */
 function xmldb_antivirus_clamav_upgrade($oldversion) {
 
-    if ($oldversion < 2016101700) {
-        // Remove setting that has been deprecated long time ago at MDL-44260.
-        unset_config('quarantinedir', 'antivirus_clamav');
-        upgrade_plugin_savepoint(true, 2016101700, 'antivirus', 'clamav');
-    }
-
-    if ($oldversion < 2016102600) {
-        // Make command line a default running method for now. We depend on this
-        // config variable in antivirus scan running, it should be defined.
-        if (!get_config('antivirus_clamav', 'runningmethod')) {
-            set_config('runningmethod', 'commandline', 'antivirus_clamav');
-        }
-
-        upgrade_plugin_savepoint(true, 2016102600, 'antivirus', 'clamav');
-    }
-
-    // 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.
 
index 509ec44..cdca30c 100644 (file)
@@ -58,15 +58,6 @@ class external extends external_api {
             );
     }
 
-    /**
-     * Remove comments from mustache template.
-     * @param string $templatestr
-     * @return mixed
-     */
-    protected static function strip_template_comments($templatestr) {
-        return preg_replace('/(?={{!)(.*)(}})/sU', '', $templatestr);
-    }
-
     /**
      * Return a mustache template, and all the strings it requires.
      *
@@ -84,23 +75,14 @@ class external extends external_api {
                                                   'themename' => $themename,
                                                   'includecomments' => $includecomments));
 
-        $component = $params['component'];
-        $template = $params['template'];
-        $themename = $params['themename'];
-        $includecomments = $params['includecomments'];
-
-        $templatename = $component . '/' . $template;
-
+        $loader = new mustache_template_source_loader();
         // Will throw exceptions if the template does not exist.
-        $filename = mustache_template_finder::get_template_filepath($templatename, $themename);
-        $templatestr = file_get_contents($filename);
-
-        // Remove comments from template.
-        if (!$includecomments) {
-            $templatestr = self::strip_template_comments($templatestr);
-        }
-
-        return $templatestr;
+        return $loader->load(
+            $params['component'],
+            $params['template'],
+            $params['themename'],
+            $params['includecomments']
+        );
     }
 
     /**
@@ -112,6 +94,95 @@ class external extends external_api {
         return new external_value(PARAM_RAW, 'template');
     }
 
+    /**
+     * Returns description of load_template_with_dependencies() parameters.
+     *
+     * @return external_function_parameters
+     */
+    public static function load_template_with_dependencies_parameters() {
+        return new external_function_parameters([
+            'component' => new external_value(PARAM_COMPONENT, 'component containing the template'),
+            'template' => new external_value(PARAM_ALPHANUMEXT, 'name of the template'),
+            'themename' => new external_value(PARAM_ALPHANUMEXT, 'The current theme.'),
+            'includecomments' => new external_value(PARAM_BOOL, 'Include comments or not', VALUE_DEFAULT, false)
+        ]);
+    }
+
+    /**
+     * Return a mustache template, and all the child templates and strings it requires.
+     *
+     * @param string $component The component that holds the template.
+     * @param string $template The name of the template.
+     * @param string $themename The name of the current theme.
+     * @param bool $includecomments Whether to strip comments from the template source.
+     * @return string the template
+     */
+    public static function load_template_with_dependencies(
+        string $component,
+        string $template,
+        string $themename,
+        bool $includecomments = false
+    ) {
+        global $DB, $CFG, $PAGE;
+
+        $params = self::validate_parameters(
+            self::load_template_with_dependencies_parameters(),
+            [
+                'component' => $component,
+                'template' => $template,
+                'themename' => $themename,
+                'includecomments' => $includecomments
+            ]
+        );
+
+        $loader = new mustache_template_source_loader();
+        // Will throw exceptions if the template does not exist.
+        $dependencies = $loader->load_with_dependencies(
+            $params['component'],
+            $params['template'],
+            $params['themename'],
+            $params['includecomments']
+        );
+        $formatdependencies = function($dependency) {
+            $results = [];
+            foreach ($dependency as $dependencycomponent => $dependencyvalues) {
+                foreach ($dependencyvalues as $dependencyname => $dependencyvalue) {
+                    array_push($results, [
+                        'component' => $dependencycomponent,
+                        'name' => $dependencyname,
+                        'value' => $dependencyvalue
+                    ]);
+                }
+            }
+            return $results;
+        };
+
+        // Now we have to unpack the dependencies into a format that can be returned
+        // by external functions (because they don't support dynamic keys).
+        return [
+            'templates' => $formatdependencies($dependencies['templates']),
+            'strings' => $formatdependencies($dependencies['strings'])
+        ];
+    }
+
+    /**
+     * Returns description of load_template_with_dependencies() result value.
+     *
+     * @return external_description
+     */
+    public static function load_template_with_dependencies_returns() {
+        $resourcestructure = new external_single_structure([
+            'component' => new external_value(PARAM_COMPONENT, 'component containing the resource'),
+            'name' => new external_value(PARAM_TEXT, 'name of the resource'),
+            'value' => new external_value(PARAM_RAW, 'resource value')
+        ]);
+
+        return new external_single_structure([
+            'templates' => new external_multiple_structure($resourcestructure),
+            'strings' => new external_multiple_structure($resourcestructure)
+        ]);
+    }
+
     /**
      * Returns description of load_icon_map() parameters.
      *
diff --git a/lib/classes/output/mustache_template_source_loader.php b/lib/classes/output/mustache_template_source_loader.php
new file mode 100644 (file)
index 0000000..6be1275
--- /dev/null
@@ -0,0 +1,342 @@
+<?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/>.
+
+/**
+ * Load template source strings.
+ *
+ * @package    core
+ * @category   output
+ * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \Mustache_Tokenizer;
+
+/**
+ * Load template source strings.
+ *
+ * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mustache_template_source_loader {
+
+    /** @var $gettemplatesource Callback function to load the template source from full name */
+    private $gettemplatesource = null;
+
+    /**
+     * Constructor that takes a callback to allow the calling code to specify how to retrieve
+     * the source for a template name.
+     *
+     * If no callback is provided then default to the load from disk implementation.
+     *
+     * @param callable|null $gettemplatesource Callback to load template source by template name
+     */
+    public function __construct(callable $gettemplatesource = null) {
+        if ($gettemplatesource) {
+            // The calling code has specified a function for retrieving the template source
+            // code by name and theme.
+            $this->gettemplatesource = $gettemplatesource;
+        } else {
+            // By default we will pull the template from disk.
+            $this->gettemplatesource = function($component, $name, $themename) {
+                $fulltemplatename = $component . '/' . $name;
+                $filename = mustache_template_finder::get_template_filepath($fulltemplatename, $themename);
+                return file_get_contents($filename);
+            };
+        }
+    }
+
+    /**
+     * Remove comments from mustache template.
+     *
+     * @param string $templatestr
+     * @return string
+     */
+    protected function strip_template_comments($templatestr) : string {
+        return preg_replace('/(?={{!)(.*)(}})/sU', '', $templatestr);
+    }
+
+    /**
+     * Load the template source from the component and template name.
+     *
+     * @param string $component The moodle component (e.g. core_message)
+     * @param string $name The template name (e.g. message_drawer)
+     * @param string $themename The theme to load the template for (e.g. boost)
+     * @param bool $includecomments If the comments should be stripped from the source before returning
+     * @return string The template source
+     */
+    public function load(
+        string $component,
+        string $name,
+        string $themename,
+        bool $includecomments = false
+    ) : string {
+        // Get the template source from the callback.
+        $source = ($this->gettemplatesource)($component, $name, $themename);
+
+        // Remove comments from template.
+        if (!$includecomments) {
+            $source = $this->strip_template_comments($source);
+        }
+
+        return $source;
+    }
+
+    /**
+     * Load a template and some of the dependencies that will be needed in order to render
+     * the template.
+     *
+     * The current implementation will return all of the templates and all of the strings in
+     * each of those templates (excluding string substitutions).
+     *
+     * The return format is an array indexed with the dependency type (e.g. templates / strings) then
+     * the component (e.g. core_message), and then the id (e.g. message_drawer).
+     *
+     * For example:
+     * * We have 3 templates in core named foo, bar, and baz.
+     * * foo includes bar and bar includes baz.
+     * * foo uses the string 'home' from core
+     * * baz uses the string 'help' from core
+     *
+     * If we load the template foo this function would return:
+     * [
+     *      'templates' => [
+     *          'core' => [
+     *              'foo' => '... template source ...',
+     *              'bar' => '... template source ...',
+     *              'baz' => '... template source ...',
+     *          ]
+     *      ],
+     *      'strings' => [
+     *          'core' => [
+     *              'home' => 'Home',
+     *              'help' => 'Help'
+     *          ]
+     *      ]
+     * ]
+     *
+     * @param string $templatecomponent The moodle component (e.g. core_message)
+     * @param string $templatename The template name (e.g. message_drawer)
+     * @param string $themename The theme to load the template for (e.g. boost)
+     * @param bool $includecomments If the comments should be stripped from the source before returning
+     * @param array $seentemplates List of templates already processed / to be skipped.
+     * @param array $seenstrings List of strings already processed / to be skipped.
+     * @return array
+     */
+    public function load_with_dependencies(
+        string $templatecomponent,
+        string $templatename,
+        string $themename,
+        bool $includecomments = false,
+        array $seentemplates = [],
+        array $seenstrings = []
+    ) : array {
+        // Initialise the return values.
+        $templates = [];
+        $strings = [];
+        $templatecomponent = trim($templatecomponent);
+        $templatename = trim($templatename);
+        // Get the requested template source.
+        $templatesource = $this->load($templatecomponent, $templatename, $themename, $includecomments);
+        // This is a helper function to save a value in one of the result arrays (either $templates or $strings).
+        $save = function(array $results, array $seenlist, string $component, string $id, $value) {
+            if (!isset($results[$component])) {
+                // If the results list doesn't already contain this component then initialise it.
+                $results[$component] = [];
+            }
+
+            // Save the value.
+            $results[$component][$id] = $value;
+            // Record that this item has been processed.
+            array_push($seenlist, "$component/$id");
+            // Return the updated results and seen list.
+            return [$results, $seenlist];
+        };
+        // This is a helper function for processing a dependency. Does stuff like ignore duplicate processing,
+        // common result formatting etc.
+        $handler = function(array $dependency, array $ignorelist, callable $processcallback) {
+            foreach ($dependency as $component => $ids) {
+                foreach ($ids as $id) {
+                    $dependencyid = "$component/$id";
+                    if (array_search($dependencyid, $ignorelist) === false) {
+                        $processcallback($component, $id);
+                        // Add this to our ignore list now that we've processed it so that we don't
+                        // process it again.
+                        array_push($ignorelist, $dependencyid);
+                    }
+                }
+            }
+
+            return $ignorelist;
+        };
+
+        // Save this template as the first result in the $templates result array.
+        list($templates, $seentemplates) = $save($templates, $seentemplates, $templatecomponent, $templatename, $templatesource);
+
+        // Check the template for any dependencies that need to be loaded.
+        $dependencies = $this->scan_template_source_for_dependencies($templatesource);
+
+        // Load all of the lang strings that this template requires and add them to the
+        // returned values.
+        $seenstrings = $handler(
+            $dependencies['strings'],
+            $seenstrings,
+            // Include $strings and $seenstrings by reference so that their values can be updated
+            // outside of this anonymous function.
+            function($component, $id) use ($save, &$strings, &$seenstrings) {
+                $string = get_string($id, $component);
+                // Save the string in the $strings results array.
+                list($strings, $seenstrings) = $save($strings, $seenstrings, $component, $id, $string);
+            }
+        );
+
+        // Load any child templates that we've found in this template and add them to
+        // the return list of dependencies.
+        $seentemplates = $handler(
+            $dependencies['templates'],
+            $seentemplates,
+            // Include $strings, $seenstrings, $templates, and $seentemplates by reference so that their values can be updated
+            // outside of this anonymous function.
+            function($component, $id) use (
+                $themename,
+                $includecomments,
+                &$seentemplates,
+                &$seenstrings,
+                &$templates,
+                &$strings,
+                $save
+            ) {
+                // We haven't seen this template yet so load it and it's dependencies.
+                $subdependencies = $this->load_with_dependencies(
+                    $component,
+                    $id,
+                    $themename,
+                    $includecomments,
+                    $seentemplates,
+                    $seenstrings
+                );
+
+                foreach ($subdependencies['templates'] as $component => $ids) {
+                    foreach ($ids as $id => $value) {
+                        // Include the child themes in our results.
+                        list($templates, $seentemplates) = $save($templates, $seentemplates, $component, $id, $value);
+                    }
+                };
+
+                foreach ($subdependencies['strings'] as $component => $ids) {
+                    foreach ($ids as $id => $value) {
+                        // Include any strings that the child templates need in our results.
+                        list($strings, $seenstrings) = $save($strings, $seenstrings, $component, $id, $value);
+                    }
+                }
+            }
+        );
+
+        return [
+            'templates' => $templates,
+            'strings' => $strings
+        ];
+    }
+
+    /**
+     * Scan over a template source string and return a list of dependencies it requires.
+     * At the moment the list will only include other templates and strings.
+     *
+     * The return format is an array indexed with the dependency type (e.g. templates / strings) then
+     * the component (e.g. core_message) with it's value being an array of the items required
+     * in that component.
+     *
+     * For example:
+     * If we have a template foo that includes 2 templates, bar and baz, and also 2 strings
+     * 'home' and 'help' from the core component then the return value would look like:
+     *
+     * [
+     *      'templates' => [
+     *          'core' => ['foo', 'bar', 'baz']
+     *      ],
+     *      'strings' => [
+     *          'core' => ['home', 'help']
+     *      ]
+     * ]
+     *
+     * @param string $source The template source
+     * @return array
+     */
+    protected function scan_template_source_for_dependencies(string $source) : array {
+        $tokenizer = new Mustache_Tokenizer();
+        $tokens = $tokenizer->scan($source);
+        $templates = [];
+        $strings = [];
+        $addtodependencies = function($dependencies, $component, $id) {
+            $id = trim($id);
+            $component = trim($component);
+
+            if (!isset($dependencies[$component])) {
+                // Initialise the component if we haven't seen it before.
+                $dependencies[$component] = [];
+            }
+
+            // Add this id to the list of dependencies.
+            array_push($dependencies[$component], $id);
+
+            return $dependencies;
+        };
+
+        foreach ($tokens as $index => $token) {
+            $type = $token['type'];
+            $name = isset($token['name']) ? $token['name'] : null;
+
+            if ($name) {
+                switch ($type) {
+                    case Mustache_Tokenizer::T_PARTIAL:
+                        list($component, $id) = explode('/', $name);
+                        $templates = $addtodependencies($templates, $component, $id);
+                        break;
+                    case Mustache_Tokenizer::T_PARENT:
+                        list($component, $id) = explode('/', $name);
+                        $templates = $addtodependencies($templates, $component, $id);
+                        break;
+                    case Mustache_Tokenizer::T_SECTION:
+                        if ($name == 'str') {
+                            // The token that containts the string identifiers (key and component) should
+                            // immediately follow the #str token.
+                            $identifiertoken = isset($tokens[$index + 1]) ? $tokens[$index + 1] : null;
+
+                            if ($identifiertoken) {
+                                // The string identifier is the key and component comma separated.
+                                $identifierstring = $identifiertoken['value'];
+                                $parts = explode(',', $identifierstring);
+                                $id = $parts[0];
+                                // Default to 'core' for the component, if not specified.
+                                $component = isset($parts[1]) ? $parts[1] : 'core';
+                                $strings = $addtodependencies($strings, $component, $id);
+                            }
+                        }
+                        break;
+                }
+            }
+        }
+
+        return [
+            'templates' => $templates,
+            'strings' => $strings
+        ];
+    }
+}
index 6d1a2c4..15b781f 100644 (file)
@@ -46,13 +46,18 @@ class messaging_cleanup_task extends scheduled_task {
 
         $timenow = time();
 
-        // Cleanup messaging.
+        // Cleanup read and unread notifications.
+        if (!empty($CFG->messagingdeleteallnotificationsdelay)) {
+            $notificationdeletetime = $timenow - $CFG->messagingdeleteallnotificationsdelay;
+            $params = array('notificationdeletetime' => $notificationdeletetime);
+            $DB->delete_records_select('notifications', 'timecreated < :notificationdeletetime', $params);
+        }
+
+        // Cleanup read notifications.
         if (!empty($CFG->messagingdeletereadnotificationsdelay)) {
             $notificationdeletetime = $timenow - $CFG->messagingdeletereadnotificationsdelay;
             $params = array('notificationdeletetime' => $notificationdeletetime);
             $DB->delete_records_select('notifications', 'timeread < :notificationdeletetime', $params);
         }
-
     }
-
 }
index 0dbb72a..46164d5 100644 (file)
@@ -1123,6 +1123,7 @@ $capabilities = array(
     ),
 
     'moodle/course:managegroups' => array(
+        'riskbitmask' => RISK_XSS,
 
         'captype' => 'write',
         'contextlevel' => CONTEXT_COURSE,
index 6d930b8..066f226 100644 (file)
@@ -1396,6 +1396,14 @@ $functions = array(
         'loginrequired' => false,
         'ajax' => true,
     ),
+    'core_output_load_template_with_dependencies' => array(
+        'classname' => 'core\output\external',
+        'methodname' => 'load_template_with_dependencies',
+        'description' => 'Load a template and its dependencies for a renderable',
+        'type' => 'read',
+        'loginrequired' => false,
+        'ajax' => true,
+    ),
     'core_output_load_fontawesome_icon_map' => array(
         'classname' => 'core\output\external',
         'methodname' => 'load_fontawesome_icon_map',
index 35cf1da..5e7324d 100644 (file)
@@ -92,388 +92,15 @@ function xmldb_main_upgrade($oldversion) {
     $dbman = $DB->get_manager(); // Loads ddl manager and xmldb classes.
 
     // Always keep this upgrade step with version being the minimum
-    // allowed version to upgrade from (v3.1.0 right now).
-    if ($oldversion < 2016052300) {
+    // allowed version to upgrade from (v3.2.0 right now).
+    if ($oldversion < 2016120500) {
         // Just in case somebody hacks upgrade scripts or env, we really can not continue.
-        echo("You need to upgrade to 3.1.x or higher first!\n");
+        echo("You need to upgrade to 3.2.x or higher first!\n");
         exit(1);
         // Note this savepoint is 100% unreachable, but needed to pass the upgrade checks.
-        upgrade_main_savepoint(true, 2016052300);
+        upgrade_main_savepoint(true, 2016120500);
     }
 
-    if ($oldversion < 2016081700.00) {
-
-        // If someone is emotionally attached to it let's leave the config (basically the version) there.
-        if (!file_exists($CFG->dirroot . '/report/search/classes/output/form.php')) {
-            unset_all_config_for_plugin('report_search');
-        }
-
-        // Savepoint reached.
-        upgrade_main_savepoint(true, 2016081700.00);
-    }
-
-    if ($oldversion < 2016081700.02) {
-        // Default schedule values.
-        $hour = 0;
-        $minute = 0;
-
-        // Get the old settings.
-        if (isset($CFG->statsruntimestarthour)) {
-            $hour = $CFG->statsruntimestarthour;
-        }
-        if (isset($CFG->statsruntimestartminute)) {
-            $minute = $CFG->statsruntimestartminute;
-        }
-
-        // Retrieve the scheduled task record first.
-        $stattask = $DB->get_record('task_scheduled', array('component' => 'moodle', 'classname' => '\core\task\stats_cron_task'));
-
-        // Don't touch customised scheduling.
-        if ($stattask && !$stattask->customised) {
-
-            $nextruntime = mktime($hour, $minute, 0, date('m'), date('d'), date('Y'));
-            if ($nextruntime < $stattask->lastruntime) {
-                // Add 24 hours to the next run time.
-                $newtime = new DateTime();
-                $newtime->setTimestamp($nextruntime);
-                $newtime->add(new DateInterval('P1D'));
-                $nextruntime = $newtime->getTimestamp();
-            }
-            $stattask->nextruntime = $nextruntime;
-            $stattask->minute = $minute;
-            $stattask->hour = $hour;
-            $stattask->customised = 1;
-            $DB->update_record('task_scheduled', $stattask);
-        }
-        // These settings are no longer used.
-        unset_config('statsruntimestarthour');
-        unset_config('statsruntimestartminute');
-        unset_config('statslastexecution');
-
-        upgrade_main_savepoint(true, 2016081700.02);
-    }
-
-    if ($oldversion < 2016082200.00) {
-        // An upgrade step to remove any duplicate stamps, within the same context, in the question_categories table, and to
-        // add a unique index to (contextid, stamp) to avoid future stamp duplication. See MDL-54864.
-
-        // Extend the execution time limit of the script to 2 hours.
-        upgrade_set_timeout(7200);
-
-        // This SQL fetches the id of those records which have duplicate stamps within the same context.
-        // This doesn't return the original record within the context, from which the duplicate stamps were likely created.
-        $fromclause = "FROM (
-                        SELECT min(id) AS minid, contextid, stamp
-                            FROM {question_categories}
-                            GROUP BY contextid, stamp
-                        ) minid
-                        JOIN {question_categories} qc
-                            ON qc.contextid = minid.contextid AND qc.stamp = minid.stamp AND qc.id > minid.minid";
-
-        // Get the total record count - used for the progress bar.
-        $countduplicatessql = "SELECT count(qc.id) $fromclause";
-        $total = $DB->count_records_sql($countduplicatessql);
-
-        // Get the records themselves.
-        $getduplicatessql = "SELECT qc.id $fromclause ORDER BY minid";
-        $rs = $DB->get_recordset_sql($getduplicatessql);
-
-        // For each duplicate, update the stamp to a new random value.
-        $i = 0;
-        $pbar = new progress_bar('updatequestioncategorystamp', 500, true);
-        foreach ($rs as $record) {
-            // Generate a new, unique stamp and update the record.
-            do {
-                $newstamp = make_unique_id_code();
-            } while (isset($usedstamps[$newstamp]));
-            $usedstamps[$newstamp] = 1;
-            $DB->set_field('question_categories', 'stamp', $newstamp, array('id' => $record->id));
-
-            // Update progress.
-            $i++;
-            $pbar->update($i, $total, "Updating duplicate question category stamp - $i/$total.");
-        }
-        $rs->close();
-        unset($usedstamps);
-
-        // The uniqueness of each (contextid, stamp) pair is now guaranteed, so add the unique index to stop future duplicates.
-        $table = new xmldb_table('question_categories');
-        $index = new xmldb_index('contextidstamp', XMLDB_INDEX_UNIQUE, array('contextid', 'stamp'));
-        if (!$dbman->index_exists($table, $index)) {
-            $dbman->add_index($table, $index);
-        }
-
-        // Savepoint reached.
-        upgrade_main_savepoint(true, 2016082200.00);
-    }
-
-    if ($oldversion < 2016091900.00) {
-
-        // Removing the themes from core.
-        $themes = array('base', 'canvas');
-
-        foreach ($themes as $key => $theme) {
-            if (check_dir_exists($CFG->dirroot . '/theme/' . $theme, false)) {
-                // Ignore the themes that have been re-downloaded.
-                unset($themes[$key]);
-            }
-        }
-
-        if (!empty($themes)) {
-            // Hacky emulation of plugin uninstallation.
-            foreach ($themes as $theme) {
-                unset_all_config_for_plugin('theme_' . $theme);
-            }
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016091900.00);
-    }
-
-    if ($oldversion < 2016091900.02) {
-
-        // Define index attemptstepid-name (unique) to be dropped from question_attempt_step_data.
-        $table = new xmldb_table('question_attempt_step_data');
-        $index = new xmldb_index('attemptstepid-name', XMLDB_INDEX_UNIQUE, array('attemptstepid', 'name'));
-
-        // Conditionally launch drop index attemptstepid-name.
-        if ($dbman->index_exists($table, $index)) {
-            $dbman->drop_index($table, $index);
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016091900.02);
-    }
-
-    if ($oldversion < 2016100300.00) {
-        unset_config('enablecssoptimiser');
-
-        upgrade_main_savepoint(true, 2016100300.00);
-    }
-
-    if ($oldversion < 2016100501.00) {
-
-        // Define field enddate to be added to course.
-        $table = new xmldb_table('course');
-        $field = new xmldb_field('enddate', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'startdate');
-
-        // Conditionally launch add field enddate.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016100501.00);
-    }
-
-    if ($oldversion < 2016101100.00) {
-        // Define field component to be added to message.
-        $table = new xmldb_table('message');
-        $field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'timeusertodeleted');
-
-        // Conditionally launch add field component.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Define field eventtype to be added to message.
-        $field = new xmldb_field('eventtype', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'component');
-
-        // Conditionally launch add field eventtype.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016101100.00);
-    }
-
-
-    if ($oldversion < 2016101101.00) {
-        // Define field component to be added to message_read.
-        $table = new xmldb_table('message_read');
-        $field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'timeusertodeleted');
-
-        // Conditionally launch add field component.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Define field eventtype to be added to message_read.
-        $field = new xmldb_field('eventtype', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'component');
-
-        // Conditionally launch add field eventtype.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016101101.00);
-    }
-
-    if ($oldversion < 2016101401.00) {
-        // Clean up repository_alfresco config unless plugin has been manually installed.
-        if (!file_exists($CFG->dirroot . '/repository/alfresco/lib.php')) {
-            // Remove capabilities.
-            capabilities_cleanup('repository_alfresco');
-            // Clean config.
-            unset_all_config_for_plugin('repository_alfresco');
-        }
-
-        // Savepoint reached.
-        upgrade_main_savepoint(true, 2016101401.00);
-    }
-
-    if ($oldversion < 2016101401.02) {
-        $table = new xmldb_table('external_tokens');
-        $field = new xmldb_field('privatetoken', XMLDB_TYPE_CHAR, '64', null, null, null, null);
-
-        // Conditionally add privatetoken field to the external_tokens table.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016101401.02);
-    }
-
-    if ($oldversion < 2016110202.00) {
-
-        // Force uninstall of deleted authentication plugin.
-        if (!file_exists("$CFG->dirroot/auth/radius")) {
-            // Leave settings inplace if there are radius users.
-            if (!$DB->record_exists('user', array('auth' => 'radius', 'deleted' => 0))) {
-                // Remove all other associated config.
-                unset_all_config_for_plugin('auth/radius');
-                // The version number for radius is in this format.
-                unset_all_config_for_plugin('auth_radius');
-            }
-        }
-        upgrade_main_savepoint(true, 2016110202.00);
-    }
-
-    if ($oldversion < 2016110300.00) {
-        // Remove unused admin email setting.
-        unset_config('emailonlyfromreplyaddress');
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016110300.00);
-    }
-
-    if ($oldversion < 2016110500.00) {
-
-        $oldplayers = [
-            'vimeo' => null,
-            'mp3' => ['.mp3'],
-            'html5video' => ['.mov', '.mp4', '.m4v', '.mpeg', '.mpe', '.mpg', '.ogv', '.webm'],
-            'flv' => ['.flv', '.f4v'],
-            'html5audio' => ['.aac', '.flac', '.mp3', '.m4a', '.oga', '.ogg', '.wav'],
-            'youtube' => null,
-            'swf' => null,
-        ];
-
-        // Convert hardcoded media players to the settings of the new media player plugin type.
-        if (get_config('core', 'media_plugins_sortorder') === false) {
-            $enabledplugins = [];
-            $videoextensions = [];
-            $audioextensions = [];
-            foreach ($oldplayers as $oldplayer => $extensions) {
-                $settingname = 'core_media_enable_'.$oldplayer;
-                if (!empty($CFG->$settingname)) {
-                    if ($extensions) {
-                        // VideoJS will be used for all media files players that were used previously.
-                        $enabledplugins['videojs'] = 'videojs';
-                        if ($oldplayer === 'mp3' || $oldplayer === 'html5audio') {
-                            $audioextensions += array_combine($extensions, $extensions);
-                        } else {
-                            $videoextensions += array_combine($extensions, $extensions);
-                        }
-                    } else {
-                        // Enable youtube, vimeo and swf.
-                        $enabledplugins[$oldplayer] = $oldplayer;
-                    }
-                }
-            }
-
-            set_config('media_plugins_sortorder', join(',', $enabledplugins));
-
-            // Configure VideoJS to match the existing players set up.
-            if ($enabledplugins['videojs']) {
-                $enabledplugins[] = 'videojs';
-                set_config('audioextensions', join(',', $audioextensions), 'media_videojs');
-                set_config('videoextensions', join(',', $videoextensions), 'media_videojs');
-                $useflash = !empty($CFG->core_media_enable_flv) || !empty($CFG->core_media_enable_mp3);
-                set_config('useflash', $useflash, 'media_videojs');
-                if (empty($CFG->core_media_enable_youtube)) {
-                    // Normally YouTube is enabled in videojs, but if youtube converter was disabled before upgrade
-                    // disable it in videojs as well.
-                    set_config('youtube', false, 'media_videojs');
-                }
-            }
-        }
-
-        // Unset old settings.
-        foreach ($oldplayers as $oldplayer => $extensions) {
-            unset_config('core_media_enable_' . $oldplayer);
-        }
-
-        // Preset defaults if CORE_MEDIA_VIDEO_WIDTH and CORE_MEDIA_VIDEO_HEIGHT are specified in config.php .
-        // After this upgrade step these constants will not be used any more.
-        if (defined('CORE_MEDIA_VIDEO_WIDTH')) {
-            set_config('media_default_width', CORE_MEDIA_VIDEO_WIDTH);
-        }
-        if (defined('CORE_MEDIA_VIDEO_HEIGHT')) {
-            set_config('media_default_height', CORE_MEDIA_VIDEO_HEIGHT);
-        }
-
-        // Savepoint reached.
-        upgrade_main_savepoint(true, 2016110500.00);
-    }
-
-    if ($oldversion < 2016110600.00) {
-        // Define a field 'deletioninprogress' in the 'course_modules' table, to background deletion tasks.
-        $table = new xmldb_table('course_modules');
-        $field = new xmldb_field('deletioninprogress', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'availability');
-
-        // Conditionally launch add field 'deletioninprogress'.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016110600.00);
-    }
-
-    if ($oldversion < 2016112200.01) {
-
-        // Define field requiredbytheme to be added to block_instances.
-        $table = new xmldb_table('block_instances');
-        $field = new xmldb_field('requiredbytheme', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'showinsubcontexts');
-
-        // Conditionally launch add field requiredbytheme.
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
-        }
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016112200.01);
-    }
-    if ($oldversion < 2016112200.02) {
-
-        // Change the existing site level admin and settings blocks to be requiredbytheme which means they won't show in boost.
-        $context = context_system::instance();
-        $params = array('blockname' => 'settings', 'parentcontextid' => $context->id);
-        $DB->set_field('block_instances', 'requiredbytheme', 1, $params);
-
-        $params = array('blockname' => 'navigation', 'parentcontextid' => $context->id);
-        $DB->set_field('block_instances', 'requiredbytheme', 1, $params);
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2016112200.02);
-    }
-
-    // Automatically generated Moodle v3.2.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2016122800.00) {
         // Find all roles with the coursecreator archetype.
         $coursecreatorroleids = $DB->get_records('role', array('archetype' => 'coursecreator'), '', 'id');
@@ -2864,7 +2491,7 @@ function xmldb_main_upgrade($oldversion) {
 
         $updatesql = "UPDATE {oauth2_issuer}
                          SET image = :newimage
-                       WHERE image = :oldimage";
+                       WHERE " . $DB->sql_compare_text('image', 100). " = :oldimage";
         $params = [
             'newimage' => $newurl,
             'oldimage' => $oldurl
index c668c23..2d91167 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_editor_atto_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 0443ae8..e10c52f 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_atto_equation_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index a6a09f3..76c25ef 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_editor_tinymce_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index b387c7f..5408bf6 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tinymce_spellchecker_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 8960c63..f0cd4c8 100644 (file)
@@ -1128,7 +1128,7 @@ function grade_recover_history_grades($userid, $courseid) {
  *
  * @param int $courseid The course ID
  * @param int $userid If specified try to do a quick regrading of the grades of this user only
- * @param object $updated_item Optional grade item to be marked for regrading
+ * @param object $updated_item Optional grade item to be marked for regrading. It is required if $userid is set.
  * @param \core\progress\base $progress If provided, will be used to update progress on this long operation.
  * @return bool true if ok, array of errors if problems found. Grade item id => error message
  */
@@ -1207,7 +1207,6 @@ function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null,
     $failed = 0;
 
     while (count($finalids) < count($gids)) { // work until all grades are final or error found
-        $count = 0;
         foreach ($gids as $gid) {
             if (in_array($gid, $finalids)) {
                 continue; // already final
@@ -1281,7 +1280,6 @@ function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null,
                 } else {
                     $grade_items[$gid]->needsupdate = 0;
                 }
-                $count++;
                 $finalids[] = $gid;
                 $updatedids[] = $gid;
 
@@ -1291,7 +1289,7 @@ function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null,
             }
         }
 
-        if ($count == 0) {
+        if (count($finalids) == 0) {
             $failed++;
         } else {
             $failed = 0;
index 18e7a74..4cfc923 100644 (file)
@@ -3108,7 +3108,10 @@ function require_course_login($courseorid, $autologinguest = true, $cm = null, $
             // If $PAGE->course, and hence $PAGE->context, have not already been set up properly, set them up now.
             $PAGE->set_course($PAGE->course);
         }
-        user_accesstime_log(SITEID);
+        // Do not update access time for webservice or ajax requests.
+        if (!WS_SERVER && !AJAX_SCRIPT) {
+            user_accesstime_log(SITEID);
+        }
         return;
 
     } else {
index 7181ffe..6ccfa79 100644 (file)
@@ -4509,6 +4509,22 @@ EOD;
                     }
                 }
 
+                // Generate the form element wrapper ids and names to pass to the template.
+                // This differs between group and non-group elements.
+                if ($element->getType() === 'group') {
+                    // Group element.
+                    // The id will be something like 'fgroup_id_NAME'. E.g. fgroup_id_mygroup.
+                    $elementcontext['wrapperid'] = $elementcontext['id'];
+
+                    // Ensure group elements pass through the group name as the element name so the id_error_{{element.name}} is
+                    // properly set in the template.
+                    $elementcontext['name'] = $elementcontext['groupname'];
+                } else {
+                    // Non grouped element.
+                    // Creates an id like 'fitem_id_NAME'. E.g. fitem_id_mytextelement.
+                    $elementcontext['wrapperid'] = 'fitem_' . $elementcontext['id'];
+                }
+
                 $context = array(
                     'element' => $elementcontext,
                     'label' => $label,
index 7beeac0..0c3e0d1 100644 (file)
@@ -48,8 +48,8 @@ function moodle_minimum_php_version_is_met($haltexecution = false) {
     // PLEASE NOTE THIS FUNCTION MUST BE COMPATIBLE WITH OLD UNSUPPORTED VERSIONS OF PHP.
     // Do not use modern php features or Moodle convenience functions (e.g. localised strings).
 
-    $minimumversion = '7.0.0';
-    $moodlerequirementchanged = '3.4';
+    $minimumversion = '7.1.0';
+    $moodlerequirementchanged = '3.7';
 
     if (version_compare(PHP_VERSION, $minimumversion) < 0) {
         if ($haltexecution) {
index 61764ed..22fa4f7 100644 (file)
@@ -921,8 +921,6 @@ function question_load_questions($questionids, $extrafields = '', $join = '') {
  * @param stdClass[]|null $filtercourses The courses to filter the course tags by.
  */
 function _tidy_question($question, $category, array $tagobjects = null, array $filtercourses = null) {
-    global $CFG;
-
     // Load question-type specific fields.
     if (!question_bank::is_qtype_installed($question->qtype)) {
         $question->questiontext = html_writer::tag('p', get_string('warningmissingtype',
@@ -1651,25 +1649,44 @@ class context_to_string_translator{
 /**
  * Check capability on category
  *
- * @param mixed $questionorid object or id. If an object is passed, it should include ->contextid and ->createdby.
+ * @param int|stdClass $questionorid object or id. If an object is passed, it should include ->contextid and ->createdby.
  * @param string $cap 'add', 'edit', 'view', 'use', 'move' or 'tag'.
- * @param integer $notused no longer used.
- * @return boolean this user has the capability $cap for this question $question?
+ * @param int $notused no longer used.
+ * @return bool this user has the capability $cap for this question $question?
+ * @throws coding_exception
  */
 function question_has_capability_on($questionorid, $cap, $notused = -1) {
-    global $USER;
+    global $USER, $DB;
 
     if (is_numeric($questionorid)) {
-        $question = question_bank::load_question_data((int)$questionorid);
+        $questionid = (int)$questionorid;
     } else if (is_object($questionorid)) {
+        // All we really need in this function is the contextid and author of the question.
+        // We won't bother fetching other details of the question if these 2 fields are provided.
         if (isset($questionorid->contextid) && isset($questionorid->createdby)) {
             $question = $questionorid;
+        } else if (!empty($questionorid->id)) {
+            $questionid = $questionorid->id;
         }
+    }
+
+    // At this point, either $question or $questionid is expected to be set.
+    if (isset($questionid)) {
+        try {
+            $question = question_bank::load_question_data($questionid);
+        } catch (Exception $e) {
+            // Let's log the exception for future debugging.
+            debugging($e->getMessage(), DEBUG_NORMAL, $e->getTrace());
 
-        if (!isset($question) && isset($questionorid->id) && $questionorid->id != 0) {
-            $question = question_bank::load_question_data($questionorid->id);
+            // Well, at least we tried. Seems that we really have to read from DB.
+            $question = $DB->get_record_sql('SELECT q.id, q.createdby, qc.contextid
+                                               FROM {question} q
+                                               JOIN {question_categories} qc ON q.category = qc.id
+                                              WHERE q.id = :id', ['id' => $questionid]);
         }
-    } else {
+    }
+
+    if (!isset($question)) {
         throw new coding_exception('$questionorid parameter needs to be an integer or an object.');
     }
 
index 9dfd7f0..a8970fa 100644 (file)
@@ -987,18 +987,24 @@ function stats_get_base_daily($time=0) {
 function stats_get_base_weekly($time=0) {
     global $CFG;
 
-    $time = stats_get_base_daily($time);
+    $datetime = new DateTime();
+    $datetime->setTimestamp(stats_get_base_daily($time));
     $startday = $CFG->calendar_startwday;
 
     core_date::set_default_server_timezone();
     $thisday = date('w', $time);
 
+    $days = 0;
+
     if ($thisday > $startday) {
-        $time = $time - (($thisday - $startday) * 60*60*24);
+        $days = $thisday - $startday;
     } else if ($thisday < $startday) {
-        $time = $time - ((7 + $thisday - $startday) * 60*60*24);
+        $days = 7 + $thisday - $startday;
     }
-    return $time;
+
+    $datetime->sub(new DateInterval("P{$days}D"));
+
+    return $datetime->getTimestamp();
 }
 
 /**
index 2541844..287531a 100644 (file)
@@ -3820,6 +3820,47 @@ class core_accesslib_testcase extends advanced_testcase {
         $this->setUser($user2);
         $this->assertEquals($expectedteacher, get_profile_roles($coursecontext));
     }
+
+    /**
+     * Ensure that the get_parent_contexts() function limits the number of queries it performs.
+     */
+    public function test_get_parent_contexts_preload() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        /*
+         * Given the following data structure:
+         * System
+         * - Category
+         * --- Category
+         * ----- Category
+         * ------- Category
+         * --------- Course
+         * ----------- Activity (Forum)
+         */
+
+        $contexts = [];
+
+        $cat1 = $this->getDataGenerator()->create_category();
+        $cat2 = $this->getDataGenerator()->create_category(['parent' => $cat1->id]);
+        $cat3 = $this->getDataGenerator()->create_category(['parent' => $cat2->id]);
+        $cat4 = $this->getDataGenerator()->create_category(['parent' => $cat3->id]);
+        $course = $this->getDataGenerator()->create_course(['category' => $cat4->id]);
+        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
+
+        $modcontext = context_module::instance($forum->cmid);
+
+        context_helper::reset_caches();
+
+        // There should only be a single DB query.
+        $predbqueries = $DB->perf_get_reads();
+
+        $parents = $modcontext->get_parent_contexts();
+        // Note: For some databases There is one read, plus one FETCH, plus one CLOSE.
+        // These all show as reads, when there has actually only been a single query.
+        $this->assertLessThanOrEqual(3, $DB->perf_get_reads() - $predbqueries);
+    }
 }
 
 /**
index a660edb..f422cbe 100644 (file)
@@ -32,36 +32,65 @@ defined('MOODLE_INTERNAL') || die();
 class core_environment_testcase extends advanced_testcase {
 
     /**
-     * Test the environment.
+     * Test the environment check status.
      */
-    public function test_environment() {
+    public function test_environment_check_status() {
         global $CFG;
+        require_once($CFG->libdir.'/environmentlib.php');
+
+        $results = check_moodle_environment(normalize_version($CFG->release), ENV_SELECT_RELEASE);
+
+        // The first element of the results array contains the environment check status.
+        $status = reset($results);
+        $this->assertTrue($status);
+    }
 
+    /**
+     * Data provider for Moodle environment check tests.
+     *
+     * @return array
+     */
+    public function environment_provider() {
+        global $CFG;
         require_once($CFG->libdir.'/environmentlib.php');
-        list($envstatus, $environment_results) = check_moodle_environment(normalize_version($CFG->release), ENV_SELECT_RELEASE);
 
+        $results = check_moodle_environment(normalize_version($CFG->release), ENV_SELECT_RELEASE);
+        // The second element of the results array contains the list of environment results.
+        $environmentresults = end($results);
+        return array_map(function($result) {
+            return [$result];
+        }, $environmentresults);
+    }
+
+    /**
+     * Test the environment.
+     *
+     * @dataProvider environment_provider
+     * @param environment_results $result
+     */
+    public function test_environment($result) {
         $sslmessages = ['ssl/tls configuration not supported', 'invalid ssl/tls configuration'];
 
-        $this->assertNotEmpty($envstatus);
-        foreach ($environment_results as $environment_result) {
-            if ($environment_result->part === 'php_setting'
-                and $environment_result->info === 'opcache.enable'
-                and $environment_result->getLevel() === 'optional'
-                and $environment_result->getStatus() === false
-            ) {
-                $this->markTestSkipped('OPCache extension is not necessary for unit testing.');
-                continue;
-            }
-            if ($environment_result->part === 'custom_check'
-                and in_array($environment_result->info, $sslmessages)
-                and $environment_result->getLevel() === 'optional'
-                and $environment_result->getStatus() === false
-            ) {
+        if ($result->part === 'php_setting'
+                && $result->info === 'opcache.enable'
+                && $result->getLevel() === 'optional'
+                && $result->getStatus() === false) {
+            $this->markTestSkipped('OPCache extension is not necessary for unit testing.');
+        }
+
+        if ($result->part === 'custom_check'
+                && $result->getLevel() === 'optional'
+                && $result->getStatus() === false) {
+            if (in_array($result->info, $sslmessages)) {
                 $this->markTestSkipped('Up-to-date TLS libraries are not necessary for unit testing.');
-                continue;
             }
-            $this->assertTrue($environment_result->getStatus(), "Problem detected in environment ($environment_result->part:$environment_result->info), fix all warnings and errors!");
+            if ($result->info === 'php not 64 bits' && PHP_INT_SIZE == 4) {
+                // If we're on a 32-bit system, skip 64-bit check. 32-bit PHP has PHP_INT_SIZE set to 4.
+                $this->markTestSkipped('64-bit check is not necessary for unit testing.');
+            }
         }
+        $info = "{$result->part}:{$result->info}";
+        $this->assertTrue($result->getStatus(), "Problem detected in environment ($info), fix all warnings and errors!");
     }
 
     /**
diff --git a/lib/tests/mustache_template_source_loader_test.php b/lib/tests/mustache_template_source_loader_test.php
new file mode 100644 (file)
index 0000000..1ccac41
--- /dev/null
@@ -0,0 +1,445 @@
+<?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/>.
+
+/**
+ * Unit tests for lib/classes/output/mustache_template_source_loader.php
+ *
+ * @package   core
+ * @copyright 2018 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\output\mustache_template_source_loader;
+
+/**
+ * Unit tests for the Mustache source loader class.
+ *
+ * @package   core
+ * @copyright 2018 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_output_mustache_template_source_loader_testcase extends advanced_testcase {
+    /**
+     * Ensure that stripping comments from templates does not mutilate the template body.
+     */
+    public function test_strip_template_comments() {
+
+        $templatebody = <<<'TBD'
+        <h1>{{# str }} pluginname, mod_lemmings {{/ str }}</h1>
+        <div>{{test}}</div>
+        <div>{{{unescapedtest}}}</div>
+        {{#lemmings}}
+            <div>
+                <h2>{{name}}</h2>
+                {{> mod_lemmings/lemmingprofile }}
+                {{# pix }} t/edit, core, Edit Lemming {{/ pix }}
+            </div>
+        {{/lemmings}}
+        {{^lemmings}}Sorry, no lemmings today{{/lemmings}}
+        <div id="{{ uniqid }}-tab-container">
+            {{# tabheader }}
+                <ul role="tablist" class="nav nav-tabs">
+                    {{# iconlist }}
+                        {{# icons }}
+                            {{> core/pix_icon }}
+                        {{/ icons }}
+                    {{/ iconlist }}
+                </ul>
+            {{/ tabheader }}
+            {{# tabbody }}
+                <div class="tab-content">
+                    {{# tabcontent }}
+                        {{# tabs }}
+                            {{> core/notification_info}}
+                        {{/ tabs }}
+                    {{/ tabcontent }}
+                </div>
+            {{/ tabbody }}
+        </div>
+        {{#js}}
+            require(['jquery','core/tabs'], function($, tabs) {
+
+                var container = $("#{{ uniqid }}-tab-container");
+                tabs.create(container);
+            });
+        {{/js}}
+TBD;
+        $templatewithcomment = <<<TBC
+        {{!
+            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 mod_lemmings/lemmings
+
+            Lemmings template.
+
+            The purpose of this template is to render a lot of lemmings.
+
+            Classes required for JS:
+            * none
+
+            Data attributes required for JS:
+            * none
+
+            Context variables required for this template:
+            * attributes Array of name / value pairs.
+
+            Example context (json):
+            {
+                "lemmings": [
+                    { "name": "Lemmy Winks", "age" : 1, "size" : "big" },
+                    { "name": "Rocky", "age" : 2, "size" : "small" }
+                ]
+            }
+
+        }}
+        $templatebody
+        {{!
+            Here's some more comment text
+            Note, there is no need to test bracketed variables inside comments as gherkin does not support that!
+            See this issue: https://github.com/mustache/spec/issues/8
+        }}
+TBC;
+
+        $loader = new mustache_template_source_loader();
+        $actual = phpunit_util::call_internal_method(
+            $loader,
+            'strip_template_comments',
+            [$templatewithcomment],
+            \core\output\mustache_template_source_loader::class
+        );
+        $this->assertEquals(trim($templatebody), trim($actual));
+    }
+
+    /**
+     * Data provider for the test_load function.
+     */
+    public function test_load_test_cases() {
+        $cache = [
+            'core' => [
+                'test' => '{{! a comment }}The rest of the template'
+            ]
+        ];
+        $loader = $this->build_loader_from_static_cache($cache);
+
+        return [
+            'with comments' => [
+                'loader' => $loader,
+                'component' => 'core',
+                'name' => 'test',
+                'includecomments' => true,
+                'expected' => '{{! a comment }}The rest of the template'
+            ],
+            'without comments' => [
+                'loader' => $loader,
+                'component' => 'core',
+                'name' => 'test',
+                'includecomments' => false,
+                'expected' => 'The rest of the template'
+            ],
+        ];
+    }
+
+    /**
+     * Test the load function.
+     *
+     * @dataProvider test_load_test_cases()
+     * @param mustache_template_source_loader $loader The loader
+     * @param string $component The moodle component
+     * @param string $name The template name
+     * @param bool $includecomments Whether to strip comments
+     * @param string $expected The expected output
+     */
+    public function test_load($loader, $component, $name, $includecomments, $expected) {
+        $this->assertEquals($expected, $loader->load($component, $name, 'boost', $includecomments));
+    }
+
+    /**
+     * Data provider for the load_with_dependencies function.
+     */
+    public function test_load_with_dependencies_test_cases() {
+        // Create a bunch of templates that include one another in various ways. There is
+        // multiple instances of recursive inclusions to test that the code doensn't get
+        // stuck in an infinite loop.
+        $foo = '{{! a comment }}{{> core/bar }}{{< test/bop }}{{/ test/bop}}{{#str}} help, core {{/str}}';
+        $foo2 = '{{! a comment }}hello';
+        $bar = '{{! a comment }}{{> core/baz }}';
+        $baz = '{{! a comment }}{{#str}} hide, core {{/str}}';
+        $bop = '{{! a comment }}{{< test/bim }}{{/ test/bim }}{{> core/foo }}';
+        $bim = '{{! a comment }}{{< core/foo }}{{/ core/foo}}{{> test/foo }}';
+        $foonocomment = '{{> core/bar }}{{< test/bop }}{{/ test/bop}}{{#str}} help, core {{/str}}';
+        $foo2nocomment = 'hello';
+        $barnocomment = '{{> core/baz }}';
+        $baznocomment = '{{#str}} hide, core {{/str}}';
+        $bopnocomment = '{{< test/bim }}{{/ test/bim }}{{> core/foo }}';
+        $bimnocomment = '{{< core/foo }}{{/ core/foo}}{{> test/foo }}';
+        $cache = [
+            'core' => [
+                'foo' => $foo,
+                'bar' => $bar,
+                'baz' => $baz,
+            ],
+            'test' => [
+                'foo' => $foo2,
+                'bop' => $bop,
+                'bim' => $bim
+            ]
+        ];
+        $loader = $this->build_loader_from_static_cache($cache);
+
+        return [
+            'no template includes w comments' => [
+                'loader' => $loader,
+                'component' => 'test',
+                'name' => 'foo',
+                'includecomments' => true,
+                'expected' => [
+                    'templates' => [
+                        'test' => [
+                            'foo' => $foo2
+                        ]
+                    ],
+                    'strings' => []
+                ]
+            ],
+            'no template includes w/o comments' => [
+                'loader' => $loader,
+                'component' => 'test',
+                'name' => 'foo',
+                'includecomments' => false,
+                'expected' => [
+                    'templates' => [
+                        'test' => [
+                            'foo' => $foo2nocomment
+                        ]
+                    ],
+                    'strings' => []
+                ]
+            ],
+            'no template includes with string w comments' => [
+                'loader' => $loader,
+                'component' => 'core',
+                'name' => 'baz',
+                'includecomments' => true,
+                'expected' => [
+                    'templates' => [
+                        'core' => [
+                            'baz' => $baz
+                        ]
+                    ],
+                    'strings' => [
+                        'core' => [
+                            'hide' => 'Hide'
+                        ]
+                    ]
+                ]
+            ],
+            'no template includes with string w/o comments' => [
+                'loader' => $loader,
+                'component' => 'core',
+                'name' => 'baz',
+                'includecomments' => false,
+                'expected' => [
+                    'templates' => [
+                        'core' => [
+                            'baz' => $baznocomment
+                        ]
+                    ],
+                    'strings' => [
+                        'core' => [
+                            'hide' => 'Hide'
+                        ]
+                    ]
+                ]
+            ],
+            'full with comments' => [
+                'loader' => $loader,
+                'component' => 'core',
+                'name' => 'foo',
+                'includecomments' => true,
+                'expected' => [
+                    'templates' => [
+                        'core' => [
+                            'foo' => $foo,
+                            'bar' => $bar,
+                            'baz' => $baz
+                        ],
+                        'test' => [
+                            'foo' => $foo2,
+                            'bop' => $bop,
+                            'bim' => $bim
+                        ]
+                    ],
+                    'strings' => [
+                        'core' => [
+                            'help' => 'Help',
+                            'hide' => 'Hide'
+                        ]
+                    ]
+                ]
+            ],
+            'full without comments' => [
+                'loader' => $loader,
+                'component' => 'core',
+                'name' => 'foo',
+                'includecomments' => false,
+                'expected' => [
+                    'templates' => [
+                        'core' => [
+                            'foo' => $foonocomment,
+                            'bar' => $barnocomment,
+                            'baz' => $baznocomment
+                        ],
+                        'test' => [
+                            'foo' => $foo2nocomment,
+                            'bop' => $bopnocomment,
+                            'bim' => $bimnocomment
+                        ]
+                    ],
+                    'strings' => [
+                        'core' => [
+                            'help' => 'Help',
+                            'hide' => 'Hide'
+                        ]
+                    ]
+                ]
+            ]
+        ];
+    }
+
+    /**
+     * Test the load_with_dependencies function.
+     *
+     * @dataProvider test_load_with_dependencies_test_cases()
+     * @param mustache_template_source_loader $loader The loader
+     * @param string $component The moodle component
+     * @param string $name The template name
+     * @param bool $includecomments Whether to strip comments
+     * @param string $expected The expected output
+     */
+    public function test_load_with_dependencies($loader, $component, $name, $includecomments, $expected) {
+        $actual = $loader->load_with_dependencies($component, $name, 'boost', $includecomments);
+        $this->assertEquals($expected, $actual);
+    }
+    /**
+     * Data provider for the test_load function.
+     */
+    public function test_scan_template_source_for_dependencies_test_cases() {
+        $foo = '{{! a comment }}{{> core/bar }}{{< test/bop }}{{/ test/bop}}{{#str}} help, core {{/str}}';
+        $bar = '{{! a comment }}{{> core/baz }}';
+        $baz = '{{! a comment }}{{#str}} hide, core {{/str}}';
+        $bop = '{{! a comment }}hello';
+        $cache = [
+            'core' => [
+                'foo' => $foo,
+                'bar' => $bar,
+                'baz' => $baz,
+                'bop' => $bop
+            ]
+        ];
+        $loader = $this->build_loader_from_static_cache($cache);
+
+        return [
+            'single template include' => [
+                'loader' => $loader,
+                'source' => $bar,
+                'expected' => [
+                    'templates' => [
+                        'core' => ['baz']
+                    ],
+                    'strings' => []
+                ]
+            ],
+            'single string include' => [
+                'loader' => $loader,
+                'source' => $baz,
+                'expected' => [
+                    'templates' => [],
+                    'strings' => [
+                        'core' => ['hide']
+                    ]
+                ]
+            ],
+            'no include' => [
+                'loader' => $loader,
+                'source' => $bop,
+                'expected' => [
+                    'templates' => [],
+                    'strings' => []
+                ]
+            ],
+            'all include' => [
+                'loader' => $loader,
+                'source' => $foo,
+                'expected' => [
+                    'templates' => [
+                        'core' => ['bar'],
+                        'test' => ['bop']
+                    ],
+                    'strings' => [
+                        'core' => ['help']
+                    ]
+                ]
+            ],
+        ];
+    }
+
+    /**
+     * Test the scan_template_source_for_dependencies function.
+     *
+     * @dataProvider test_scan_template_source_for_dependencies_test_cases()
+     * @param mustache_template_source_loader $loader The loader
+     * @param string $source The template to test
+     * @param string $expected The expected output
+     */
+    public function test_scan_template_source_for_dependencies($loader, $source, $expected) {
+        $actual = phpunit_util::call_internal_method(
+            $loader,
+            'scan_template_source_for_dependencies',
+            [$source],
+            \core\output\mustache_template_source_loader::class
+        );
+        $this->assertEquals($expected, $actual);
+    }
+
+    /**
+     * Create an instance of mustache_template_source_loader which loads its templates
+     * from the given cache rather than disk.
+     *
+     * @param array $cache A cache of templates
+     * @return mustache_template_source_loader
+     */
+    private function build_loader_from_static_cache(array $cache) : mustache_template_source_loader {
+        return new mustache_template_source_loader(function($component, $name, $themename) use ($cache) {
+            return $cache[$component][$name];
+        });
+    }
+}
diff --git a/lib/tests/output_external_test.php b/lib/tests/output_external_test.php
deleted file mode 100644 (file)
index 35acb97..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-<?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/>.
-
-/**
- * Unit tests for lib/classes/output/external.php
- * @author    Guy Thomas <gthomas@moodlerooms.com>
- * @copyright Copyright (c) 2017 Blackboard Inc.
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-use core\output\external;
-
-require_once(__DIR__.'/../../lib/externallib.php');
-require_once(__DIR__.'/../../lib/mustache/src/Mustache/Tokenizer.php');
-require_once(__DIR__.'/../../lib/mustache/src/Mustache/Parser.php');
-
-/**
- * Class core_output_external_testcase - test \core\output\external class.
- * @package   core
- * @author    Guy Thomas <gthomas@moodlerooms.com>
- * @copyright Copyright (c) 2017 Blackboard Inc.
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class core_output_external_testcase extends base_testcase {
-
-    /**
-     * Ensure that stripping comments from templates does not mutilate the template body.
-     */
-    public function test_strip_template_comments() {
-
-        $templatebody = <<<'TBD'
-        <h1>{{# str }} pluginname, mod_lemmings {{/ str }}</h1>
-        <div>{{test}}</div>
-        <div>{{{unescapedtest}}}</div>
-        {{#lemmings}}
-            <div>
-                <h2>{{name}}</h2>
-                {{> mod_lemmings/lemmingprofile }}
-                {{# pix }} t/edit, core, Edit Lemming {{/ pix }}
-            </div>
-        {{/lemmings}}
-        {{^lemmings}}Sorry, no lemmings today{{/lemmings}}
-        <div id="{{ uniqid }}-tab-container">
-            {{# tabheader }}
-                <ul role="tablist" class="nav nav-tabs">
-                    {{# iconlist }}
-                        {{# icons }}
-                            {{> core/pix_icon }}
-                        {{/ icons }}
-                    {{/ iconlist }}
-                </ul>
-            {{/ tabheader }}
-            {{# tabbody }}
-                <div class="tab-content">
-                    {{# tabcontent }}
-                        {{# tabs }}
-                            {{> core/notification_info}}
-                        {{/ tabs }}
-                    {{/ tabcontent }}
-                </div>
-            {{/ tabbody }}
-        </div>
-        {{#js}}
-            require(['jquery','core/tabs'], function($, tabs) {
-
-                var container = $("#{{ uniqid }}-tab-container");
-                tabs.create(container);
-            });
-        {{/js}}
-TBD;
-        $templatewithcomment = <<<TBC
-        {{!
-            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 mod_lemmings/lemmings
-
-            Lemmings template.
-
-            The purpose of this template is to render a lot of lemmings.
-
-            Classes required for JS:
-            * none
-
-            Data attributes required for JS:
-            * none
-
-            Context variables required for this template:
-            * attributes Array of name / value pairs.
-
-            Example context (json):
-            {
-                "lemmings": [
-                    { "name": "Lemmy Winks", "age" : 1, "size" : "big" },
-                    { "name": "Rocky", "age" : 2, "size" : "small" }
-                ]
-            }
-
-        }}
-        $templatebody
-        {{!
-            Here's some more comment text
-            Note, there is no need to test bracketed variables inside comments as gherkin does not support that!
-            See this issue: https://github.com/mustache/spec/issues/8
-        }}
-TBC;
-
-        // Ensure that the template when stripped of comments just includes the body.
-        $stripped = phpunit_util::call_internal_method(null, 'strip_template_comments',
-                [$templatewithcomment], 'core\output\external');
-        $this->assertEquals(trim($templatebody), trim($stripped));
-
-        $tokenizer = new Mustache_Tokenizer();
-        $tokens = $tokenizer->scan($templatebody);
-        $parser = new Mustache_Parser();
-        $tree = $parser->parse($tokens);
-        $this->assertNotEmpty($tree);
-    }
-}
index ced468b..ae1cd80 100644 (file)
@@ -1643,6 +1643,37 @@ class core_questionlib_testcase extends advanced_testcase {
         ];
     }
 
+    /**
+     * Tests that question_has_capability_on does not throw exception on broken questions.
+     */
+    public function test_question_has_capability_on_broken_question() {
+        global $DB;
+
+        // Create the test data.
+        $generator = $this->getDataGenerator();
+        $questiongenerator = $generator->get_plugin_generator('core_question');
+
+        $category = $generator->create_category();
+        $context = context_coursecat::instance($category->id);
+        $questioncat = $questiongenerator->create_question_category([
+            'contextid' => $context->id,
+        ]);
+
+        // Create a cloze question.
+        $question = $questiongenerator->create_question('multianswer', null, [
+            'category' => $questioncat->id,
+        ]);
+        // Now, break the question.
+        $DB->delete_records('question_multianswer', ['question' => $question->id]);
+
+        $this->setAdminUser();
+
+        $result = question_has_capability_on($question->id, 'tag');
+        $this->assertTrue($result);
+
+        $this->assertDebuggingCalled();
+    }
+
     /**
      * Tests for the deprecated question_has_capability_on function when passing a stdClass as parameter.
      *
index f8ced49..7af80e0 100644 (file)
@@ -219,6 +219,34 @@ class core_statslib_testcase extends advanced_testcase {
         return $logfiles;
     }
 
+    /**
+     * Set of data for test_statlibs_get_base_weekly
+     *
+     * @return array Dates and timezones for which the first day of the week will be calculated
+     */
+    public function get_base_weekly_provider() {
+        return [
+            [
+                "startwday" => 0,
+                "timezone" => 'America/Chicago',
+                "timestart" => '18-03-2017 22:00',
+                "expected" => '12-03-2017 00:00:00'
+            ],
+            [
+                "startwday" => 0,
+                "timezone" => 'America/Chicago',
+                "date" => '25-03-2017 22:00',
+                "expected" => '19-03-2017 00:00:00'
+            ],
+            [
+                "startwday" => 1,
+                "timezone" => 'Atlantic/Canary',
+                "date" => '06-08-2018 22:00',
+                "expected" => '06-08-2018 00:00:00'
+            ],
+        ];
+    }
+
     /**
      * Compare the expected stats to those in the database.
      *
@@ -420,6 +448,27 @@ class core_statslib_testcase extends advanced_testcase {
         $this->assertEquals(24, ((1446526800 - 1446440400) / 60 ) / 60);
     }
 
+    /**
+     * Test the function that calculates the start of the week.
+     *
+     * @dataProvider get_base_weekly_provider
+     * @param int $startwday Day in which the week starts (Sunday = 0)
+     * @param string $timezone Default timezone
+     * @param string $timestart Date and time for which the first day of the week will be obtained
+     * @param string $expected Expected date of the first day of the week
+     */
+    public function test_statslib_get_base_weekly($startwday, $timezone, $timestart, $expected) {
+        $this->setTimezone($timezone);
+        $time = strtotime($timestart);
+        $expected = strtotime($expected);
+        set_config('calendar_startwday', $startwday);
+        set_config('statslastweekly', $time);
+
+        $weekstarttime = stats_get_base_weekly($time);
+
+        $this->assertEquals($expected, $weekstarttime);
+    }
+
     /**
      * Test the function that gets the action names.
      *
index 42eca59..b6a602c 100644 (file)
@@ -29,7 +29,7 @@ $string['audioextensions'] = 'Audio file extensions';
 $string['configaudiocssclass'] = 'A CSS class that will be added to the &lt;audio&gt; element.';
 $string['configaudioextensions'] = 'A comma-separated list of supported audio file extensions. VideoJS will try to use the browser\'s native video player when available, and fall back to a Flash player for other formats if Flash is supported by the browser and Flash fallback is enabled below.';
 $string['configlimitsize'] = 'If enabled, and width and height are not specified, the video will display with default width and height. Otherwise it will stretch to the maximum possible width.';
-$string['configrtmp'] = 'If enabled, links that start with rtmp:// will be handled by the plugin, irrespective of whether its extension is enabled in the supported extensions setting. Flash fallback must be enabled for RTMP to work.';
+$string['configrtmp'] = 'If enabled, links that start with rtmp:// will be handled by the plugin, irrespective of whether the extension is enabled in the Video file extensions (videoextensions) setting. Flash fallback must be enabled for RTMP to work.';
 $string['configvideocssclass'] = 'A CSS class that will be added to the &lt;video&gt; element. For example, the CSS class "vjs-big-play-centered" will place the play button in the middle. For details, including how to set a custom skin, see docs.videojs.com.';
 $string['configvideoextensions'] = 'A comma-separated list of supported video file extensions. VideoJS will try to use the browser\'s native video player when available, and fall back to a Flash player for other formats if Flash is supported by the browser and Flash fallback is enabled below.';
 $string['configyoutube'] = 'Use VideoJS to play YouTube videos. Note that YouTube playlists are not yet supported by VideoJS.';
index 28f7499..a7d2929 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_patcher.min.js and b/message/amd/build/message_drawer_view_conversation_patcher.min.js differ
index 5e51454..dd7556a 100644 (file)
@@ -322,11 +322,10 @@ function(
      * @return {Object} patch
      */
     var buildHeaderPatchTypePublic = function(state, newState) {
-        var totalMemberCount = newState.totalMemberCount;
+        var oldMemberCount = state.totalMemberCount;
+        var newMemberCount = newState.totalMemberCount;
 
-        if (totalMemberCount === null) {
-            return null;
-        } else {
+        if (oldMemberCount != newMemberCount) {
             return {
                 type: Constants.CONVERSATION_TYPES.PUBLIC,
                 showControls: true,
@@ -334,13 +333,15 @@ function(
                     id: newState.id,
                     name: newState.name,
                     subname: newState.subname,
-                    totalmembercount: totalMemberCount,
+                    totalmembercount: newState.totalMemberCount,
                     imageurl: newState.imageUrl,
                     isfavourite: newState.isFavourite,
                     // Don't show favouriting if we don't have a conversation.
                     showfavourite: newState.id !== null
                 }
             };
+        } else {
+            return null;
         }
     };
 
index a138fe4..af26d4b 100644 (file)
@@ -555,7 +555,7 @@ class api {
 
         $sql = "SELECT m.id as messageid, mc.id as id, mc.name as conversationname, mc.type as conversationtype, m.useridfrom,
                        m.smallmessage, m.fullmessage, m.fullmessageformat, m.fullmessagehtml, m.timecreated, mc.component,
-                       mc.itemtype, mc.itemid
+                       mc.itemtype, mc.itemid, mc.contextid
                   FROM {message_conversations} mc
             INNER JOIN {message_conversation_members} mcm
                     ON (mcm.conversationid = mc.id AND mcm.userid = :userid3)
@@ -750,6 +750,10 @@ class api {
         $unreadcounts = $DB->get_records_sql($unreadcountssql, [$userid, self::MESSAGE_ACTION_READ, self::MESSAGE_ACTION_DELETED,
             $userid, $userid]);
 
+        // Because we'll be calling format_string on each conversation name and passing contexts, we preload them here.
+        // This warms the cache and saves potentially hitting the DB once for each context fetch below.
+        \context_helper::preload_contexts_by_id(array_column($conversations, 'contextid'));
+
         // Now, create the final return structure.
         $arrconversations = [];
         foreach ($conversations as $conversation) {
@@ -768,7 +772,16 @@ class api {
 
             $conv = new \stdClass();
             $conv->id = $conversation->id;
-            $conv->name = $conversation->conversationname;
+
+            // Name should be formatted and depends on the context the conversation resides in.
+            // If not set, the context is always context_user.
+            if (is_null($conversation->contextid)) {
+                $convcontext = \context_user::instance($userid);
+            } else {
+                $convcontext = \context::instance_by_id($conversation->contextid);
+            }
+            $conv->name = format_string($conversation->conversationname, true, ['context' => $convcontext]);
+
             $conv->subname = $convextrafields[$conv->id]['subname'] ?? null;
             $conv->imageurl = $convextrafields[$conv->id]['imageurl'] ?? null;
             $conv->type = $conversation->conversationtype;
index b03af2a..c9c6631 100644 (file)
@@ -1118,8 +1118,8 @@ class core_message_external extends external_api {
         return new external_single_structure(
             array(
                 'id' => new external_value(PARAM_INT, 'The conversation id'),
-                'name' => new external_value(PARAM_NOTAGS, 'The conversation name, if set', VALUE_DEFAULT, null),
-                'subname' => new external_value(PARAM_NOTAGS, 'A subtitle for the conversation name, if set', VALUE_DEFAULT, null),
+                'name' => new external_value(PARAM_TEXT, 'The conversation name, if set', VALUE_DEFAULT, null),
+                'subname' => new external_value(PARAM_TEXT, 'A subtitle for the conversation name, if set', VALUE_DEFAULT, null),
                 'imageurl' => new external_value(PARAM_URL, 'A link to the conversation picture, if set', VALUE_DEFAULT, null),
                 'type' => new external_value(PARAM_INT, 'The type of the conversation (1=individual,2=group)'),
                 'membercount' => new external_value(PARAM_INT, 'Total number of conversation members'),
index 3335d7f..e7bb6de 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_message_email_upgrade($oldversion) {
     global $CFG;
 
-    // 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.
 
index 1a7a222..96536ec 100644 (file)
@@ -37,7 +37,7 @@ class message_output_email extends message_output {
      * @param object $eventdata the event data submitted by the message sender plus $eventdata->savedmessageid
      */
     function send_message($eventdata) {
-        global $CFG;
+        global $CFG, $DB;
 
         // skip any messaging suspended and deleted users
         if ($eventdata->userto->auth === 'nologin' or $eventdata->userto->suspended or $eventdata->userto->deleted) {
@@ -93,6 +93,10 @@ class message_output_email extends message_output {
         $result = email_to_user($recipient, $eventdata->userfrom, $eventdata->subject, $eventdata->fullmessage,
                                 $eventdata->fullmessagehtml, $attachment, $attachname, true, $replyto, $replytoname);
 
+        if ($result && $notification = $DB->get_record('notifications', ['id' => $eventdata->savedmessageid])) {
+            \core_message\api::mark_notification_as_read($notification);
+        }
+
         // Remove an attachment file if any.
         if (!empty($attachment) && file_exists($attachment)) {
             unlink($attachment);