Merge branch 'MDL-60962-master' of git://github.com/ryanwyllie/moodle
authorJun Pataleta <jun@moodle.com>
Wed, 3 Jan 2018 08:53:49 +0000 (16:53 +0800)
committerJun Pataleta <jun@moodle.com>
Wed, 3 Jan 2018 08:53:49 +0000 (16:53 +0800)
388 files changed:
.gitattributes
.travis.yml
admin/auth_config.php
admin/category.php
admin/environment.xml
admin/registration/confirmregistration.php
admin/registration/renewregistration.php
admin/roles/allow.php
admin/roles/classes/allow_assign_page.php
admin/roles/classes/allow_override_page.php
admin/roles/classes/allow_switch_page.php
admin/roles/classes/allow_view_page.php [new file with mode: 0644]
admin/roles/classes/define_role_table_advanced.php
admin/roles/classes/preset.php
admin/roles/classes/preset_form.php
admin/roles/define.php
admin/roles/managetabs.php
admin/roles/role_schema.xml
admin/roles/tests/preset_test.php
admin/search.php
admin/searchareas.php
admin/searchreindex.php [new file with mode: 0644]
admin/settings.php
admin/settings/grades.php
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/lang/en/tool_analytics.php
admin/tool/analytics/templates/models_list.mustache
admin/tool/behat/cli/util.php
admin/tool/customlang/db/upgrade.php
admin/tool/httpsreplace/tests/httpsreplace_test.php
admin/tool/log/db/upgrade.php
admin/tool/log/store/database/db/upgrade.php
admin/tool/log/store/standard/db/upgrade.php
admin/tool/lp/templates/manage_competencies_page.mustache
admin/tool/monitor/db/upgrade.php
admin/tool/recyclebin/tests/behat/basic_functionality.feature
admin/tool/task/cli/schedule_task.php
admin/tool/task/lang/en/tool_task.php
admin/tool/uploaduser/index.php
admin/tool/uploaduser/tests/behat/upload_users.feature
admin/tool/xmldb/actions/edit_field/edit_field.js
admin/tool/xmldb/actions/edit_field_save/edit_field_save.class.php
admin/tool/xmldb/lang/en/tool_xmldb.php
admin/user.php
analytics/classes/model.php
analytics/tests/model_test.php
auth/cas/db/upgrade.php
auth/db/auth.php
auth/db/lang/en/auth_db.php
auth/ldap/auth.php
auth/ldap/db/upgrade.php
auth/ldap/lang/en/auth_ldap.php
auth/ldap/settings.php
auth/manual/db/upgrade.php
auth/mnet/auth.php
auth/mnet/db/upgrade.php
auth/upgrade.txt
backup/cc/entity11.resource.class.php
backup/moodle2/tests/moodle2_test.php
backup/util/ui/backup_moodleform.class.php
badges/classes/observer.php
badges/criteria/award_criteria.php
badges/criteria/award_criteria_badge.php [new file with mode: 0644]
badges/criteria/award_criteria_manual.php
badges/tests/behat/award_badge.feature
badges/tests/behat/role_visibility.feature [new file with mode: 0644]
blocks/badges/db/upgrade.php
blocks/calendar_month/db/upgrade.php
blocks/calendar_upcoming/db/upgrade.php
blocks/community/db/upgrade.php
blocks/completionstatus/db/upgrade.php
blocks/course_list/block_course_list.php
blocks/course_summary/db/upgrade.php
blocks/globalsearch/block_globalsearch.php
blocks/html/db/upgrade.php
blocks/lp/classes/output/summary.php
blocks/myoverview/classes/output/main.php
blocks/navigation/db/upgrade.php
blocks/online_users/block_online_users.php
blocks/online_users/lang/en/block_online_users.php
blocks/online_users/tests/behat/block_online_users_course.feature
blocks/online_users/tests/behat/block_online_users_dashboard.feature
blocks/online_users/tests/behat/block_online_users_frontpage.feature
blocks/quiz_results/db/upgrade.php
blocks/recent_activity/db/upgrade.php
blocks/rss_client/db/upgrade.php
blocks/rss_client/templates/item.mustache
blocks/section_links/db/upgrade.php
blocks/selfcompletion/db/upgrade.php
blocks/settings/db/upgrade.php
blocks/tags/block_tags.php
calendar/amd/build/calendar.min.js
calendar/amd/src/calendar.js
calendar/externallib.php
calendar/lib.php
calendar/templates/month_detailed.mustache
cohort/classes/external/cohort_summary_exporter.php
competency/classes/api.php
competency/classes/competency.php
competency/classes/external.php
competency/classes/template_competency.php
competency/classes/user_competency_course.php
competency/classes/user_competency_plan.php
competency/tests/external_test.php
completion/classes/external.php
completion/criteria/completion_criteria_activity.php
course/edit_form.php
course/renderer.php
enrol/database/db/upgrade.php
enrol/flatfile/db/upgrade.php
enrol/guest/db/upgrade.php
enrol/imsenterprise/db/upgrade.php
enrol/imsenterprise/tests/imsenterprise_test.php
enrol/locallib.php
enrol/manual/db/upgrade.php
enrol/manual/tests/externallib_test.php
enrol/mnet/db/upgrade.php
enrol/paypal/db/upgrade.php
enrol/self/db/upgrade.php
enrol/tests/behat/role_visibility.feature [new file with mode: 0644]
enrol/tests/enrollib_test.php
enrol/upgrade.txt
enrol/users_forms.php
files/classes/converter.php
files/tests/converter_test.php
filter/mathjaxloader/db/upgrade.php
filter/mediaplugin/db/upgrade.php
filter/tex/db/upgrade.php
grade/export/grade_export_form.php
grade/grading/form/guide/db/upgrade.php
grade/grading/form/rubric/db/upgrade.php
grade/report/user/db/upgrade.php
grade/tests/edittreelib_test.php
grade/tests/importlib_test.php
grade/tests/querylib_test.php
grade/tests/report_graderlib_test.php
grade/tests/reportlib_test.php
grade/tests/reportuserlib_test.php
group/lib.php
group/tests/behat/role_visibility.feature [new file with mode: 0644]
install/lang/eu/admin.php
install/lang/he/moodle.php
install/lang/ig/langconfig.php [new file with mode: 0644]
install/lang/pcm/langconfig.php [new file with mode: 0644]
lang/en/admin.php
lang/en/auth.php
lang/en/backup.php
lang/en/badges.php
lang/en/form.php
lang/en/grades.php
lang/en/role.php
lang/en/search.php
lib/accesslib.php
lib/antivirus/clamav/db/upgrade.php
lib/authlib.php
lib/badgeslib.php
lib/classes/access/get_user_capability_course_helper.php
lib/classes/event/role_allow_view_updated.php [new file with mode: 0644]
lib/classes/external/exporter.php
lib/classes/hub/api.php
lib/classes/hub/registration.php
lib/classes/plugininfo/gradingform.php
lib/classes/session/redis.php
lib/classes/task/delete_unconfirmed_users_task.php
lib/completionlib.php
lib/dataformatlib.php
lib/db/events.php
lib/db/install.php
lib/db/install.xml
lib/db/upgrade.php
lib/ddl/mssql_sql_generator.php
lib/ddl/mysql_sql_generator.php
lib/ddl/oracle_sql_generator.php
lib/ddl/tests/ddl_test.php
lib/ddl/tests/fixtures/xmldb_table.xml
lib/deprecatedlib.php
lib/dml/moodle_database.php
lib/dml/mssql_native_moodle_database.php
lib/dml/oci_native_moodle_package.sql
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/enrollib.php
lib/externallib.php
lib/filelib.php
lib/filestorage/stored_file.php
lib/form/filemanager.js
lib/form/passwordunmask.php
lib/form/tags.php
lib/formslib.php
lib/moodlelib.php
lib/myprofilelib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.php
lib/statslib.php
lib/testing/generator/data_generator.php
lib/tests/accesslib_test.php
lib/tests/externallib_test.php
lib/tests/filelib_test.php
lib/tests/gradelib_test.php
lib/tests/weblib_test.php
lib/upgrade.txt
lib/weblib.php
lib/xmldb/xmldb_field.php
message/output/email/db/upgrade.php
message/output/jabber/db/upgrade.php
message/output/popup/db/upgrade.php
mod/assign/classes/output/grading_app.php
mod/assign/db/upgrade.php
mod/assign/feedback/comments/db/upgrade.php
mod/assign/feedback/editpdf/db/upgrade.php
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/locallib.php
mod/assign/feedback/editpdf/settings.php
mod/assign/feedback/file/db/upgrade.php
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/submission/comments/db/upgrade.php
mod/assign/submission/file/db/upgrade.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/db/upgrade.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/templates/grading_navigation.mustache
mod/assign/tests/behat/assign_group_override.feature
mod/assign/tests/behat/assign_user_override.feature
mod/assign/tests/locallib_test.php
mod/assignment/db/upgrade.php
mod/book/db/upgrade.php
mod/chat/backup/moodle2/backup_chat_stepslib.php
mod/chat/backup/moodle2/restore_chat_stepslib.php
mod/chat/chat_ajax.php
mod/chat/chatd.php
mod/chat/classes/external.php
mod/chat/db/install.xml
mod/chat/db/upgrade.php
mod/chat/lib.php
mod/chat/report.php
mod/chat/tests/format_message_test.php
mod/chat/version.php
mod/choice/db/upgrade.php
mod/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/renderer.php
mod/choice/tests/behat/publish_info.feature [new file with mode: 0644]
mod/choice/tests/lib_test.php
mod/choice/view.php
mod/data/db/upgrade.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/tests/lib_test.php
mod/feedback/db/upgrade.php
mod/feedback/db/upgradelib.php [deleted file]
mod/feedback/lang/en/feedback.php
mod/feedback/show_entries.php
mod/feedback/styles.css
mod/feedback/tests/behat/anonymous.feature
mod/feedback/tests/behat/coursemapping.feature
mod/feedback/tests/behat/multichoice.feature
mod/feedback/tests/behat/multipleattempt.feature
mod/feedback/tests/behat/non_anonymous.feature
mod/feedback/tests/behat/question_types.feature
mod/feedback/tests/behat/question_types_non_anon.feature
mod/feedback/tests/upgradelib_test.php [deleted file]
mod/feedback/upgrade.txt
mod/feedback/version.php
mod/feedback/view.php
mod/folder/db/upgrade.php
mod/forum/classes/search/post.php
mod/forum/db/upgrade.php
mod/forum/tests/search_test.php
mod/glossary/db/upgrade.php
mod/imscp/db/upgrade.php
mod/label/db/upgrade.php
mod/lesson/db/upgrade.php
mod/lesson/essay.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lesson/tests/lib_test.php
mod/lti/backup/moodle2/backup_lti_stepslib.php
mod/lti/backup/moodle2/restore_lti_stepslib.php
mod/lti/db/upgrade.php
mod/lti/tests/behat/addtype.feature
mod/lti/tests/behat/backup_restore.feature [new file with mode: 0644]
mod/lti/view.php
mod/page/db/upgrade.php
mod/quiz/classes/output/edit_renderer.php
mod/quiz/db/upgrade.php
mod/quiz/edit.php
mod/quiz/index.php
mod/quiz/mod_form.php
mod/quiz/report/overview/db/upgrade.php
mod/quiz/report/statistics/db/upgrade.php
mod/quiz/styles.css
mod/quiz/tests/behat/editing_add.feature
mod/quiz/tests/behat/settings_form_fields_disableif.feature
mod/resource/db/upgrade.php
mod/scorm/db/upgrade.php
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/tests/lib_test.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/picasa/db/upgrade.php
question/behaviour/manualgraded/db/upgrade.php
question/type/calculated/db/upgrade.php
question/type/calculated/questiontype.php
question/type/ddmarker/db/upgrade.php
question/type/essay/db/upgrade.php
question/type/match/db/upgrade.php
question/type/multianswer/db/upgrade.php
question/type/multianswer/questiontype.php
question/type/multichoice/db/upgrade.php
question/type/numerical/db/install.xml
question/type/numerical/db/upgrade.php
question/type/numerical/version.php
question/type/random/db/upgrade.php
question/type/randomsamatch/db/upgrade.php
question/type/shortanswer/db/upgrade.php
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-debug.js
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-min.js
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager.js
question/yui/src/qbankmanager/js/qbankmanager.js
report/outline/db/access.php
report/outline/lang/en/report_outline.php
report/outline/lib.php
report/outline/tests/lib_test.php
report/outline/version.php
report/progress/index.php
report/stats/locallib.php
repository/boxnet/db/upgrade.php
repository/dropbox/db/upgrade.php
repository/googledocs/db/upgrade.php
repository/lib.php
repository/picasa/db/upgrade.php
search/classes/base.php
search/classes/base_block.php
search/classes/base_mod.php
search/classes/engine.php
search/classes/manager.php
search/classes/output/form/search.php
search/classes/output/renderer.php
search/engine/solr/classes/engine.php
search/engine/solr/classes/schema.php
search/engine/solr/tests/engine_test.php
search/engine/solr/version.php
search/index.php
search/templates/index_requests.mustache [new file with mode: 0644]
search/tests/base_activity_test.php
search/tests/base_block_test.php
search/tests/base_test.php
search/tests/behat/behat_search.php
search/tests/behat/search_query.feature
search/tests/fixtures/mock_search_area.php
search/tests/fixtures/testable_core_search.php
search/tests/generator/lib.php
search/tests/manager_test.php
search/upgrade.txt
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/question.scss
theme/boost/scss/moodle/search.scss
theme/boost/templates/core_form/element-tags-inline.mustache
theme/boost/templates/core_form/element-tags.mustache
theme/boost/templates/mod_assign/grading_navigation.mustache
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/search.less
theme/bootstrapbase/style/moodle.css
theme/more/db/upgrade.php
user/classes/output/user_roles_editable.php
user/classes/participants_table.php
user/profile/lib.php
user/renderer.php
version.php
webservice/xmlrpc/lib.php
webservice/xmlrpc/tests/lib_test.php

index fb39fe0..8afacce 100644 (file)
@@ -1,4 +1,5 @@
 **/yui/build/** -diff
 **/amd/build/** -diff
+lib/dml/oci_native_moodle_package.sql text eol=lf
 theme/bootstrapbase/style/editor.css -diff
 theme/bootstrapbase/style/moodle.css -diff
index 5139a7c..9e436f5 100644 (file)
@@ -23,8 +23,9 @@ addons:
     - mysql-client-core-5.6
     - mysql-client-5.6
 
-services:
-    - redis-server
+# Redis tests are currently failing on php 7.2 due to https://bugs.php.net/bug.php?id=75628
+# services:
+#     - redis-server
 
 env:
     # Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
@@ -99,8 +100,9 @@ install:
             fi
 
             # Enable Redis.
-            echo 'extension="redis.so"' > /tmp/redis.ini
-            phpenv config-add /tmp/redis.ini
+            # Redis tests are currently failing on php 7.2 due to https://bugs.php.net/bug.php?id=75628
+            # echo 'extension="redis.so"' > /tmp/redis.ini
+            # phpenv config-add /tmp/redis.ini
 
             # Install composer dependencies.
             # We need --no-interaction in case we hit API limits for composer. This causes it to fall back to a standard clone.
@@ -171,10 +173,11 @@ before_script:
         mkdir -p "$HOME"/roots/phpunit
 
         # The phpunit dataroot and prefix..
+        # Redis tests are currently failing on php 7.2 due to https://bugs.php.net/bug.php?id=75628
+        # -e "/require_once/i \\define('TEST_SESSION_REDIS_HOST', '127.0.0.1');" \
         sed -i \
           -e "/require_once/i \\\$CFG->phpunit_dataroot = '\/home\/travis\/roots\/phpunit';" \
           -e "/require_once/i \\\$CFG->phpunit_prefix = 'p_';" \
-          -e "/require_once/i \\define('TEST_SESSION_REDIS_HOST', '127.0.0.1');" \
           config.php ;
 
         # Initialise PHPUnit for Moodle.
index 6c3e315..3ee8ebc 100644 (file)
@@ -165,6 +165,12 @@ function print_auth_lock_options($auth, $user_fields, $helptext, $retrieveopts,
             // If custom field then pick name from database.
             $fieldshortname = str_replace('profile_field_', '', $fieldname);
             $fieldname = $customfieldname[$fieldshortname]->name;
+            if (core_text::strlen($fieldshortname) > 67) {
+                // If custom profile field name is longer than 67 characters we will not be able to store the setting
+                // such as 'field_updateremote_profile_field_NOTSOSHORTSHORTNAME' in the database because the character
+                // limit for the setting name is 100.
+                continue;
+            }
         } elseif ($fieldname == 'url') {
             $fieldname = get_string('webpage');
         } else {
index 0a80394..c87022b 100644 (file)
@@ -54,14 +54,16 @@ $statusmsg = '';
 $errormsg  = '';
 
 if ($data = data_submitted() and confirm_sesskey()) {
-    if (admin_write_settings($data)) {
-        $statusmsg = get_string('changessaved');
-    }
-
+    $count = admin_write_settings($data);
     if (empty($adminroot->errors)) {
-        switch ($return) {
-            case 'site': redirect("$CFG->wwwroot/");
-            case 'admin': redirect("$CFG->wwwroot/$CFG->admin/");
+        // No errors. Did we change any setting?  If so, then indicate success.
+        if ($count) {
+            $statusmsg = get_string('changessaved');
+        } else {
+            switch ($return) {
+                case 'site': redirect("$CFG->wwwroot/");
+                case 'admin': redirect("$CFG->wwwroot/$CFG->admin/");
+            }
         }
     } else {
         $errormsg = get_string('errorwithsettings', 'admin');
index 1f2f001..2b8d4ef 100644 (file)
       </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
+  <MOODLE version="3.5" requires="3.1">
+    <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.5.31" />
+      <VENDOR name="postgres" version="9.3" />
+      <VENDOR name="mssql" version="10.0" />
+      <VENDOR name="oracle" version="10.2" />
+    </DATABASE>
+    <PHP version="7.0.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_CHECKS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index 6b20e8d..7c9d422 100644 (file)
@@ -46,6 +46,14 @@ $error = optional_param('error', '', PARAM_ALPHANUM);
 admin_externalpage_setup('registrationmoodleorg');
 
 if ($url !== HUB_MOODLEORGHUBURL) {
+    // Allow other plugins to confirm registration on hubs other than moodle.net . Plugins implementing this
+    // callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
+    $callbacks = get_plugins_with_function('hub_registration');
+    foreach ($callbacks as $plugintype => $plugins) {
+        foreach ($plugins as $plugin => $callback) {
+            $callback('confirm');
+        }
+    }
     throw new moodle_exception('errorotherhubsnotsupported', 'hub');
 }
 
index 7926e0f..84e727b 100644 (file)
@@ -40,6 +40,14 @@ $token = optional_param('token', '', PARAM_TEXT);
 admin_externalpage_setup('registrationmoodleorg');
 
 if ($url !== HUB_MOODLEORGHUBURL) {
+    // Allow other plugins to renew registration on hubs other than moodle.net . Plugins implementing this
+    // callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
+    $callbacks = get_plugins_with_function('hub_registration');
+    foreach ($callbacks as $plugintype => $plugins) {
+        foreach ($plugins as $plugin => $callback) {
+            $callback('renew');
+        }
+    }
     throw new moodle_exception('errorotherhubsnotsupported', 'hub');
 }
 
index 916e6c2..71e9bcb 100644 (file)
@@ -29,7 +29,8 @@ $mode = required_param('mode', PARAM_ALPHANUMEXT);
 $classformode = array(
     'assign' => 'core_role_allow_assign_page',
     'override' => 'core_role_allow_override_page',
-    'switch' => 'core_role_allow_switch_page'
+    'switch' => 'core_role_allow_switch_page',
+    'view' => 'core_role_allow_view_page'
 );
 if (!isset($classformode[$mode])) {
     print_error('invalidmode', '', '', $mode);
@@ -58,6 +59,9 @@ if (optional_param('submit', false, PARAM_BOOL) && data_submitted() && confirm_s
         case 'switch':
             $event = \core\event\role_allow_switch_updated::create(array('context' => $syscontext));
             break;
+        case 'view':
+            $event = \core\event\role_allow_view_updated::create(array('context' => $syscontext));
+            break;
     }
     if ($event) {
         $event->trigger();
index 8d31cc0..a89228f 100644 (file)
@@ -33,7 +33,7 @@ class core_role_allow_assign_page extends core_role_allow_role_page {
     }
 
     protected function set_allow($fromroleid, $targetroleid) {
-        allow_assign($fromroleid, $targetroleid);
+        core_role_set_assign_allowed($fromroleid, $targetroleid);
     }
 
     protected function get_cell_tooltip($fromrole, $targetrole) {
index a277abb..4b160ee 100644 (file)
@@ -33,7 +33,7 @@ class core_role_allow_override_page extends core_role_allow_role_page {
     }
 
     protected function set_allow($fromroleid, $targetroleid) {
-        allow_override($fromroleid, $targetroleid);
+        core_role_set_override_allowed($fromroleid, $targetroleid);
     }
 
     protected function get_cell_tooltip($fromrole, $targetrole) {
index 934bda3..5b22e1e 100644 (file)
@@ -41,7 +41,7 @@ class core_role_allow_switch_page extends core_role_allow_role_page {
     }
 
     protected function set_allow($fromroleid, $targetroleid) {
-        allow_switch($fromroleid, $targetroleid);
+        core_role_set_switch_allowed($fromroleid, $targetroleid);
     }
 
     protected function is_allowed_target($targetroleid) {
diff --git a/admin/roles/classes/allow_view_page.php b/admin/roles/classes/allow_view_page.php
new file mode 100644 (file)
index 0000000..f1a1031
--- /dev/null
@@ -0,0 +1,77 @@
+<?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/>.
+
+/**
+ * Role view matrix.
+ *
+ * @package    core_role
+ * @copyright  2016 onwards Andrew Hancox <andrewdchancox@googlemail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Subclass of role_allow_role_page for the Allow views tab.
+ *
+ * @package    core_role
+ * @copyright  2016 onwards Andrew Hancox <andrewdchancox@googlemail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_role_allow_view_page extends core_role_allow_role_page {
+    /** @var array */
+    protected $allowedtargetroles;
+
+    /**
+     * core_role_allow_view_page constructor.
+     */
+    public function __construct() {
+        parent::__construct('role_allow_view', 'allowview');
+    }
+
+
+    /**
+     * Allow from role to view target role.
+     * @param int $fromroleid
+     * @param int $targetroleid
+     */
+    protected function set_allow($fromroleid, $targetroleid) {
+        core_role_set_view_allowed($fromroleid, $targetroleid);
+    }
+
+    /**
+     * Get tool tip for cell.
+     * @param string $fromrole
+     * @param string $targetrole
+     * @return string
+     * @throws \coding_exception
+     */
+    protected function get_cell_tooltip($fromrole, $targetrole) {
+        $a = new stdClass;
+        $a->fromrole = $fromrole->localname;
+        $a->targetrole = $targetrole->localname;
+        return get_string('allowroletoview', 'core_role', $a);
+    }
+
+    /**
+     * Get intro text for role allow view page.
+     * @return string
+     * @throws \coding_exception
+     */
+    public function get_intro_text() {
+        return get_string('configallowview', 'core_admin');
+    }
+}
index bcf6a49..957fe85 100644 (file)
@@ -42,6 +42,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
     protected $allowassign;
     protected $allowoverride;
     protected $allowswitch;
+    protected $allowview;
 
     public function __construct($context, $roleid) {
         $this->roleid = $roleid;
@@ -72,6 +73,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             $this->allowassign = array_keys($this->get_allow_roles_list('assign'));
             $this->allowoverride = array_keys($this->get_allow_roles_list('override'));
             $this->allowswitch = array_keys($this->get_allow_roles_list('switch'));
+            $this->allowview = array_keys($this->get_allow_roles_list('view'));
 
         } else {
             $this->role = new stdClass;
@@ -83,6 +85,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             $this->allowassign = array();
             $this->allowoverride = array();
             $this->allowswitch = array();
+            $this->allowview = array();
         }
         parent::load_current_permissions();
     }
@@ -162,6 +165,10 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         if (!is_null($allow)) {
             $this->allowswitch = $allow;
         }
+        $allow = optional_param_array('allowview', null, PARAM_INT);
+        if (!is_null($allow)) {
+            $this->allowview = $allow;
+        }
 
         // Now read the permissions for each capability.
         parent::read_submitted_permissions();
@@ -178,7 +185,8 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
      * @param int $roleid role id or 0 for no role
      * @param array $options array with following keys:
      *      'name', 'shortname', 'description', 'permissions', 'archetype',
-     *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
+     *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
+     *      'allowview'
      */
     public function force_duplicate($roleid, array $options) {
         global $DB;
@@ -215,6 +223,9 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             if ($options['allowswitch']) {
                 $this->allowswitch = array();
             }
+            if ($options['allowview']) {
+                $this->allowview = array();
+            }
 
             if ($options['permissions']) {
                 foreach ($this->capabilities as $capid => $cap) {
@@ -260,6 +271,9 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         if ($options['allowswitch']) {
             $this->allowswitch = array_keys($this->get_allow_roles_list('switch', $roleid));
         }
+        if ($options['allowview']) {
+            $this->allowview = array_keys($this->get_allow_roles_list('view', $roleid));
+        }
 
         if ($options['permissions']) {
             $this->permissions = $DB->get_records_menu('role_capabilities',
@@ -280,7 +294,8 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
      * @param string $archetype
      * @param array $options array with following keys:
      *      'name', 'shortname', 'description', 'permissions', 'archetype',
-     *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
+     *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
+     *      'allowview'
      */
     public function force_archetype($archetype, array $options) {
         $archetypes = get_role_archetypes();
@@ -321,6 +336,9 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         if ($options['allowswitch']) {
             $this->allowswitch = get_default_role_archetype_allows('switch', $archetype);
         }
+        if ($options['allowview']) {
+            $this->allowview = get_default_role_archetype_allows('view', $archetype);
+        }
 
         if ($options['permissions']) {
             $defaultpermissions = get_default_capabilities($archetype);
@@ -340,7 +358,8 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
      * @param string $xml
      * @param array $options array with following keys:
      *      'name', 'shortname', 'description', 'permissions', 'archetype',
-     *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
+     *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
+     *      'allowview'
      */
     public function force_preset($xml, array $options) {
         if (!$info = core_role_preset::parse_preset($xml)) {
@@ -377,7 +396,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             }
         }
 
-        foreach (array('assign', 'override', 'switch') as $type) {
+        foreach (array('assign', 'override', 'switch', 'view') as $type) {
             if ($options['allow'.$type]) {
                 if (isset($info['allow'.$type])) {
                     $this->{'allow'.$type} = $info['allow'.$type];
@@ -438,6 +457,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         $this->save_allow('assign');
         $this->save_allow('override');
         $this->save_allow('switch');
+        $this->save_allow('view');
 
         // Permissions.
         parent::save_changes();
@@ -449,7 +469,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         $current = array_keys($this->get_allow_roles_list($type));
         $wanted = $this->{'allow'.$type};
 
-        $addfunction = 'allow_'.$type;
+        $addfunction = "core_role_set_{$type}_allowed";
         $deltable = 'role_allow_'.$type;
         $field = 'allow'.$type;
 
@@ -523,7 +543,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
     protected function get_allow_roles_list($type, $roleid = null) {
         global $DB;
 
-        if ($type !== 'assign' and $type !== 'switch' and $type !== 'override') {
+        if ($type !== 'assign' and $type !== 'switch' and $type !== 'override' and $type !== 'view') {
             debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
             return array();
         }
@@ -547,11 +567,11 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
     /**
      * Returns an array of roles with the allowed type.
      *
-     * @param string $type Must be one of: assign, switch, or override.
+     * @param string $type Must be one of: assign, switch, override or view.
      * @return array Am array of role names with the allowed type
      */
     protected function get_allow_role_control($type) {
-        if ($type !== 'assign' and $type !== 'switch' and $type !== 'override') {
+        if ($type !== 'assign' and $type !== 'switch' and $type !== 'override' and $type !== 'view') {
             debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
             return '';
         }
@@ -641,6 +661,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         $this->print_field('menuallowassign', get_string('allowassign', 'core_role'), $this->get_allow_role_control('assign'));
         $this->print_field('menuallowoverride', get_string('allowoverride', 'core_role'), $this->get_allow_role_control('override'));
         $this->print_field('menuallowswitch', get_string('allowswitch', 'core_role'), $this->get_allow_role_control('switch'));
+        $this->print_field('menuallowview', get_string('allowview', 'core_role'), $this->get_allow_role_control('view'));
         if ($risks = $this->get_role_risks_info()) {
             $this->print_field('', get_string('rolerisks', 'core_role'), $risks);
         }
index 256f635..b994342 100644 (file)
@@ -84,7 +84,7 @@ class core_role_preset {
             $contextlevels->appendChild($dom->createElement('level', $name));
         }
 
-        foreach (array('assign', 'override', 'switch') as $type) {
+        foreach (array('assign', 'override', 'switch', 'view') as $type) {
             $allows = $dom->createElement('allow'.$type);
             $top->appendChild($allows);
             $records = $DB->get_records('role_allow_'.$type, array('roleid'=>$roleid), "allow$type ASC");
@@ -205,7 +205,7 @@ class core_role_preset {
             }
         }
 
-        foreach (array('assign', 'override', 'switch') as $type) {
+        foreach (array('assign', 'override', 'switch', 'view') as $type) {
             $values = self::get_node_children_values($dom, '/role/allow'.$type, 'shortname');
             if (!isset($values)) {
                 $info['allow'.$type] = null;
index ff2024d..426c3d8 100644 (file)
@@ -79,6 +79,7 @@ class core_role_preset_form extends moodleform {
             $mform->addElement('advcheckbox', 'allowassign', get_string('allowassign', 'core_role'));
             $mform->addElement('advcheckbox', 'allowoverride', get_string('allowoverride', 'core_role'));
             $mform->addElement('advcheckbox', 'allowswitch', get_string('allowswitch', 'core_role'));
+            $mform->addElement('advcheckbox', 'allowview', get_string('allowview', 'core_role'));
             $mform->addElement('advcheckbox', 'permissions', get_string('permissions', 'core_role'));
         }
 
index d670bae..de3eac1 100644 (file)
@@ -103,7 +103,8 @@ if ($action === 'add' and $resettype !== 'none') {
             'contextlevels' => 1,
             'allowassign'   => 1,
             'allowoverride' => 1,
-            'allowswitch'   => 1);
+            'allowswitch'   => 1,
+            'allowview'   => 1);
         if ($showadvanced) {
             $definitiontable = new core_role_define_role_table_advanced($systemcontext, 0);
         } else {
@@ -150,7 +151,8 @@ if ($action === 'add' and $resettype !== 'none') {
             'contextlevels' => $data->contextlevels,
             'allowassign'   => $data->allowassign,
             'allowoverride' => $data->allowoverride,
-            'allowswitch'   => $data->allowswitch);
+            'allowswitch'   => $data->allowswitch,
+            'allowview'     => $data->allowview);
         if ($showadvanced) {
             $definitiontable = new core_role_define_role_table_advanced($systemcontext, $roleid);
         } else {
index 78f9f75..a9a8385 100644 (file)
@@ -29,6 +29,7 @@ $toprow[] = new tabobject('manage', new moodle_url('/admin/roles/manage.php'), g
 $toprow[] = new tabobject('assign', new moodle_url('/admin/roles/allow.php', array('mode'=>'assign')), get_string('allowassign', 'core_role'));
 $toprow[] = new tabobject('override', new moodle_url('/admin/roles/allow.php', array('mode'=>'override')), get_string('allowoverride', 'core_role'));
 $toprow[] = new tabobject('switch', new moodle_url('/admin/roles/allow.php', array('mode'=>'switch')), get_string('allowswitch', 'core_role'));
+$toprow[] = new tabobject('view', new moodle_url('/admin/roles/allow.php', ['mode' => 'view']), get_string('allowview', 'core_role'));
 
 echo $OUTPUT->tabtree($toprow, $currenttab);
 
index f0a33c9..bb3de89 100644 (file)
@@ -11,6 +11,7 @@
                 <xs:element ref="allowassign" minOccurs="0"/>
                 <xs:element ref="allowoverride" minOccurs="0"/>
                 <xs:element ref="allowswitch" minOccurs="0"/>
+                <xs:element ref="allowview" minOccurs="0"/>
                 <xs:element ref="permissions" minOccurs="0"/>
             </xs:sequence>
         </xs:complexType>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
+    <xs:element name="allowview">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="unbounded" minOccurs="0" ref="shortname"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
     <xs:element name="permissions">
         <xs:complexType>
             <xs:sequence>
index 0899480..4ab0ebc 100644 (file)
@@ -44,7 +44,7 @@ class core_role_preset_testcase extends advanced_testcase {
             $contextlevels = get_role_contextlevels($role->id);
             $this->assertEquals(array_values($contextlevels), array_values($info['contextlevels']));
 
-            foreach (array('assign', 'override', 'switch') as $type) {
+            foreach (array('assign', 'override', 'switch', 'view') as $type) {
                 $records = $DB->get_records('role_allow_'.$type, array('roleid'=>$role->id), "allow$type ASC");
                 $allows = array();
                 foreach ($records as $record) {
index dfb6043..5539517 100644 (file)
@@ -32,15 +32,16 @@ $focus = '';
 // now we'll deal with the case that the admin has submitted the form with changed settings
 if ($data = data_submitted() and confirm_sesskey() and isset($data->action) and $data->action == 'save-settings') {
     require_capability('moodle/site:config', $context);
-    if (admin_write_settings($data)) {
-        redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
-    }
-
+    $count = admin_write_settings($data);
     if (!empty($adminroot->errors)) {
         $errormsg = get_string('errorwithsettings', 'admin');
         $firsterror = reset($adminroot->errors);
         $focus = $firsterror->id;
     } else {
+        // No errors. Did we change any setting? If so, then redirect with success.
+        if ($count) {
+            redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
+        }
         redirect($PAGE->url);
     }
 }
index 9ce7af6..9cac439 100644 (file)
@@ -132,7 +132,17 @@ foreach ($searchareas as $area) {
                 $laststatus = '';
             }
             $columns[] = $laststatus;
-            $columns[] = html_writer::link(admin_searcharea_action_url('delete', $areaid), 'Delete index');
+            $accesshide = html_writer::span($area->get_visible_name(), 'accesshide');
+            $actions = [];
+            $actions[] = $OUTPUT->pix_icon('t/delete', '') .
+                    html_writer::link(admin_searcharea_action_url('delete', $areaid),
+                    get_string('deleteindex', 'search', $accesshide));
+            if ($area->supports_get_document_recordset()) {
+                $actions[] = $OUTPUT->pix_icon('i/reload', '') . html_writer::link(
+                        new moodle_url('searchreindex.php', ['areaid' => $areaid]),
+                        get_string('gradualreindex', 'search', $accesshide));
+            }
+            $columns[] = html_writer::alist($actions, ['class' => 'unstyled list-unstyled']);
 
         } else {
             $blankrow = new html_table_cell(get_string('searchnotavailable', 'admin'));
@@ -165,6 +175,13 @@ echo $OUTPUT->single_button(admin_searcharea_action_url('deleteall'), get_string
 echo $OUTPUT->box_end();
 
 echo html_writer::table($table);
+
+if (empty($searchmanagererror)) {
+    // Show information about queued index requests for specific contexts.
+    $searchrenderer = $PAGE->get_renderer('core_search');
+    echo $searchrenderer->render_index_requests_info($searchmanager->get_index_requests_info());
+}
+
 echo $OUTPUT->footer();
 
 /**
diff --git a/admin/searchreindex.php b/admin/searchreindex.php
new file mode 100644 (file)
index 0000000..9fa7a3c
--- /dev/null
@@ -0,0 +1,87 @@
+<?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/>.
+
+/**
+ * Adds a search area to the queue for indexing.
+ *
+ * @package core_search
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('NO_OUTPUT_BUFFERING', true);
+
+require(__DIR__ . '/../config.php');
+
+// Check access.
+require_once($CFG->libdir . '/adminlib.php');
+
+admin_externalpage_setup('searchareas', '', null, (new moodle_url('/admin/searchreindex.php'))->out(false));
+
+// Get area parameter and check it exists.
+$areaid = required_param('areaid', PARAM_ALPHAEXT);
+$area = \core_search\manager::get_search_area($areaid);
+if ($area === false) {
+    throw new moodle_exception('invalidrequest');
+}
+$areaname = $area->get_visible_name();
+
+// Start page output.
+$heading = get_string('gradualreindex', 'search', '');
+$PAGE->set_title($PAGE->title . ': ' . $heading);
+$PAGE->navbar->add($heading);
+echo $OUTPUT->header();
+echo $OUTPUT->heading($heading);
+
+// If sesskey is supplied, actually carry out the action.
+if (optional_param('sesskey', '', PARAM_ALPHANUM)) {
+    require_sesskey();
+
+    // Get all contexts for search area. This query can take time in large cases.
+    \core_php_time_limit::raise(0);
+    $contextiterator = $area->get_contexts_to_reindex();
+
+    $progress = new \core\progress\display_if_slow('');
+    $progress->start_progress($areaname);
+
+    // Request reindexing for each context (with low priority).
+    $count = 0;
+    foreach ($contextiterator as $context) {
+        \core_php_time_limit::raise(30);
+        \core_search\manager::request_index($context, $area->get_area_id(),
+                \core_search\manager::INDEX_PRIORITY_REINDEXING);
+        $progress->progress();
+        $count++;
+    }
+
+    // Unset the iterator which should close the recordset (if there is one).
+    unset($contextiterator);
+
+    $progress->end_progress();
+
+    $a = (object)['name' => html_writer::tag('strong', $areaname), 'count' => $count];
+    echo $OUTPUT->box(get_string('gradualreindex_queued', 'search', $a));
+
+    echo $OUTPUT->continue_button(new moodle_url('/admin/searchareas.php'));
+} else {
+    // Display confirmation prompt.
+    echo $OUTPUT->confirm(get_string('gradualreindex_confirm', 'search', html_writer::tag('strong', $areaname)),
+            new single_button(new moodle_url('/admin/searchreindex.php', ['areaid' => $areaid,
+                'sesskey' => sesskey()]), get_string('continue'), 'post', true),
+            new single_button(new moodle_url('/admin/searchareas.php'), get_string('cancel'), 'get'));
+}
+
+echo $OUTPUT->footer();
index 72f7f9c..6292240 100644 (file)
@@ -39,11 +39,15 @@ $statusmsg = '';
 $errormsg  = '';
 
 if ($data = data_submitted() and confirm_sesskey()) {
-    if (admin_write_settings($data)) {
-        redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
-    }
 
+    $count = admin_write_settings($data);
+    // Regardless of whether any setting change was written (a positive count), check validation errors for those that didn't.
     if (empty($adminroot->errors)) {
+        // No errors. Did we change any setting? If so, then redirect with success.
+        if ($count) {
+            redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
+        }
+        // We didn't change a setting.
         switch ($return) {
             case 'site': redirect("$CFG->wwwroot/");
             case 'admin': redirect("$CFG->wwwroot/$CFG->admin/");
index 8dfceea..8598f54 100644 (file)
@@ -42,6 +42,9 @@ if (has_capability('moodle/grade:manage', $systemcontext)
         // enable publishing in exports/imports
         $temp->add(new admin_setting_configcheckbox('gradepublishing', new lang_string('gradepublishing', 'grades'), new lang_string('gradepublishing_help', 'grades'), 0));
 
+        $temp->add(new admin_setting_configcheckbox('grade_export_exportfeedback', new lang_string('exportfeedback', 'grades'),
+                                                  new lang_string('exportfeedback_desc', 'grades'), 0));
+
         $temp->add(new admin_setting_configselect('grade_export_displaytype', new lang_string('gradeexportdisplaytype', 'grades'),
                                                   new lang_string('gradeexportdisplaytype_desc', 'grades'), GRADE_DISPLAY_TYPE_REAL, $display_types));
 
index 0e8d4e8..cbd0a60 100644 (file)
@@ -119,6 +119,9 @@ class models_list implements \renderable, \templatable {
                     debugging("The time splitting method '{$modeldata->timesplitting}' should include a '{$identifier}_help'
                         string to describe its purpose.", DEBUG_DEVELOPER);
                 }
+            } else {
+                $helpicon = new \help_icon('timesplittingnotdefined', 'tool_analytics');
+                $modeldata->timesplittinghelp = $helpicon->export_for_template($output);
             }
 
             // Has this model generated predictions?.
@@ -207,19 +210,22 @@ class models_list implements \renderable, \templatable {
             }
 
             // Enable / disable.
-            if ($model->is_enabled()) {
-                $action = 'disable';
-                $text = get_string('disable');
-                $icontype = 't/block';
-            } else {
-                $action = 'enable';
-                $text = get_string('enable');
-                $icontype = 'i/checked';
+            if ($model->is_enabled() || !empty($modeldata->timesplitting)) {
+                // If there is no timesplitting method set, the model can not be enabled.
+                if ($model->is_enabled()) {
+                    $action = 'disable';
+                    $text = get_string('disable');
+                    $icontype = 't/block';
+                } else {
+                    $action = 'enable';
+                    $text = get_string('enable');
+                    $icontype = 'i/checked';
+                }
+                $urlparams['action'] = $action;
+                $url = new \moodle_url('model.php', $urlparams);
+                $icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
+                $actionsmenu->add($icon);
             }
-            $urlparams['action'] = $action;
-            $url = new \moodle_url('model.php', $urlparams);
-            $icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
-            $actionsmenu->add($icon);
 
             // Export training data.
             if (!$model->is_static() && $model->is_trained()) {
index 68ca347..47def2b 100644 (file)
@@ -85,6 +85,8 @@ $string['previouspage'] = 'Previous page';
 $string['samestartdate'] = 'Current start date is good';
 $string['sameenddate'] = 'Current end date is good';
 $string['target'] = 'Target';
+$string['timesplittingnotdefined'] = 'Time splitting is not defined.';
+$string['timesplittingnotdefined_help'] = 'You need to select a time-splitting method before enabling the model.';
 $string['trainandpredictmodel'] = 'Training model and calculating predictions';
 $string['trainingprocessfinished'] = 'Training process finished';
 $string['trainingresults'] = 'Training results';
index 8eb6211..efdc4ca 100644 (file)
                     {{/timesplitting}}
                     {{^timesplitting}}
                         {{#str}}notdefined, tool_analytics{{/str}}
+                        {{#timesplittinghelp}}
+                            {{>core/help_icon}}
+                        {{/timesplittinghelp}}
                     {{/timesplitting}}
                 </td>
                 <td>
index 8efa4ed..e88125b 100644 (file)
@@ -398,7 +398,7 @@ function print_combined_install_output($processes) {
     // Show process name in first row.
     foreach ($processes as $name => $process) {
         // If we don't have enough space to show full run name then show runX.
-        if ($lengthofprocessline < strlen($name + 2)) {
+        if ($lengthofprocessline < strlen($name) + 2) {
             $name = substr($name, -5);
         }
         // One extra padding as we are adding | separator for rest of the data.
index 1c04587..a7ff6a3 100644 (file)
@@ -29,9 +29,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_customlang_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index c10ea2f..e3599c6 100644 (file)
@@ -47,12 +47,12 @@ class httpsreplace_test extends \advanced_testcase {
             "Test image from another site should be replaced" => [
                 "content" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', false) . '">',
                 "outputregex" => '/UPDATE/',
-                "expectedcontent" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', true) . '">',
+                "expectedcontent" => '<img src="' . $this->get_converted_http_link('/test.jpg') . '">',
             ],
             "Test object from another site should be replaced" => [
                 "content" => '<object data="' . $this->getExternalTestFileUrl('/test.swf', false) . '">',
                 "outputregex" => '/UPDATE/',
-                "expectedcontent" => '<object data="' . $this->getExternalTestFileUrl('/test.swf', true) . '">',
+                "expectedcontent" => '<object data="' . $this->get_converted_http_link('/test.swf') . '">',
             ],
             "Test image from a site with international name should be replaced" => [
                 "content" => '<img src="http://中国互联网络信息中心.中国/logosy/201706/W01.png">',
@@ -82,7 +82,7 @@ class httpsreplace_test extends \advanced_testcase {
             "Search for params should be case insensitive" => [
                 "content" => '<object DATA="' . $this->getExternalTestFileUrl('/test.swf', false) . '">',
                 "outputregex" => '/UPDATE/',
-                "expectedcontent" => '<object DATA="' . $this->getExternalTestFileUrl('/test.swf', true) . '">',
+                "expectedcontent" => '<object DATA="' . $this->get_converted_http_link('/test.swf') . '">',
             ],
             "URL should be case insensitive" => [
                 "content" => '<object data="HTTP://some.site/path?query">',
@@ -93,7 +93,7 @@ class httpsreplace_test extends \advanced_testcase {
                 "content" => '<img alt="A picture" src="' . $this->getExternalTestFileUrl('/test.png', false) .
                     '" width="1”><p style="font-size: \'20px\'"></p>',
                 "outputregex" => '/UPDATE/',
-                "expectedcontent" => '<img alt="A picture" src="' . $this->getExternalTestFileUrl('/test.png', true) .
+                "expectedcontent" => '<img alt="A picture" src="' . $this->get_converted_http_link('/test.png') .
                     '" width="1”><p style="font-size: \'20px\'"></p>',
             ],
             "Broken URL should not be changed" => [
@@ -113,11 +113,25 @@ class httpsreplace_test extends \advanced_testcase {
                     $this->getExternalTestFileUrl('/test.jpg', false) . '"></a>',
                 "outputregex" => '/UPDATE/',
                 "expectedcontent" => '<a href="' . $this->getExternalTestFileUrl('/test.png', false) . '"><img src="' .
-                    $this->getExternalTestFileUrl('/test.jpg', true) . '"></a>',
+                    $this->get_converted_http_link('/test.jpg') . '"></a>',
             ],
         ];
     }
 
+    /**
+     * Convert the HTTP external test file URL to use HTTPS.
+     *
+     * Note: We *must not* use getExternalTestFileUrl with the True option
+     * here, becase it is reasonable to have only one of these set due to
+     * issues with SSL certificates.
+     *
+     * @param   string  $path Path to be rewritten
+     * @return  string
+     */
+    protected function get_converted_http_link($path) {
+        return preg_replace('/^http:/', 'https:', $this->getExternalTestFileUrl($path, false));
+    }
+
     /**
      * Test upgrade_http_links
      * @param string $content Example content that we'll attempt to replace.
@@ -152,7 +166,7 @@ class httpsreplace_test extends \advanced_testcase {
         // Get the http url, since the default test wwwroot is https.
         $wwwrootdomain = 'www.example.com';
         $wwwroothttp = preg_replace('/^https:/', 'http:', $CFG->wwwroot);
-        $testdomain = 'download.moodle.org';
+        $testdomain = $this->get_converted_http_link('');
         return [
             "Test image from an available site so shouldn't be reported" => [
                 "content" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', false) . '">',
index fc1e3d1..06d7441 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_tool_log_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 8f014cf..f9c8a8b 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_logstore_database_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 8880f59..610b61c 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_logstore_standard_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index bf43b5d..e99f7c5 100644 (file)
@@ -64,6 +64,7 @@
                                 <li>
                                     <a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
                                     <ul class="dropdown dropdown-menu">
+                                    {{#canmanage}}
                                     <li class="dropdown-item">
                                         <a href="#" data-action="edit">
                                             {{#pix}}t/edit{{/pix}} {{#str}}edit{{/str}}
                                             {{#pix}}t/down{{/pix}} {{#str}}movedown{{/str}}
                                         </a>
                                     </li>
+                                    {{/canmanage}}
                                     <li class="dropdown-item">
                                         <a href="#" data-action="linkedcourses">
                                             {{#pix}}t/viewdetails{{/pix}} {{#str}}linkedcourses, tool_lp{{/str}}
                                         </a>
                                     </li>
+                                    {{#canmanage}}
                                     <li class="dropdown-item">
                                         <a href="#" data-action="relatedcompetencies">
                                             {{#pix}}t/add{{/pix}} {{#str}}addcrossreferencedcompetency, tool_lp{{/str}}
                                             {{#pix}}t/edit{{/pix}} {{#str}}competencyrule, tool_lp{{/str}}
                                         </a>
                                     </li>
+                                    {{/canmanage}}
                                 </ul>
                             </li>
                         </ul>
                 <p data-region="competencyinfo">
                     {{#str}}nocompetencyselected, tool_lp{{/str}}
                 </p>
-                {{#canmanage}}
                 <div data-region="competencyactions">
+                    {{#canmanage}}
                     <button class="btn btn-secondary" data-action="add">{{#pix}}t/add{{/pix}} <span data-region="term"></span></button>
+                    {{/canmanage}}
                 </div>
-                {{/canmanage}}
             </div>
         </div>
     </div>
index 6fbca49..1358647 100644 (file)
@@ -35,9 +35,6 @@ function xmldb_tool_monitor_upgrade($oldversion) {
 
     $dbman = $DB->get_manager();
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2016052305) {
 
         // Define field inactivedate to be added to tool_monitor_subscriptions.
index 26a0096..679a0cf 100644 (file)
@@ -9,6 +9,7 @@ Feature: Basic recycle bin functionality
       | username | firstname | lastname | email |
       | teacher1 | Teacher | 1 | teacher@asd.com |
       | student1 | Student | 1 | student@asd.com |
+      | student2 | Student | 2 | student2@asd.com |
     And the following "courses" exist:
       | fullname | shortname |
       | Course 1 | C1 |
@@ -16,6 +17,23 @@ Feature: Basic recycle bin functionality
     And the following "course enrolments" exist:
       | user | course | role |
       | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+      | student2 | C1 | student |
+      | teacher1 | C2 | editingteacher |
+      | student1 | C2 | student |
+      | student2 | C2 | student |
+    And the following "groups" exist:
+      | name | course | idnumber |
+      | Group A | C2 | G1 |
+      | Group B | C2 | G2 |
+      | Group C | C2 | G3 |
+    And the following "group members" exist:
+      | user | group |
+      | teacher1 | G1 |
+      | teacher1 | G2 |
+      | student1 | G1 |
+      | student2 | G1 |
+      | student2 | G2 |
     And the following config values are set as admin:
       | coursebinenable | 1 | tool_recyclebin |
       | categorybinenable | 1 | tool_recyclebin |
@@ -58,6 +76,12 @@ Feature: Basic recycle bin functionality
     And I wait to be redirected
     And I go to the courses management page
     And I should see "Course 2" in the "#course-listing" "css_element"
+    And I am on "Course 2" course homepage
+    And I navigate to "Groups" node in "Course administration > Users"
+    And I follow "Overview"
+    And "Student 1" "text" should exist in the "Group A" "table_row"
+    And "Student 2" "text" should exist in the "Group A" "table_row"
+    And "Student 2" "text" should exist in the "Group B" "table_row"
 
   @javascript
   Scenario: Deleting a single item from the recycle bin
index 7e94fcf..0de826c 100644 (file)
@@ -71,6 +71,8 @@ if ($options['list']) {
     $shorttime = get_string('strftimedatetimeshort');
 
     $tasks = \core\task\manager::get_all_scheduled_tasks();
+    echo str_pad(get_string('scheduledtasks', 'tool_task'), 50, ' ') . ' ' . str_pad(get_string('runpattern', 'tool_task'), 17, ' ')
+        . ' ' . str_pad(get_string('lastruntime', 'tool_task'), 40, ' ') . get_string('nextruntime', 'tool_task') . "\n";
     foreach ($tasks as $task) {
         $class = '\\' . get_class($task);
         $schedule = $task->get_minute() . ' '
@@ -80,6 +82,7 @@ if ($options['list']) {
             . $task->get_month() . ' '
             . $task->get_day_of_week();
         $nextrun = $task->get_next_run_time();
+        $lastrun = $task->get_last_run_time();
 
         $plugininfo = core_plugin_manager::instance()->get_plugin_info($task->get_component());
         $plugindisabled = $plugininfo && $plugininfo->is_enabled() === false && !$task->get_run_if_component_disabled();
@@ -94,7 +97,14 @@ if ($options['list']) {
             $nextrun = get_string('asap', 'tool_task');
         }
 
-        echo str_pad($class, 50, ' ') . ' ' . str_pad($schedule, 17, ' ') . ' ' . $nextrun . "\n";
+        if ($lastrun) {
+            $lastrun = userdate($lastrun);
+        } else {
+            $lastrun = get_string('never');
+        }
+
+        echo str_pad($class, 50, ' ') . ' ' . str_pad($schedule, 17, ' ') .
+            ' ' . str_pad($lastrun, 40, ' ') . ' ' . $nextrun . "\n";
     }
     exit(0);
 }
index 8663112..2f382b0 100644 (file)
@@ -42,6 +42,7 @@ $string['resettasktodefaults'] = 'Reset task schedule to defaults';
 $string['resettasktodefaults_help'] = 'This will discard any local changes and revert the schedule for this task back to its original settings.';
 $string['runnow'] = 'Run now';
 $string['runnow_confirm'] = 'Are you sure you want to run this task \'{$a}\' now? The task will run on the web server and may take some time to complete.';
+$string['runpattern'] = 'Run pattern';
 $string['scheduledtasks'] = 'Scheduled tasks';
 $string['scheduledtaskchangesdisabled'] = 'Modifications to the list of scheduled tasks have been prevented in Moodle configuration';
 $string['taskdisabled'] = 'Task disabled';
index d11332b..496b52d 100644 (file)
@@ -358,7 +358,7 @@ if ($formdata = $mform2->is_cancelled()) {
 
         // add default values for remaining fields
         $formdefaults = array();
-        if ($updatetype != UU_UPDATE_FILEOVERRIDE && $updatetype != UU_UPDATE_NOCHANGES) {
+        if (!$existinguser || ($updatetype != UU_UPDATE_FILEOVERRIDE && $updatetype != UU_UPDATE_NOCHANGES)) {
             foreach ($STD_FIELDS as $field) {
                 if (isset($user->$field)) {
                     continue;
index e217cf1..1ee26f4 100644 (file)
@@ -38,6 +38,31 @@ Feature: Upload users
     And I set the field "groups" to "Section 1 (1)"
     And the "members" select box should contain "Tom Jones"
 
+  @javascript
+  Scenario: Upload users enrolling them on courses and groups applying defaults
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Maths | math102 | 0 |
+    And the following "groups" exist:
+      | name | course | idnumber |
+      | Section 1 | math102 | S1 |
+      | Section 3 | math102 | S3 |
+    And I log in as "admin"
+    And I navigate to "Upload users" node in "Site administration > Users > Accounts"
+    When I upload "lib/tests/fixtures/upload_users.csv" file to "File" filemanager
+    And I press "Upload users"
+    And I set the following fields to these values:
+      | City/town  | Brighton   |
+      | Department | Purchasing |
+    And I press "Upload users"
+    And I press "Continue"
+    And I navigate to "Users > Accounts > Browse list of users" in site administration
+    And I should see "Tom Jones"
+    And I follow "Tom Jones"
+    And I follow "Edit profile"
+    And the field "City/town" matches value "Brighton"
+    And the field "Department" matches value "Purchasing"
+
   @javascript
   Scenario: Upload users with custom profile fields
     # Create user profile field.
index e816350..2e4a1a9 100644 (file)
@@ -80,7 +80,7 @@ function transformForm(event) {
             decimalsField.value = '';
             break;
         case '2':  // XMLDB_TYPE_NUMBER
-            lengthTip.innerHTML = ' 1...20'; // Hardcoded xmldb_field::NUMBER_MAX_LENGTH, yes!
+            lengthTip.innerHTML = ' 1...38'; // Hardcoded xmldb_field::NUMBER_MAX_LENGTH, yes!
             lengthField.disabled = false;
             decimalsTip.innerHTML = ' 0...length or empty';
             break;
index aaabf94..6d0cec5 100644 (file)
@@ -48,6 +48,7 @@ class edit_field_save extends XMLDBAction {
             'floatincorrectlength' => 'tool_xmldb',
             'charincorrectlength' => 'tool_xmldb',
             'numberincorrectdecimals' => 'tool_xmldb',
+            'numberincorrectwholepart' => 'tool_xmldb',
             'floatincorrectdecimals' => 'tool_xmldb',
             'defaultincorrect' => 'tool_xmldb',
             'back' => 'tool_xmldb',
@@ -158,6 +159,9 @@ class edit_field_save extends XMLDBAction {
                                        $decimals < $length))) {
                 $errors[] = $this->str['numberincorrectdecimals'];
             }
+            if (!empty($decimals) && ($length - $decimals > xmldb_field::INTEGER_MAX_LENGTH)) {
+                $errors[] = $this->str['numberincorrectwholepart'];
+            }
             if (!(empty($default) || (is_numeric($default) &&
                                        !empty($default)))) {
                 $errors[] = $this->str['defaultincorrect'];
index 80a7dda..5c52efd 100644 (file)
@@ -165,6 +165,7 @@ $string['nowrongintsfound'] = 'No wrong integers have been found, your DB doesn\
 $string['nowrongoraclesemanticsfound'] = 'No Oracle columns using BYTE semantics have been found, your DB doesn\'t need further actions.';
 $string['numberincorrectdecimals'] = 'Incorrect number of decimals for number field';
 $string['numberincorrectlength'] = 'Incorrect length for number field';
+$string['numberincorrectwholepart'] = 'Too big whole number part for number field';
 $string['pendingchanges'] = 'Note: You have performed changes to this file. They can be saved at any moment.';
 $string['pendingchangescannotbesaved'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server.';
 $string['pendingchangescannotbesavedreload'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server. Then reload this page and you should be able to save those changes.';
index 65bf4e6..ac0f5f9 100644 (file)
                 $users[$key]->country = $countries[$user->country];
             }
         }
-        if ($sort == "country") {  // Need to resort by full country name, not code
+        if ($sort == "country") {
+            // Need to resort by full country name, not code.
             foreach ($users as $user) {
                 $susers[$user->id] = $user->country;
             }
-            asort($susers);
+            // Sort by country name, according to $dir.
+            if ($dir === 'DESC') {
+                arsort($susers);
+            } else {
+                asort($susers);
+            }
             foreach ($susers as $key => $value) {
                 $nusers[] = $users[$key];
             }
index d881645..0bee650 100644 (file)
@@ -1013,6 +1013,10 @@ class model {
             if (!$this->is_static()) {
                 $this->model->trained = 0;
             }
+        } else if (empty($this->model->timesplitting)) {
+            // A valid timesplitting method needs to be supplied before a model can be enabled.
+            throw new \moodle_exception('invalidtimesplitting', 'analytics', '', $this->model->id);
+
         }
 
         // Purge pages with insights as this may change things.
index 2685b64..21009ba 100644 (file)
@@ -226,13 +226,17 @@ class analytics_model_testcase extends advanced_testcase {
         $this->model->mark_as_trained();
         $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
 
-        $this->model->enable();
-        $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
+        // Wait for the current timestamp to change.
+        $this->waitForSecond();
+        $this->model->enable('\core\analytics\time_splitting\deciles');
+        $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
+        $uniqueid = $this->model->get_unique_id();
 
-        // Wait 1 sec so the timestamp changes.
-        sleep(1);
+        // Wait for the current timestamp to change.
+        $this->waitForSecond();
         $this->model->enable('\core\analytics\time_splitting\quarters');
         $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
+        $this->assertNotEquals($uniqueid, $this->model->get_unique_id());
     }
 
     /**
index 167c76b..f3999b3 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_cas_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 2dcd4ae..549e103 100644 (file)
@@ -42,6 +42,7 @@ class auth_plugin_db extends auth_plugin_base {
 
         $this->authtype = 'db';
         $this->config = get_config('auth_db');
+        $this->errorlogtag = '[AUTH DB] ';
         if (empty($this->config->extencoding)) {
             $this->config->extencoding = 'utf-8';
         }
@@ -381,7 +382,7 @@ class auth_plugin_db extends auth_plugin_base {
                     list($in_sql, $params) = $DB->get_in_or_equal($userlistchunk, SQL_PARAMS_NAMED, 'u', true);
                     $params['authtype'] = $this->authtype;
                     $params['mnethostid'] = $CFG->mnet_localhost_id;
-                    $sql = "SELECT u.id, u.username
+                    $sql = "SELECT u.id, u.username, u.suspended
                           FROM {user} u
                          WHERE u.auth = :authtype AND u.deleted = 0 AND u.mnethostid = :mnethostid AND u.username {$in_sql}";
                     $update_users = $update_users + $DB->get_records_sql($sql, $params);
@@ -391,7 +392,7 @@ class auth_plugin_db extends auth_plugin_base {
                     $trace->output("User entries to update: ".count($update_users));
 
                     foreach ($update_users as $user) {
-                        if ($this->update_user_record($user->username, $updatekeys)) {
+                        if ($this->update_user_record($user->username, $updatekeys, false, (bool) $user->suspended)) {
                             $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
                         } else {
                             $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped'), 1);
@@ -545,67 +546,6 @@ class auth_plugin_db extends auth_plugin_base {
         return $user;
     }
 
-    /**
-     * will update a local user record from an external source.
-     * is a lighter version of the one in moodlelib -- won't do
-     * expensive ops such as enrolment.
-     *
-     * If you don't pass $updatekeys, there is a performance hit and
-     * values removed from DB won't be removed from moodle.
-     *
-     * @param string $username username
-     * @param bool $updatekeys
-     * @return stdClass
-     */
-    function update_user_record($username, $updatekeys=false) {
-        global $CFG, $DB;
-
-        //just in case check text case
-        $username = trim(core_text::strtolower($username));
-
-        // get the current user record
-        $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
-        if (empty($user)) { // trouble
-            error_log("Cannot update non-existent user: $username");
-            print_error('auth_dbusernotexist','auth_db',$username);
-            die;
-        }
-
-        // Ensure userid is not overwritten.
-        $userid = $user->id;
-        $needsupdate = false;
-
-        $updateuser = new stdClass();
-        $updateuser->id = $userid;
-        if ($newinfo = $this->get_userinfo($username)) {
-            $newinfo = truncate_userinfo($newinfo);
-
-            if (empty($updatekeys)) { // All keys? This does not support removing values.
-                $updatekeys = array_keys($newinfo);
-            }
-
-            foreach ($updatekeys as $key) {
-                if (isset($newinfo[$key])) {
-                    $value = $newinfo[$key];
-                } else {
-                    $value = '';
-                }
-
-                if (!empty($this->config->{'field_updatelocal_' . $key})) {
-                    if (isset($user->{$key}) and $user->{$key} != $value) { // Only update if it's changed.
-                        $needsupdate = true;
-                        $updateuser->$key = $value;
-                    }
-                }
-            }
-        }
-        if ($needsupdate) {
-            require_once($CFG->dirroot . '/user/lib.php');
-            user_update_user($updateuser);
-        }
-        return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
-    }
-
     /**
      * Called when the user record is updated.
      * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
index 3b21d60..a9af49f 100644 (file)
@@ -66,7 +66,6 @@ $string['auth_dbupdateusers_description'] = 'As well as inserting new users, upd
 $string['auth_dbupdatinguser'] = 'Updating user {$a->name} id {$a->id}';
 $string['auth_dbuser'] = 'Username with read access to the database';
 $string['auth_dbuser_key'] = 'DB user';
-$string['auth_dbusernotexist'] = 'Cannot update non-existent user: {$a}';
 $string['auth_dbuserstoadd'] = 'User entries to add: {$a}';
 $string['auth_dbuserstoremove'] = 'User entries to remove: {$a}';
 $string['pluginname'] = 'External database';
index 91218ad..a87cc11 100644 (file)
@@ -547,7 +547,9 @@ class auth_plugin_ldap extends auth_plugin_base {
         // Save any custom profile field information
         profile_save_data($user);
 
-        $this->update_user_record($user->username);
+        $userinfo = $this->get_userinfo($user->username);
+        $this->update_user_record($user->username, false, false, $this->is_user_suspended((object) $userinfo));
+
         // This will also update the stored hash to the latest algorithm
         // if the existing hash is using an out-of-date algorithm (or the
         // legacy md5 algorithm).
@@ -668,6 +670,8 @@ class auth_plugin_ldap extends auth_plugin_base {
     function sync_users($do_updates=true) {
         global $CFG, $DB;
 
+        require_once($CFG->dirroot . '/user/profile/lib.php');
+
         print_string('connectingldap', 'auth_ldap');
         $ldapconnection = $this->ldap_connect();
 
@@ -872,7 +876,9 @@ class auth_plugin_ldap extends auth_plugin_base {
 
                 foreach ($users as $user) {
                     echo "\t"; print_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id));
-                    if (!$this->update_user_record($user->username, $updatekeys, true)) {
+                    $userinfo = $this->get_userinfo($user->username);
+                    if (!$this->update_user_record($user->username, $updatekeys, true,
+                            $this->is_user_suspended((object) $userinfo))) {
                         echo ' - '.get_string('skipped');
                     }
                     echo "\n";
@@ -928,14 +934,24 @@ class auth_plugin_ldap extends auth_plugin_base {
 
                 $id = user_create_user($user, false);
                 echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n";
-                $user = $DB->get_record('user', array('id' => $id));
+                $euser = $DB->get_record('user', array('id' => $id));
 
                 if (!empty($this->config->forcechangepassword)) {
                     set_user_preference('auth_forcepasswordchange', 1, $id);
                 }
 
+                // Save custom profile fields.
+                $euser->profile = array();
+                foreach ($user as $key => $value) {
+                    if (preg_match('/^profile_field_(.*)$/', $key, $match)) {
+                        $field = $match[1];
+                        $euser->profile[$field] = $user->$key;
+                    }
+                }
+                profile_save_custom_fields($euser);
+
                 // Add roles if needed.
-                $this->sync_roles($user);
+                $this->sync_roles($euser);
 
             }
             $transaction->allow_commit();
@@ -950,72 +966,6 @@ class auth_plugin_ldap extends auth_plugin_base {
         return true;
     }
 
-    /**
-     * Update a local user record from an external source.
-     * This is a lighter version of the one in moodlelib -- won't do
-     * expensive ops such as enrolment.
-     *
-     * If you don't pass $updatekeys, there is a performance hit and
-     * values removed from LDAP won't be removed from moodle.
-     *
-     * @param string $username username
-     * @param boolean $updatekeys true to update the local record with the external LDAP values.
-     * @param bool $triggerevent set false if user_updated event should not be triggered.
-     *             This will not affect user_password_updated event triggering.
-     * @return stdClass|bool updated user record or false if there is no new info to update.
-     */
-    function update_user_record($username, $updatekeys = false, $triggerevent = false) {
-        global $CFG, $DB;
-
-        // Just in case check text case
-        $username = trim(core_text::strtolower($username));
-
-        // Get the current user record
-        $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
-        if (empty($user)) { // trouble
-            error_log($this->errorlogtag.get_string('auth_dbusernotexist', 'auth_db', '', $username));
-            print_error('auth_dbusernotexist', 'auth_db', '', $username);
-            die;
-        }
-
-        // Protect the userid from being overwritten
-        $userid = $user->id;
-
-        if ($newinfo = $this->get_userinfo($username)) {
-            $newinfo = truncate_userinfo($newinfo);
-
-            if (empty($updatekeys)) { // all keys? this does not support removing values
-                $updatekeys = array_keys($newinfo);
-            }
-
-            if (!empty($updatekeys)) {
-                $newuser = new stdClass();
-                $newuser->id = $userid;
-                // The cast to int is a workaround for MDL-53959.
-                $newuser->suspended = (int)$this->is_user_suspended((object) $newinfo);
-
-                foreach ($updatekeys as $key) {
-                    if (isset($newinfo[$key])) {
-                        $value = $newinfo[$key];
-                    } else {
-                        $value = '';
-                    }
-
-                    if (!empty($this->config->{'field_updatelocal_' . $key})) {
-                        // Only update if it's changed.
-                        if ($user->{$key} != $value) {
-                            $newuser->$key = $value;
-                        }
-                    }
-                }
-                user_update_user($newuser, false, $triggerevent);
-            }
-        } else {
-            return false;
-        }
-        return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
-    }
-
     /**
      * Bulk insert in SQL's temp table
      */
@@ -1201,6 +1151,14 @@ class auth_plugin_ldap extends auth_plugin_base {
             return false;
         }
 
+        // Load old custom fields.
+        profile_load_custom_fields($olduser, false);
+
+        $fields = array();
+        foreach (profile_get_custom_fields(false) as $field) {
+            $fields[$field->shortname] = $field;
+        }
+
         $success = true;
         $user_info_result = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
         if ($user_info_result) {
@@ -1219,19 +1177,24 @@ class auth_plugin_ldap extends auth_plugin_base {
             $user_entry = $user_entry[0];
 
             foreach ($attrmap as $key => $ldapkeys) {
-                $profilefield = '';
-                // Only process if the moodle field ($key) has changed and we
-                // are set to update LDAP with it
-                $customprofilefield = 'profile_field_' . $key;
-                if (isset($olduser->$key) and isset($newuser->$key)
-                    and ($olduser->$key !== $newuser->$key)) {
-                    $profilefield = $key;
-                } else if (isset($olduser->$customprofilefield) && isset($newuser->$customprofilefield)
-                    && $olduser->$customprofilefield !== $newuser->$customprofilefield) {
-                    $profilefield = $customprofilefield;
+                if (preg_match('/^profile_field_(.*)$/', $key, $match)) {
+                    // Custom field.
+                    $fieldname = $match[1];
+                    if (isset($fields[$fieldname])) {
+                        $class = 'profile_field_' . $fields[$fieldname]->datatype;
+                        $formfield = new $class($fields[$fieldname]->id, $olduser->id);
+                        $oldvalue = isset($olduser->profile[$fieldname]) ? $olduser->profile[$fieldname] : null;
+                    } else {
+                        $oldvalue = null;
+                    }
+                    $newvalue = $formfield->edit_save_data_preprocess($newuser->{$formfield->inputname}, new stdClass);
+                } else {
+                    // Standard field.
+                    $oldvalue = isset($olduser->$key) ? $olduser->$key : null;
+                    $newvalue = isset($newuser->$key) ? $newuser->$key : null;
                 }
 
-                if (!empty($profilefield) && !empty($this->config->{'field_updateremote_' . $key})) {
+                if ($newvalue !== null and $newvalue !== $oldvalue and !empty($this->config->{'field_updateremote_' . $key})) {
                     // For ldap values that could be in more than one
                     // ldap key, we will do our best to match
                     // where they came from
@@ -1244,9 +1207,9 @@ class auth_plugin_ldap extends auth_plugin_base {
                         $ambiguous = false;
                     }
 
-                    $nuvalue = core_text::convert($newuser->$profilefield, 'utf-8', $this->config->ldapencoding);
+                    $nuvalue = core_text::convert($newvalue, 'utf-8', $this->config->ldapencoding);
                     empty($nuvalue) ? $nuvalue = array() : $nuvalue;
-                    $ouvalue = core_text::convert($olduser->$profilefield, 'utf-8', $this->config->ldapencoding);
+                    $ouvalue = core_text::convert($oldvalue, 'utf-8', $this->config->ldapencoding);
 
                     foreach ($ldapkeys as $ldapkey) {
                         $ldapkey   = $ldapkey;
index 6811fd7..02f1323 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_ldap_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index fdb9541..843e1b0 100644 (file)
@@ -41,7 +41,7 @@ $string['auth_ldapdescription'] = 'This method provides authentication against a
                                   entry in its database. This module can read user attributes from LDAP and prefill
                                   wanted fields in Moodle.  For following logins only the username and
                                   password are checked.';
-$string['auth_ldap_expiration_desc'] = 'Select No to disable expired password checking or LDAP to read passwordexpiration time directly from LDAP';
+$string['auth_ldap_expiration_desc'] = 'Select \'{$a->no}\' to disable expired password checking or \'{$a->ldapserver}\' to read the password expiration time directly from the LDAP server';
 $string['auth_ldap_expiration_key'] = 'Expiration';
 $string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiration warning is issued.';
 $string['auth_ldap_expiration_warning_key'] = 'Expiration warning';
index af1ddf7..43fb8eb 100644 (file)
@@ -185,12 +185,24 @@ if ($ADMIN->fulltree) {
                 new lang_string('auth_ldap_passwdexpire_settings', 'auth_ldap'), ''));
 
         // Password Expiration.
+
+        // Create the description lang_string object.
+        $strno = get_string('no');
+        $strldapserver = get_string('pluginname', 'auth_ldap');
+        $langobject = new stdClass();
+        $langobject->no = $strno;
+        $langobject->ldapserver = $strldapserver;
+        $description = new lang_string('auth_ldap_expiration_desc', 'auth_ldap', $langobject);
+
+        // Now create the options.
         $expiration = array();
-        $expiration['0'] = 'no';
-        $expiration['1'] = 'LDAP';
+        $expiration['0'] = $strno;
+        $expiration['1'] = $strldapserver;
+
+        // Add the setting.
         $settings->add(new admin_setting_configselect('auth_ldap/expiration',
                 new lang_string('auth_ldap_expiration_key', 'auth_ldap'),
-                new lang_string('auth_ldap_expiration_desc', 'auth_ldap'), 0 , $expiration));
+                $description, 0 , $expiration));
 
         // Password Expiration warning.
         $settings->add(new admin_setting_configtext('auth_ldap/expiration_warning',
index 50a5266..03dbdb2 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_manual_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 1f269f7..b3b22f5 100644 (file)
@@ -382,7 +382,7 @@ class auth_plugin_mnet extends auth_plugin_base {
             // with info so that the IDP can maintain mnetservice_enrol_enrolments
             $mnetrequest->add_param($remoteuser->username);
             $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible';
-            $courses = enrol_get_users_courses($localuser->id, false, $fields, 'visible DESC,sortorder ASC');
+            $courses = enrol_get_users_courses($localuser->id, false, $fields);
             if (is_array($courses) && !empty($courses)) {
                 // Second request to do the JOINs that we'd have done
                 // inside enrol_get_users_courses() if we had been allowed
index 925124e..9db6da9 100644 (file)
@@ -32,9 +32,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_auth_mnet_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
     if ($oldversion < 2017020700) {
index fff9fdd..70c49d5 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in /auth/* - plugins,
 information provided here is intended especially for developers.
 
+=== 3.5 ===
+
+* The auth_db and auth_ldap plugins' implementations of update_user_record() have been removed and both now
+  call the new implementation added in the base class.
+
 === 3.3 ===
 
 * Authentication plugins have been migrated to use the admin settings API.  Plugins should use a settings.php file to
index bc5bb5a..7bb6da7 100644 (file)
@@ -99,7 +99,7 @@ class cc11_resource extends entities11 {
                                 $link = 'http://invalidurldetected/';
                             }
                         } else {
-                            $link = $rawlink;
+                            $link = htmlspecialchars(trim($rawlink), ENT_COMPAT, 'UTF-8', false);
                         }
                     }
                 }
index 3b45eea..0589755 100644 (file)
@@ -596,7 +596,7 @@ class core_backup_moodle2_testcase extends advanced_testcase {
             assign_capability($cap, CAP_ALLOW, $roleidcat, $categorycontext);
         }
 
-        allow_assign($roleidcat, $studentrole->id);
+        core_role_set_assign_allowed($roleidcat, $studentrole->id);
         role_assign($roleidcat, $user->id, $categorycontext);
         accesslib_clear_all_caches_for_unit_testing();
 
index f586d40..8fa0654 100644 (file)
@@ -122,6 +122,8 @@ class backup_confirmation_form extends backup_moodleform {
         if (!array_key_exists('setting_root_filename', $errors)) {
             if (trim($data['setting_root_filename']) == '') {
                 $errors['setting_root_filename'] = get_string('errorfilenamerequired', 'backup');
+            } else if (strlen(trim($data['setting_root_filename'])) > 255) {
+                $errors['setting_root_filename'] = get_string('errorfilenametoolong', 'backup');
             } else if (!preg_match('#\.mbz$#i', $data['setting_root_filename'])) {
                 $errors['setting_root_filename'] = get_string('errorfilenamemustbezip', 'backup');
             }
index 34cf105..31be94e 100644 (file)
@@ -106,6 +106,37 @@ class core_badges_observer {
         }
     }
 
+    /**
+     * Triggered when 'badge_awarded' event happens.
+     *
+     * @param \core\event\badge_awarded $event event generated when a badge is awarded.
+     */
+    public static function badge_criteria_review(\core\event\badge_awarded $event) {
+        global $DB, $CFG;
+
+        if (!empty($CFG->enablebadges)) {
+            require_once($CFG->dirroot.'/lib/badgeslib.php');
+            $userid = $event->relateduserid;
+
+            if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_BADGE))) {
+                foreach ($rs as $r) {
+                    $badge = new badge($r->badgeid);
+                    if (!$badge->is_active() || $badge->is_issued($userid)) {
+                        continue;
+                    }
+
+                    if ($badge->criteria[BADGE_CRITERIA_TYPE_BADGE]->review($userid)) {
+                        $badge->criteria[BADGE_CRITERIA_TYPE_BADGE]->mark_complete($userid);
+
+                        if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+                            $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+                            $badge->issue($userid);
+                        }
+                    }
+                }
+            }
+        }
+    }
     /**
      * Triggered when 'user_updated' event happens.
      *
index ee13151..5427783 100644 (file)
@@ -68,6 +68,12 @@ define('BADGE_CRITERIA_TYPE_COURSESET', 5);
  */
 define('BADGE_CRITERIA_TYPE_PROFILE', 6);
 
+/*
+ * Badge completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_BADGE', 7);
+
 /*
  * Criteria type constant to class name mapping
  */
@@ -79,7 +85,8 @@ $BADGE_CRITERIA_TYPES = array(
     BADGE_CRITERIA_TYPE_SOCIAL    => 'social',
     BADGE_CRITERIA_TYPE_COURSE    => 'course',
     BADGE_CRITERIA_TYPE_COURSESET => 'courseset',
-    BADGE_CRITERIA_TYPE_PROFILE   => 'profile'
+    BADGE_CRITERIA_TYPE_PROFILE   => 'profile',
+    BADGE_CRITERIA_TYPE_BADGE     => 'badge',
 );
 
 /**
diff --git a/badges/criteria/award_criteria_badge.php b/badges/criteria/award_criteria_badge.php
new file mode 100644 (file)
index 0000000..d9bd795
--- /dev/null
@@ -0,0 +1,271 @@
+<?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/>.
+
+/**
+ * This file contains the badge earned badge award criteria type class
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2017 Stephen Bourget
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Badge award criteria -- award on badge completion
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2017 Stephen Bourget
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class award_criteria_badge extends award_criteria {
+
+    /* @var int Criteria [BADGE_CRITERIA_TYPE_BADGE] */
+    public $criteriatype = BADGE_CRITERIA_TYPE_BADGE;
+
+    public $required_param = 'badge';
+    public $optional_params = array();
+
+    /**
+     * Get criteria details for displaying to users
+     * @param string $short Print short version of criteria
+     * @return string
+     */
+    public function get_details($short = '') {
+        global $DB, $OUTPUT;
+        $output = array();
+        foreach ($this->params as $p) {
+            $badgename = $DB->get_field('badge', 'name', array('id' => $p['badge']));
+            if (!$badgename) {
+                $str = $OUTPUT->error_text(get_string('error:nosuchbadge', 'badges'));
+            } else {
+                $str = html_writer::tag('b', '"' . $badgename . '"');
+            }
+            $output[] = $str;
+        }
+
+        if ($short) {
+            return implode(', ', $output);
+        } else {
+            return html_writer::alist($output, array(), 'ul');
+        }
+    }
+
+    /**
+     * Add appropriate new criteria options to the form
+     * @param object $mform moodle form
+     */
+    public function get_options(&$mform) {
+        global $DB;
+        $none = false;
+        $availablebadges = null;
+
+        $mform->addElement('header', 'first_header', $this->get_title());
+        $mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
+
+        // Determine if this badge is a course badge or a site badge.
+        $thisbadge = $DB->get_record('badge', array('id' => $this->badgeid));
+
+        if ($thisbadge->type == BADGE_TYPE_SITE) {
+            // Only list site badges that are enabled.
+            $select = " type = :site AND (status = :status1 OR status = :status2)";
+            $params = array('site' => BADGE_TYPE_SITE,
+                            'status1' => BADGE_STATUS_ACTIVE,
+                            'status2' => BADGE_STATUS_ACTIVE_LOCKED);
+            $availablebadges = $DB->get_records_select_menu('badge', $select, $params, 'name ASC', 'id, name');
+
+        } else if ($thisbadge->type == BADGE_TYPE_COURSE) {
+            // List both site badges and course badges belonging to this course.
+            $select = " (type = :site OR (type = :course AND courseid = :courseid)) AND (status = :status1 OR status = :status2)";
+            $params = array('site' => BADGE_TYPE_SITE,
+                            'course' => BADGE_TYPE_COURSE,
+                            'courseid' => $thisbadge->courseid,
+                            'status1' => BADGE_STATUS_ACTIVE,
+                            'status2' => BADGE_STATUS_ACTIVE_LOCKED);
+            $availablebadges = $DB->get_records_select_menu('badge', $select, $params, 'name ASC', 'id, name');
+        }
+        if (!empty($availablebadges)) {
+            $select = array();
+            $selected = array();
+            foreach ($availablebadges as $bid => $badgename) {
+                if ($bid != $this->badgeid) {
+                    // Do not let it use itself as criteria.
+                    $select[$bid] = format_string($badgename, true);
+                }
+            }
+
+            if ($this->id !== 0) {
+                $selected = array_keys($this->params);
+            }
+            $settings = array('multiple' => 'multiple', 'size' => 20, 'class' => 'selectbadge');
+            $mform->addElement('select', 'badge_badges', get_string('addbadge', 'badges'), $select, $settings);
+            $mform->addRule('badge_badges', get_string('requiredbadge', 'badges'), 'required');
+            $mform->addHelpButton('badge_badges', 'addbadge', 'badges');
+
+            if ($this->id !== 0) {
+                $mform->setDefault('badge_badges', $selected);
+            }
+        } else {
+            $mform->addElement('static', 'nobadges', '', get_string('error:nobadges', 'badges'));
+            $none = true;
+        }
+
+        // Add aggregation.
+        if (!$none) {
+            $mform->addElement('header', 'aggregation', get_string('method', 'badges'));
+            $agg = array();
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodbadges', 'badges'), 1);
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodbadges', 'badges'), 2);
+            $mform->addGroup($agg, 'methodgr', '', array('<br/>'), false);
+            if ($this->id !== 0) {
+                $mform->setDefault('agg', $this->method);
+            } else {
+                $mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY);
+            }
+        }
+
+        return array($none, get_string('noparamstoadd', 'badges'));
+    }
+
+    /**
+     * Save criteria records
+     *
+     * @param array $params Values from the form or any other array.
+     */
+    public function save($params = array()) {
+        $badges = $params['badge_badges'];
+        unset($params['badge_badges']);
+        foreach ($badges as $badgeid) {
+            $params["badge_{$badgeid}"] = $badgeid;
+        }
+
+        parent::save($params);
+    }
+
+    /**
+     * Review this criteria and decide if it has been completed
+     *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @param bool $filtered An additional parameter indicating that user list
+     *        has been reduced and some expensive checks can be skipped.
+     *
+     * @return bool Whether criteria is complete.
+     */
+    public function review($userid, $filtered = false) {
+
+        global $DB;
+        $overall = false;
+
+        foreach ($this->params as $param) {
+            $badge = $DB->get_record('badge', array('id' => $param['badge']));
+            // See if the user has earned this badge.
+            $awarded = $DB->get_record('badge_issued', array('badgeid' => $param['badge'], 'userid' => $userid));
+
+            // Extra check in case a badge was deleted while this badge is still active.
+            if (!$badge) {
+                if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+                    return false;
+                } else {
+                    continue;
+                }
+            }
+
+            if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+
+                if ($awarded) {
+                    $overall = true;
+                    continue;
+                } else {
+                    return false;
+                }
+            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                if ($awarded) {
+                    return true;
+                } else {
+                    $overall = false;
+                    continue;
+                }
+            }
+        }
+
+        return $overall;
+    }
+
+    /**
+     * Checks criteria for any major problems.
+     *
+     * @return array A list containing status and an error message (if any).
+     */
+    public function validate() {
+        global $DB;
+        $params = array_keys($this->params);
+        $method = ($this->method == BADGE_CRITERIA_AGGREGATION_ALL);
+        $singleparam = (count($params) == 1);
+
+        foreach ($params as $param) {
+            // Perform check if there only one parameter with any type of aggregation,
+            // Or there are more than one parameter with aggregation ALL.
+
+            if (($singleparam || $method) && !$DB->record_exists('badge', array('id' => $param))) {
+                return array(false, get_string('error:invalidparambadge', 'badges'));
+            }
+        }
+
+        return array(true, '');
+    }
+
+    /**
+     * Returns array with sql code and parameters returning all ids
+     * of users who meet this particular criterion.
+     *
+     * @return array list($join, $where, $params)
+     */
+    public function get_completed_criteria_sql() {
+        $join = '';
+        $where = '';
+        $params = array();
+
+        if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            // User has received ANY of the required badges.
+            $join = " LEFT JOIN {badge_issued} bi2 ON bi2.userid = u.id";
+            $where = "AND (";
+            $i = 0;
+            foreach ($this->params as $param) {
+                if ($i == 0) {
+                    $where .= ' bi2.badgeid = :badgeid'.$i;
+                } else {
+                    $where .= ' OR bi2.badgeid = :badgeid'.$i;
+                }
+                $params['badgeid'.$i] = $param['badge'];
+                $i++;
+            }
+            $where .= ") ";
+            return array($join, $where, $params);
+        } else {
+            // User has received ALL of the required badges.
+            $join = " LEFT JOIN {badge_issued} bi2 ON bi2.userid = u.id";
+            $i = 0;
+            foreach ($this->params as $param) {
+                $i++;
+                $where = ' AND bi2.badgeid = :badgeid'.$i;
+                $params['badgeid'.$i] = $param['badge'];
+            }
+            return array($join, $where, $params);
+        }
+    }
+}
index 83fde27..ff1e613 100644 (file)
@@ -65,6 +65,7 @@ class award_criteria_manual extends award_criteria {
         $none = true;
 
         $roles = get_roles_with_capability('moodle/badges:awardbadge', CAP_ALLOW, $PAGE->context);
+        $visibleroles = get_viewable_roles($PAGE->context);
         $roleids = array_map(function($o) {
             return $o->id;
         }, $roles);
@@ -89,6 +90,9 @@ class award_criteria_manual extends award_criteria {
             $mform->addElement('header', 'first_header', $this->get_title());
             $mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
             foreach ($roleids as $rid) {
+                if (!key_exists($rid, $visibleroles)) {
+                    continue;
+                }
                 $checked = false;
                 if (in_array($rid, $existing)) {
                     $checked = true;
index 13c040a..cd64b32 100644 (file)
@@ -4,6 +4,71 @@ Feature: Award badges
   As an admin
   I need to add criteria to badges in the system
 
+  @javascript
+  Scenario: Award badge on other badges as criteria
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    # Create course badge 1.
+    And I navigate to "Add a new badge" node in "Course administration > Badges"
+    And I follow "Add a new badge"
+    And I set the following fields to these values:
+      | Name | Course Badge 1 |
+      | Description | Course badge 1 description |
+      | issuername | Tester of course badge |
+    And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    And I set the field "type" to "Manual issue by role"
+    And I expand all fieldsets
+    # Set to ANY of the roles awards badge.
+    And I set the field "Teacher" to "1"
+    And I set the field "Any of the selected roles awards the badge" to "1"
+    And I press "Save"
+    And I press "Enable access"
+    And I press "Continue"
+    # Badge #2
+    And I navigate to "Add a new badge" node in "Course administration > Badges"
+    And I follow "Add a new badge"
+    And I set the following fields to these values:
+      | Name | Course Badge 2 |
+      | Description | Course badge 2 description |
+      | issuername | Tester of course badge |
+    And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    # Set "course badge 1" as criteria
+    And I set the field "type" to "Awarded badges"
+    And I set the field "id_badge_badges" to "Course Badge 1"
+    And I press "Save"
+    And I press "Enable access"
+    And I press "Continue"
+    And I follow "Manage badges"
+    And I follow "Course Badge 1"
+    And I follow "Recipients (0)"
+    And I press "Award badge"
+    # Award course badge 1 to student 1.
+    And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
+    When I press "Award badge"
+    And I follow "Course Badge 1"
+    And I follow "Recipients (1)"
+    Then I should see "Recipients (1)"
+    And I log out
+    # Student 1 should have both badges.
+    And I log in as "student1"
+    And I follow "Profile" in the user menu
+    When I click on "Course 1" "link" in the "region-main" "region"
+    Then I should see "Course Badge 1"
+    And I should see "Course Badge 2"
+
   @javascript
   Scenario: Award profile badge
     Given I log in as "admin"
diff --git a/badges/tests/behat/role_visibility.feature b/badges/tests/behat/role_visibility.feature
new file mode 100644 (file)
index 0000000..bb3a49d
--- /dev/null
@@ -0,0 +1,51 @@
+@core @core_badges
+Feature: Test role visibility for the badge administration page
+  In order to control access
+  As an admin
+  I need to control which roles can see each other
+
+  Background: Add a bunch of users
+    Given  the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | Teacher   | 1        | teacher1@example.com |
+      | manager1 | Manager   | 1        | manager1@example.com |
+    And the following "course enrolments" exist:
+      | user     | course | role    |
+      | teacher1 | C1     | editingteacher |
+      | manager1 | C1     | manager        |
+
+  @javascript @_file_upload
+  Scenario: Check the default roles are visible
+    Given I log in as "manager1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Add a new badge" node in "Course administration > Badges"
+    And I follow "Add a new badge"
+    And I set the following fields to these values:
+      | Name | Course Badge |
+      | Description | Course badge description |
+      | issuername | Tester of course badge |
+    And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    And I set the field "type" to "Manual issue by role"
+    Then I should see "Teacher"
+    And I should see "Manager"
+
+  @javascript @_file_upload
+  Scenario: Check hidden roles are not visible
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Add a new badge" node in "Course administration > Badges"
+    And I follow "Add a new badge"
+    And I set the following fields to these values:
+      | Name | Course Badge |
+      | Description | Course badge description |
+      | issuername | Tester of course badge |
+    And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    And I set the following fields to these values:
+      | Add badge criteria | Manual issue by role |
+    Then I should see "Teacher"
+    And I should not see "Manager"
index 980e5f9..bb9b3cf 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_badges_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 397cb90..4403a77 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_calendar_month_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index e7aa7fe..dbe19a5 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_calendar_upcoming_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index f5f0ae3..d01face 100644 (file)
@@ -46,9 +46,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_community_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index c74148a..f0837fc 100644 (file)
@@ -48,9 +48,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_completionstatus_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 36003f7..10cbf23 100644 (file)
@@ -57,14 +57,7 @@ class block_course_list extends block_list {
 
         if (empty($CFG->disablemycourses) and isloggedin() and !isguestuser() and
           !(has_capability('moodle/course:update', context_system::instance()) and $adminseesall)) {    // Just print My Courses
-            // As this is producing navigation sort order should default to $CFG->navsortmycoursessort instead
-            // of using the default.
-            if (!empty($CFG->navsortmycoursessort)) {
-                $sortorder = 'visible DESC, ' . $CFG->navsortmycoursessort . ' ASC';
-            } else {
-                $sortorder = 'visible DESC, sortorder ASC';
-            }
-            if ($courses = enrol_get_my_courses(NULL, $sortorder)) {
+            if ($courses = enrol_get_my_courses()) {
                 foreach ($courses as $course) {
                     $coursecontext = context_course::instance($course->id);
                     $linkcss = $course->visible ? "" : " class=\"dimmed\" ";
index 9186a40..763a538 100644 (file)
@@ -48,9 +48,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_course_summary_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 7dd3057..d3d96b2 100644 (file)
@@ -78,6 +78,12 @@ class block_globalsearch extends block_base {
             'type' => 'text', 'size' => '15');
         $this->content->text .= html_writer::empty_tag('input', $inputoptions);
 
+        // Context id.
+        if ($this->page->context && $this->page->context->contextlevel !== CONTEXT_SYSTEM) {
+            $this->content->text .= html_writer::empty_tag('input', ['type' => 'hidden',
+                    'name' => 'context', 'value' => $this->page->context->id]);
+        }
+
         // Search button.
         $this->content->text .= html_writer::tag('button', get_string('search', 'search'),
             array('id' => 'searchform_button', 'type' => 'submit', 'title' => 'globalsearch', 'class' => 'btn btn-secondary'));
index e386f20..5ad7f22 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_html_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index e5cee22..995f6d4 100644 (file)
@@ -35,6 +35,7 @@ use core_competency\url;
 use renderable;
 use renderer_base;
 use templatable;
+use required_capability_exception;
 
 /**
  * Summary renderable class.
@@ -68,7 +69,11 @@ class summary implements renderable, templatable {
         $this->user = $user;
 
         // Get the plans.
-        $this->plans = api::list_user_plans($this->user->id);
+        try {
+            $this->plans = api::list_user_plans($this->user->id);
+        } catch (required_capability_exception $e) {
+            $this->plans = [];
+        }
 
         // Get the competencies to review.
         $this->compstoreview = api::list_user_competencies_to_review(0, 3);
index 2435f54..2850637 100644 (file)
@@ -63,7 +63,7 @@ class main implements renderable, templatable {
     public function export_for_template(renderer_base $output) {
         global $USER;
 
-        $courses = enrol_get_my_courses('*', 'fullname ASC');
+        $courses = enrol_get_my_courses('*');
         $coursesprogress = [];
 
         foreach ($courses as $course) {
index 04dc110..07b4381 100644 (file)
@@ -55,9 +55,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_navigation_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 1a41395..08521a3 100644 (file)
@@ -74,15 +74,25 @@ class block_online_users extends block_base {
 
         //Calculate minutes
         $minutes  = floor($timetoshowusers/60);
+        $periodminutes = get_string('periodnminutes', 'block_online_users', $minutes);
+
+        // Count users.
+        $usercount = $onlineusers->count_users();
+        if ($usercount === 0) {
+            $usercount = get_string('nouser', 'block_online_users');
+        } else if ($usercount === 1) {
+            $usercount = get_string('numuser', 'block_online_users', $usercount);
+        } else {
+            $usercount = get_string('numusers', 'block_online_users', $usercount);
+        }
+
+        $this->content->text = '<div class="info">'.$usercount.' ('.$periodminutes.')</div>';
 
         // Verify if we can see the list of users, if not just print number of users
         if (!has_capability('block/online_users:viewlist', $this->page->context)) {
-            if (!$usercount = $onlineusers->count_users()) {
-                $usercount = get_string("none");
-            }
-            $this->content->text = "<div class=\"info\">".get_string("periodnminutes","block_online_users",$minutes).": $usercount</div>";
             return $this->content;
         }
+
         $userlimit = 50; // We'll just take the most recent 50 maximum.
         if ($users = $onlineusers->get_users($userlimit)) {
             foreach ($users as $user) {
@@ -92,11 +102,6 @@ class block_online_users extends block_base {
             $users = array();
         }
 
-        $usercount = $onlineusers->count_users();
-        $usercount = ": $usercount";
-
-        $this->content->text = "<div class=\"info\">(".get_string("periodnminutes","block_online_users",$minutes)."$usercount)</div>";
-
         //Now, we have in users, the list of users to show
         //Because they are online
         if (!empty($users)) {
@@ -133,8 +138,6 @@ class block_online_users extends block_base {
                 $this->content->text .= "</li>\n";
             }
             $this->content->text .= '</ul><div class="clearer"><!-- --></div>';
-        } else {
-            $this->content->text .= "<div class=\"info\">".get_string("none")."</div>";
         }
 
         return $this->content;
index 5d641ee..e5a79e3 100644 (file)
@@ -24,6 +24,9 @@
  */
 
 $string['configtimetosee'] = 'Number of minutes determining the period of inactivity after which a user is no longer considered to be online.';
+$string['nouser'] = 'No online users';
+$string['numuser'] = '{$a} online user';
+$string['numusers'] = '{$a} online users';
 $string['online_users:addinstance'] = 'Add a new online users block';
 $string['online_users:myaddinstance'] = 'Add a new online users block to Dashboard';
 $string['online_users:viewlist'] = 'View list of online users';
index 22591be..e86e4d8 100644 (file)
@@ -24,6 +24,7 @@ Feature: The online users block allow you to see who is currently online
     And I am on "Course 1" course homepage with editing mode on
     When I add the "Online users" block
     Then I should see "Teacher 1" in the "Online users" "block"
+    And I should see "1 online user" in the "Online users" "block"
 
   Scenario: Add the online users on course page and see other logged in users
     Given I log in as "teacher1"
@@ -37,3 +38,4 @@ Feature: The online users block allow you to see who is currently online
     Then I should see "Teacher 1" in the "Online users" "block"
     And I should see "Student 1" in the "Online users" "block"
     And I should not see "Student 2" in the "Online users" "block"
+    And I should see "2 online users" in the "Online users" "block"
index 7f88d76..34207c5 100644 (file)
@@ -14,6 +14,7 @@ Feature: The online users block allow you to see who is currently online on dash
   Scenario: View the online users block on the dashboard and see myself
     Given I log in as "teacher1"
     Then I should see "Teacher 1" in the "Online users" "block"
+    And I should see "1 online user" in the "Online users" "block"
 
   Scenario: View the online users block on the dashboard and see other logged in users
     Given I log in as "student2"
@@ -24,3 +25,4 @@ Feature: The online users block allow you to see who is currently online on dash
     Then I should see "Teacher 1" in the "Online users" "block"
     And I should see "Student 1" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
+    And I should see "3 online users" in the "Online users" "block"
index 5909b1c..6237d3d 100644 (file)
@@ -16,6 +16,7 @@ Feature: The online users block allow you to see who is currently online on fron
     And I navigate to "Turn editing on" node in "Front page settings"
     When I add the "Online users" block
     Then I should see "Admin User" in the "Online users" "block"
+    And I should see "1 online user" in the "Online users" "block"
 
   Scenario: View the online users block on the front page as a logged in user
     Given I log in as "admin"
@@ -30,6 +31,7 @@ Feature: The online users block allow you to see who is currently online on fron
     Then I should see "Admin User" in the "Online users" "block"
     And I should see "Student 1" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
+    And I should see "3 online users" in the "Online users" "block"
 
   Scenario: View the online users block on the front page as a guest
     Given I log in as "admin"
@@ -46,3 +48,4 @@ Feature: The online users block allow you to see who is currently online on fron
     Then I should see "Admin User" in the "Online users" "block"
     And I should see "Student 1" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
+    And I should see "3 online users" in the "Online users" "block"
index 0bf77eb..18ee857 100644 (file)
@@ -45,9 +45,6 @@
 function xmldb_block_quiz_results_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 4a9f5fb..d9bf407 100644 (file)
@@ -47,9 +47,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_recent_activity_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 518fffa..d968dda 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_rss_client_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index b21bf11..1d700ba 100644 (file)
@@ -43,7 +43,7 @@
         "datepublished": "12 January 2016, 9:12 pm"
     }
 }}
-<li>
+<li class="p-y-1">
     {{$title}}
         <div class="link">
             <a href="{{{link}}}" onclick='this.target="_blank"'>{{title}}</a>
@@ -52,6 +52,9 @@
 
     {{$content}}
         {{#description}}
+            <div class="date text-muted muted m-b-1">
+                <small>{{{datepublished}}}</small>
+            </div>
             <div class="description">
                 {{{description}}}
             </div>
index f0d5534..0200032 100644 (file)
@@ -49,9 +49,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_section_links_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 1f0f0a7..7e5e56a 100644 (file)
@@ -48,9 +48,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_selfcompletion_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index ba37266..2a9dceb 100644 (file)
@@ -55,9 +55,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_block_settings_upgrade($oldversion, $block) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index e483abe..f263ddf 100644 (file)
@@ -45,7 +45,7 @@ class block_tags extends block_base {
         if (empty($this->config->title)) {
             $this->title = get_string('pluginname', 'block_tags');
         } else {
-            $this->title = $this->config->title;
+            $this->title = format_string($this->config->title, true, ['context' => $this->context]);
         }
     }
 
index ffa3f2e..6a78673 100644 (file)
Binary files a/calendar/amd/build/calendar.min.js and b/calendar/amd/build/calendar.min.js differ
index 6102199..ecec1a0 100644 (file)
@@ -183,35 +183,38 @@ define([
                 .fail(Notification.exception);
         });
 
-        var eventFormPromise = CalendarCrud.registerEventFormModal(root);
+        var eventFormPromise = CalendarCrud.registerEventFormModal(root),
+            contextId = $(SELECTORS.CALENDAR_MONTH_WRAPPER).data('context-id');
         registerCalendarEventListeners(root, eventFormPromise);
 
-        // Bind click events to calendar days.
-        root.on('click', SELECTORS.DAY, function(e) {
-
-            var target = $(e.target);
-
-            if (!target.is(SELECTORS.VIEW_DAY_LINK)) {
-                var startTime = $(this).attr('data-new-event-timestamp');
-                eventFormPromise.then(function(modal) {
-                    var wrapper = target.closest(CalendarSelectors.wrapper);
-                    modal.setCourseId(wrapper.data('courseid'));
-
-                    var categoryId = wrapper.data('categoryid');
-                    if (typeof categoryId !== 'undefined') {
-                        modal.setCategoryId(categoryId);
-                    }
-
-                    modal.setContextId(wrapper.data('contextId'));
-                    modal.setStartTime(startTime);
-                    modal.show();
-                    return;
-                })
-                .fail(Notification.exception);
-
-                e.preventDefault();
-            }
-        });
+        if (contextId) {
+            // Bind click events to calendar days.
+            root.on('click', SELECTORS.DAY, function (e) {
+
+                var target = $(e.target);
+
+                if (!target.is(SELECTORS.VIEW_DAY_LINK)) {
+                    var startTime = $(this).attr('data-new-event-timestamp');
+                    eventFormPromise.then(function (modal) {
+                        var wrapper = target.closest(CalendarSelectors.wrapper);
+                        modal.setCourseId(wrapper.data('courseid'));
+
+                        var categoryId = wrapper.data('categoryid');
+                        if (typeof categoryId !== 'undefined') {
+                            modal.setCategoryId(categoryId);
+                        }
+
+                        modal.setContextId(wrapper.data('contextId'));
+                        modal.setStartTime(startTime);
+                        modal.show();
+                        return;
+                    })
+                    .fail(Notification.exception);
+
+                    e.preventDefault();
+                }
+            });
+        }
     };
 
     return {
index 049f13d..7592536 100644 (file)
@@ -503,7 +503,7 @@ class core_calendar_external extends external_api {
             $params['aftereventid'] = null;
         }
 
-        $courses = enrol_get_my_courses('*', 'visible DESC,sortorder ASC', 0, [$courseid]);
+        $courses = enrol_get_my_courses('*', null, 0, [$courseid]);
         $courses = array_values($courses);
 
         if (empty($courses)) {
@@ -588,7 +588,7 @@ class core_calendar_external extends external_api {
         }
 
         $renderer = $PAGE->get_renderer('core_calendar');
-        $courses = enrol_get_my_courses('*', 'visible DESC,sortorder ASC', 0, $params['courseids']);
+        $courses = enrol_get_my_courses('*', null, 0, $params['courseids']);
         $courses = array_values($courses);
 
         if (empty($courses)) {
index 6f44e71..beac35f 100644 (file)
@@ -249,10 +249,6 @@ class calendar_event {
         }
 
         $this->properties = $data;
-
-        if (empty($data->context)) {
-            $this->properties->context = $this->calculate_context();
-        }
     }
 
     /**
@@ -343,6 +339,24 @@ class calendar_event {
         return $context;
     }
 
+    /**
+     * Returns the context for this event. The context is calculated
+     * the first time is is requested and then stored in a member
+     * variable to be returned each subsequent time.
+     *
+     * This is a magical getter function that will be called when
+     * ever the context property is accessed, e.g. $event->context.
+     *
+     * @return context
+     */
+    protected function get_context() {
+        if (!isset($this->properties->context)) {
+            $this->properties->context = $this->calculate_context();
+        }
+
+        return $this->properties->context;
+    }
+
     /**
      * Returns an array of editoroptions for this event.
      *
@@ -367,7 +381,7 @@ class calendar_event {
             // Check if we have already resolved the context for this event.
             if ($this->editorcontext === null) {
                 // Switch on the event type to decide upon the appropriate context to use for this event.
-                $this->editorcontext = $this->properties->context;
+                $this->editorcontext = $this->get_context();
                 if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
                     return clean_text($this->properties->description, $this->properties->format);
                 }
@@ -433,7 +447,7 @@ class calendar_event {
 
         // Prepare event data.
         $eventargs = array(
-            'context' => $this->properties->context,
+            'context' => $this->get_context(),
             'objectid' => $this->properties->id,
             'other' => array(
                 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
@@ -485,7 +499,7 @@ class calendar_event {
                 // were set when calculate_context() was called from the constructor.
                 if ($usingeditor) {
                     $this->properties->context = $this->calculate_context();
-                    $this->editorcontext = $this->properties->context;
+                    $this->editorcontext = $this->get_context();
                 }
 
                 $editor = $this->properties->description;
@@ -512,7 +526,7 @@ class calendar_event {
 
             // Log the event entry.
             $eventargs['objectid'] = $this->properties->id;
-            $eventargs['context'] = $this->properties->context;
+            $eventargs['context'] = $this->get_context();
             $event = \core\event\calendar_event_created::create($eventargs);
             $event->trigger();
 
@@ -681,7 +695,7 @@ class calendar_event {
 
         // Trigger an event for the delete action.
         $eventargs = array(
-            'context' => $this->properties->context,
+            'context' => $this->get_context(),
             'objectid' => $this->properties->id,
             'other' => array(
                 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
@@ -715,7 +729,7 @@ class calendar_event {
 
         // If the editor context hasn't already been set then set it now.
         if ($this->editorcontext === null) {
-            $this->editorcontext = $this->properties->context;
+            $this->editorcontext = $this->get_context();
         }
 
         // If the context has been set delete all associated files.
@@ -774,10 +788,10 @@ class calendar_event {
 
                 if ($properties->eventtype === 'site') {
                     // Site context.
-                    $this->editorcontext = $this->properties->context;
+                    $this->editorcontext = $this->get_context();
                 } else if ($properties->eventtype === 'user') {
                     // User context.
-                    $this->editorcontext = $this->properties->context;
+                    $this->editorcontext = $this->get_context();
                 } else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') {
                     // First check the course is valid.
                     $course = $DB->get_record('course', array('id' => $properties->courseid));
@@ -785,7 +799,7 @@ class calendar_event {
                         print_error('invalidcourse');
                     }
                     // Course context.
-                    $this->editorcontext = $this->properties->context;
+                    $this->editorcontext = $this->get_context();
                     // We have a course and are within the course context so we had
                     // better use the courses max bytes value.
                     $this->editoroptions['maxbytes'] = $course->maxbytes;
@@ -793,7 +807,7 @@ class calendar_event {
                     // First check the course is valid.
                     \coursecat::get($properties->categoryid, MUST_EXIST, true);
                     // Course context.
-                    $this->editorcontext = $this->properties->context;
+                    $this->editorcontext = $this->get_context();
                     // We have a course and are within the course context so we had
                     // better use the courses max bytes value.
                     $this->editoroptions['maxbytes'] = $course->maxbytes;
@@ -869,7 +883,7 @@ class calendar_event {
 
         // Prepare event data.
         $eventargs = array(
-            'context' => $this->properties->context,
+            'context' => $this->get_context(),
             'objectid' => $this->properties->id,
             'other' => array(
                 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
@@ -939,7 +953,7 @@ class calendar_event {
 
         if ($this->editorcontext === null) {
             // Switch on the event type to decide upon the appropriate context to use for this event.
-            $this->editorcontext = $this->properties->context;
+            $this->editorcontext = $this->get_context();
 
             if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
                 // We don't have a context here, do a normal format_text.
index 9abff8e..f3cdfeb 100644 (file)
                     <td class="dayblank">&nbsp;</td>
                 {{/prepadding}}
                 {{#days}}
-                    <td class="clickable day text-sm-center text-md-left{{!
+                    <td class="day text-sm-center text-md-left{{!
                             }}{{#istoday}} today{{/istoday}}{{!
                             }}{{#isweekend}} weekend{{/isweekend}}{{!
                             }}{{#durationevents.0}} duration{{/durationevents.0}}{{!
                             }}{{#durationevents}} duration_{{.}}{{/durationevents}}{{!
+                            }}{{#defaulteventcontext}} clickable{{/defaulteventcontext}}{{!
                         }}"
                         data-day-timestamp="{{timestamp}}"
                         data-drop-zone="month-view-day"
index 44d8e25..55e7529 100644 (file)
@@ -52,6 +52,16 @@ class cohort_summary_exporter extends \core\external\exporter {
                 'default' => '',
                 'null' => NULL_ALLOWED
             ),
+            'description' => array(
+                'type' => PARAM_TEXT,
+                'default' => '',
+                'null' => NULL_ALLOWED
+            ),
+            'descriptionformat' => array(
+                'type' => PARAM_INT,
+                'default' => FORMAT_HTML,
+                'null' => NULL_ALLOWED
+            ),
             'visible' => array(
                 'type' => PARAM_BOOL,
             )
index fc16c0c..9d04083 100644 (file)
@@ -165,7 +165,7 @@ class api {
         require_capability('moodle/competency:competencymanage', $competency->get_context());
 
         // Reset the sortorder, use reorder instead.
-        $competency->set('sortorder', null);
+        $competency->set('sortorder', 0);
         $competency->create();
 
         \core\event\competency_created::create_from_competency($competency)->trigger();
index c537508..9e417e9 100644 (file)
@@ -76,7 +76,7 @@ class competency extends persistent {
                 'default' => FORMAT_HTML
             ),
             'sortorder' => array(
-                'default' => null,
+                'default' => 0,
                 'type' => PARAM_INT
             ),
             'parentid' => array(
index 1024e57..0c35f41 100644 (file)
@@ -841,10 +841,10 @@ class external extends external_api {
         $validcolumns = array('id', 'shortname', 'description', 'sortorder', 'idnumber',
             'parentid', 'competencyframeworkid');
         foreach ($params['filters'] as $filter) {
-            if (!in_array($filter->column, $validcolumns)) {
+            if (!in_array($filter['column'], $validcolumns)) {
                 throw new invalid_parameter_exception('Filter column was invalid');
             }
-            $safefilters[$filter->column] = $filter->value;
+            $safefilters[$filter['column']] = $filter['value'];
         }
 
         $context = null;
index 3e0c8f8..8c6df0b 100644 (file)
@@ -53,7 +53,7 @@ class template_competency extends persistent {
             ),
             'sortorder' => array(
                 'type' => PARAM_INT,
-                'default' => null,
+                'default' => 0,
             ),
         );
     }
index 392e1c5..ad97f6c 100644 (file)
@@ -282,7 +282,6 @@ class user_competency_course extends persistent {
               ORDER BY p.timesproficient ASC, c.id DESC';
 
         $results = $DB->get_records_sql($sql, $params, $skip, $limit);
-        $a = $DB->get_records_sql('SELECT * from {' . self::TABLE . '}');
 
         $comps = array();
         foreach ($results as $r) {
index 3880a2a..5b53589 100644 (file)
@@ -66,7 +66,7 @@ class user_competency_plan extends persistent {
             ),
             'sortorder' => array(
                 'type' => PARAM_INT,
-                'default' => null,
+                'default' => 0,
             ),
         );
     }
index f8a09e0..1a26d38 100644 (file)
@@ -281,8 +281,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
             'idnumber' => 'idnumber' . $number,
             'description' => 'description' . $number,
             'descriptionformat' => FORMAT_HTML,
-            'competencyframeworkid' => $frameworkid,
-            'sortorder' => 0
+            'competencyframeworkid' => $frameworkid
         );
         $result = external::create_competency($competency);
         return (object) external_api::clean_returnvalue(external::create_competency_returns(), $result);
@@ -294,8 +293,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
             'shortname' => 'shortname' . $number,
             'idnumber' => 'idnumber' . $number,
             'description' => 'description' . $number,
-            'descriptionformat' => FORMAT_HTML,
-            'sortorder' => 0
+            'descriptionformat' => FORMAT_HTML
         );
         $result = external::update_competency($competency);
         return external_api::clean_returnvalue(external::update_competency_returns(), $result);
@@ -2803,4 +2801,43 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $result = external::update_course_competency_settings($course->id, array('pushratingstouserplans' => true));
     }
 
+    /**
+     * Test that we can list competencies with a filter.
+     *
+     * @return void
+     */
+    public function test_list_competencies_with_filter() {
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $dg = $this->getDataGenerator();
+        $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency');
+
+        $framework = $lpg->create_framework();
+        $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+        $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+        $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+        $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+        $c5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+
+        // Test if removing competency from plan don't create sortorder holes.
+        $filters = [];
+        $sort = 'id';
+        $order = 'ASC';
+        $skip = 0;
+        $limit = 0;
+        $result = external::list_competencies($filters, $sort, $order, $skip, $limit);
+        $this->assertCount(5, $result);
+
+        $result = external::list_competencies($filters, $sort, $order, 2, $limit);
+        $this->assertCount(3, $result);
+        $result = external::list_competencies($filters, $sort, $order, 2, 2);
+        $this->assertCount(2, $result);
+
+        $filter = $result[0]->shortname;
+        $filters[0] = ['column' => 'shortname', 'value' => $filter];
+        $result = external::list_competencies($filters, $sort, $order, $skip, $limit);
+        $this->assertCount(1, $result);
+        $this->assertEquals($filter, $result[0]->shortname);
+    }
+
 }
index b610108..24384e9 100644 (file)
@@ -249,7 +249,7 @@ class core_completion_external extends external_api {
 
         $completion = new completion_info($course);
         $activities = $completion->get_activities();
-        $progresses = $completion->get_progress_all();
+        $progresses = $completion->get_progress_all('u.id = :uid', ['uid' => $params['userid']]);
         $userprogress = $progresses[$user->id];
 
         $results = array();
index 29c570b..592c84e 100644 (file)
@@ -279,7 +279,8 @@ class completion_criteria_activity extends completion_criteria {
             $details['requirement'][] = get_string('markingyourselfcomplete', 'completion');
         } elseif ($cm->completion == COMPLETION_TRACKING_AUTOMATIC) {
             if ($cm->completionview) {
-                $details['requirement'][] = get_string('viewingactivity', 'completion', $this->module);
+                $modulename = core_text::strtolower(get_string('modulename', $this->module));
+                $details['requirement'][] = get_string('viewingactivity', 'completion', $modulename);
             }
 
             if (!is_null($cm->completiongradeitemnumber)) {
index 4c03e2d..47c8c18 100644 (file)
@@ -88,7 +88,7 @@ class course_edit_form extends moodleform {
             }
         } else {
             if (has_capability('moodle/course:changecategory', $coursecontext)) {
-                $displaylist = coursecat::make_categories_list('moodle/course:create');
+                $displaylist = coursecat::make_categories_list('moodle/course:changecategory');
                 if (!isset($displaylist[$course->category])) {
                     //always keep current
                     $displaylist[$course->category] = coursecat::get($course->category, MUST_EXIST, true)->get_formatted_name();
index 64b2575..a086ba6 100644 (file)
@@ -647,7 +647,7 @@ class core_course_renderer extends plugin_renderer_base {
             }
         } else {
             $linkclasses .= ' dimmed';
-            $textclasses .= ' dimmed_text';
+            $textclasses .= ' dimmed dimmed_text';
         }
         return array($linkclasses, $textclasses);
     }
@@ -1988,13 +1988,7 @@ class core_course_renderer extends plugin_renderer_base {
         }
 
         $output = '';
-        if (!empty($CFG->navsortmycoursessort)) {
-            // sort courses the same as in navigation menu
-            $sortorder = 'visible DESC,'. $CFG->navsortmycoursessort.' ASC';
-        } else {
-            $sortorder = 'visible DESC,sortorder ASC';
-        }
-        $courses  = enrol_get_my_courses('summary, summaryformat', $sortorder);
+        $courses  = enrol_get_my_courses('summary, summaryformat');
         $rhosts   = array();
         $rcourses = array();
         if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
index c38575a..351cf66 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_database_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 91ea55b..59ae42e 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_flatfile_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 51a3a0b..cb6fb76 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_guest_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 53366c6..a4e1df7 100644 (file)
@@ -33,9 +33,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_imsenterprise_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index e3ed112..dd8d9a8 100644 (file)
@@ -206,7 +206,7 @@ class enrol_imsenterprise_testcase extends advanced_testcase {
         $this->set_xml_file(array($imsuser));
 
         $this->imsplugin->cron();
-        $this->assertEquals(1, $DB->get_field('user', 'deleted', array('id' => $user->id), '*', MUST_EXIST));
+        $this->assertEquals(1, $DB->get_field('user', 'deleted', array('id' => $user->id), MUST_EXIST));
     }
 
     /**
@@ -227,7 +227,7 @@ class enrol_imsenterprise_testcase extends advanced_testcase {
         $this->set_xml_file(array($imsuser));
 
         $this->imsplugin->cron();
-        $this->assertEquals(0, $DB->get_field('user', 'deleted', array('id' => $user->id), '*', MUST_EXIST));
+        $this->assertEquals(0, $DB->get_field('user', 'deleted', array('id' => $user->id), MUST_EXIST));
     }
 
     /**
index 7556419..0447951 100644 (file)
@@ -117,6 +117,7 @@ class course_enrolment_manager {
     private $_plugins = null;
     private $_allplugins = null;
     private $_roles = null;
+    private $_visibleroles = null;
     private $_assignableroles = null;
     private $_assignablerolesothers = null;
     private $_groups = null;
@@ -593,6 +594,18 @@ class course_enrolment_manager {
         return $this->_roles;
     }
 
+    /**
+     * Gets all of the roles this course can contain.
+     *
+     * @return array
+     */
+    public function get_viewable_roles() {
+        if ($this->_visibleroles === null) {
+            $this->_visibleroles = get_viewable_roles($this->context);
+        }
+        return $this->_visibleroles;
+    }
+
     /**
      * Gets all of the assignable roles for this course.
      *
@@ -1032,7 +1045,7 @@ class course_enrolment_manager {
         $strunenrol = get_string('unenrol', 'enrol');
         $stredit = get_string('edit');
 
-        $allroles   = $this->get_all_roles();
+        $visibleroles   = $this->get_viewable_roles();
         $assignable = $this->get_assignable_roles();
         $allgroups  = $this->get_all_groups();
         $context    = $this->get_context();
@@ -1054,7 +1067,15 @@ class course_enrolment_manager {
                 if (!is_siteadmin() and !isset($assignable[$rid])) {
                     $unchangeable = true;
                 }
-                $details['roles'][$rid] = array('text'=>$allroles[$rid]->localname, 'unchangeable'=>$unchangeable);
+
+                if (isset($visibleroles[$rid])) {
+                    $label = $visibleroles[$rid];
+                } else {
+                    $label = get_string('novisibleroles', 'role');
+                    $unchangeable = true;
+                }
+
+                $details['roles'][$rid] = array('text' => $label, 'unchangeable' => $unchangeable);
             }
 
             // Users
index 0086edf..cec4ea4 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_manual_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 2eefd4a..6b224dd 100644 (file)
@@ -62,7 +62,7 @@ class enrol_manual_externallib_testcase extends externallib_advanced_testcase {
         $this->assignUserCapability('moodle/course:view', $context2->id, $roleid);
         $this->assignUserCapability('moodle/role:assign', $context2->id, $roleid);
 
-        allow_assign($roleid, 3);
+        core_role_set_assign_allowed($roleid, 3);
 
         // Call the external function.
         enrol_manual_external::enrol_users(array(
index 29f8564..6f22cca 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_mnet_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index 5ed2ae7..bdbbbf6 100644 (file)
@@ -45,9 +45,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_paypal_upgrade($oldversion) {
     global $CFG;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
index f9dff59..4965298 100644 (file)
@@ -27,9 +27,6 @@ defined('MOODLE_INTERNAL') || die();
 function xmldb_enrol_self_upgrade($oldversion) {
     global $CFG, $DB;
 
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     if ($oldversion < 2016052301) {
         // Get roles with manager archetype.
         $managerroles = get_archetype_roles('manager');
diff --git a/enrol/tests/behat/role_visibility.feature b/enrol/tests/behat/role_visibility.feature
new file mode 100644 (file)
index 0000000..7c8d911
--- /dev/null
@@ -0,0 +1,38 @@
+@core @core_enrol
+Feature: Test role visibility for the participants page
+  In order to control access
+  As an admin
+  I need to control which roles can see each other
+
+  Background: Add a bunch of users
+    Given  the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | learner1 | Learner   | 1        | learner1@example.com |
+      | teacher1 | Teacher   | 1        | teacher1@example.com |
+      | manager1 | Manager   | 1        | manager1@example.com |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | learner1 | C1     | student        |
+      | teacher1 | C1     | editingteacher |
+      | manager1 | C1     | manager        |
+
+  Scenario: Check the default roles are visible
+    Given I log in as "manager1"
+    And I follow "Course 1"
+    When I navigate to "Enrolled users" node in "Course administration > Users"
+    Then "Learner 1" row "Roles" column of "participants" table should contain "Student"
+    And "Teacher 1" row "Roles" column of "participants" table should contain "Teacher"
+    And "Manager 1" row "Roles" column of "participants" table should contain "Manager"
+    And I should not see "No Roles" in the "table#participants" "css_element"
+
+  Scenario: Do not allow managers to view any roles but manager and check they are hidden
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    When I navigate to "Enrolled users" node in "Course administration > Users"
+    Then "Learner 1" row "Roles" column of "participants" table should contain "Student"
+    And "Teacher 1" row "Roles" column of "participants" table should contain "Teacher"
+    And "Manager 1" row "Roles" column of "participants" table should not contain "Manager"
+    And "Manager 1" row "Roles" column of "participants" table should contain "No roles"
index d52dc6f..1678462 100644 (file)
@@ -55,10 +55,24 @@ class core_enrollib_testcase extends advanced_testcase {
 
         $category1 = $this->getDataGenerator()->create_category(array('visible'=>0));
         $category2 = $this->getDataGenerator()->create_category();
-        $course1 = $this->getDataGenerator()->create_course(array('category'=>$category1->id));
-        $course2 = $this->getDataGenerator()->create_course(array('category'=>$category2->id));
-        $course3 = $this->getDataGenerator()->create_course(array('category'=>$category2->id, 'visible'=>0));
-        $course4 = $this->getDataGenerator()->create_course(array('category'=>$category2->id));
+
+        $course1 = $this->getDataGenerator()->create_course(array(
+            'shortname' => 'Z',
+            'category' => $category1->id,
+        ));
+        $course2 = $this->getDataGenerator()->create_course(array(
+            'shortname' => 'X',
+            'category' => $category2->id,
+        ));
+        $course3 = $this->getDataGenerator()->create_course(array(
+            'shortname' => 'Y',
+            'category' => $category2->id,
+            'visible' => 0,
+        ));
+        $course4 = $this->getDataGenerator()->create_course(array(
+            'shortname' => 'W',
+            'category' => $category2->id,
+        ));
 
         $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST);
         $DB->set_field('enrol', 'status', ENROL_INSTANCE_DISABLED, array('id'=>$maninstance1->id));
@@ -150,6 +164,18 @@ class core_enrollib_testcase extends advanced_testcase {
 
         $courses = enrol_get_all_users_courses($user2->id, false, null, 'id DESC');
         $this->assertEquals(array($course3->id, $course2->id, $course1->id), array_keys($courses));
+
+        // Make sure that implicit sorting defined in navsortmycoursessort is respected.
+
+        $CFG->navsortmycoursessort = 'shortname';
+
+        $courses = enrol_get_all_users_courses($user1->id);
+        $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
+
+        // But still the explicit sorting takes precedence over the implicit one.
+
+        $courses = enrol_get_all_users_courses($user1->id, false, null, 'shortname DESC');
+        $this->assertEquals(array($course1->id, $course3->id, $course2->id), array_keys($courses));
     }
 
     public function test_enrol_user_sees_own_courses() {
@@ -590,15 +616,15 @@ class core_enrollib_testcase extends advanced_testcase {
         // Create test user and 4 courses, two of which have guest access enabled.
         $user = $this->getDataGenerator()->create_user();
         $course1 = $this->getDataGenerator()->create_course(
-                (object)array('shortname' => 'Z',
+                (object)array('shortname' => 'X',
                 'enrol_guest_status_0' => ENROL_INSTANCE_DISABLED,
                 'enrol_guest_password_0' => ''));
         $course2 = $this->getDataGenerator()->create_course(
-                (object)array('shortname' => 'Y',
+                (object)array('shortname' => 'Z',
                 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
                 'enrol_guest_password_0' => ''));
         $course3 = $this->getDataGenerator()->create_course(
-                (object)array('shortname' => 'X',
+                (object)array('shortname' => 'Y',
                 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
                 'enrol_guest_password_0' => 'frog'));
         $course4 = $this->getDataGenerator()->create_course(
@@ -645,10 +671,19 @@ class core_enrollib_testcase extends advanced_testcase {
         $this->assertObjectHasAttribute('summary', $courses[$course3->id]);
         $this->assertObjectHasAttribute('summaryformat', $courses[$course3->id]);
 
-        // Check sort parameter still works.
-        $courses = enrol_get_my_courses(null, 'shortname', 0, [], true);
+        // By default, courses are ordered by sortorder - which by default is most recent first.
+        $courses = enrol_get_my_courses(null, null, 0, [], true);
         $this->assertEquals([$course3->id, $course2->id, $course1->id], array_keys($courses));
 
+        // Make sure that implicit sorting defined in navsortmycoursessort is respected.
+        $CFG->navsortmycoursessort = 'shortname';
+        $courses = enrol_get_my_courses(null, null, 0, [], true);
+        $this->assertEquals([$course1->id, $course3->id, $course2->id], array_keys($courses));
+
+        // But still the explicit sorting takes precedence over the implicit one.
+        $courses = enrol_get_my_courses(null, 'shortname DESC', 0, [], true);
+        $this->assertEquals([$course2->id, $course3->id, $course1->id], array_keys($courses));
+
         // Check filter parameter still works.
         $courses = enrol_get_my_courses(null, 'id', 0, [$course2->id, $course3->id, $course4->id], true);
         $this->assertEquals([$course2->id, $course3->id], array_keys($courses));
index 528fe37..09ff3ba 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in /enrol/* - plugins,
 information provided here is intended especially for developers.
 
+=== 3.5 ===
+
+* Default sorting in enrol_get_my_courses(), enrol_get_all_users_courses() and enrol_get_users_courses() now respects
+  the site setting "navsortmycoursessort" and should be consistently used when displaying the courses in the UI.
+
 === 3.4 ===
 
 * render_course_enrolment_users_table method has been removed from the renderer. The enrolled users page is now
index 97b62b7..aa3ada7 100644 (file)
@@ -156,10 +156,10 @@ class enrol_users_filter_form extends moodleform {
         // names if applied. The reason for not restricting to roles that can
         // be assigned at course level is that upper-level roles display in the
         // enrolments table so it makes sense to let users filter by them.
-        $allroles = $manager->get_all_roles();
+        $visibleroles = $manager->get_viewable_roles();
         $rolenames = array();
-        foreach ($allroles as $id => $role) {
-            $rolenames[$id] = $role->localname;
+        foreach ($visibleroles as $id => $role) {
+            $rolenames[$id] = $role;
         }
         $mform->addElement('select', 'role', get_string('role'),
                 array(0 => get_string('all')) + $rolenames);
index 32793d3..da8c5ba 100644 (file)
@@ -127,7 +127,7 @@ class converter {
         if ($status === conversion::STATUS_PENDING || $status === conversion::STATUS_FAILED) {
             // The current status is either pending or failed.
             // Attempt to pick up a new converter and convert the document.
-            $from = \core_filetypes::get_file_extension($file->get_mimetype());
+            $from = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
             $converters = $this->get_document_converter_classes($from, $format);
             $currentconverter = $this->get_next_converter($converters, $conversion->get('converter'));
 
@@ -225,9 +225,9 @@ class converter {
             return false;
         }
 
-        $from = \core_filetypes::get_file_extension($file->get_mimetype());
+        $from = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
         if (!$from) {
-            // No mime type could be found. Unable to determine converter.
+            // No file extension could be found. Unable to determine converter.
             return false;
         }
 
index 950d593..e345c1e 100644 (file)
@@ -341,7 +341,7 @@ class core_files_converter_testcase extends advanced_testcase {
     }
 
     /**
-     * Test the can_convert_storedfile_to function with a file with indistinguished mimetype.
+     * Test the can_convert_storedfile_to function with a file with a known mimetype and extension.
      */
     public function test_can_convert_storedfile_to_docx() {
         $returnvalue = (object) [];
@@ -352,8 +352,7 @@ class core_files_converter_testcase extends advanced_testcase {
 
         $types = \core_filetypes::get_types();
 
-        // A file with filename '.' is a directory.
-        $file = $this->get_stored_file('example content', 'example', [
+        $file = $this->get_stored_file('example content', 'example.docx', [
                 'mimetype' => $types['docx']['type'],
             ]);
 
index 7edde8a..5294046 100644 (file)
@@ -33,26 +33,6 @@ function xmldb_filter_mathjaxloader_upgrade($oldversion) {
 
     require_once($CFG->dirroot . '/filter/mathjaxloader/db/upgradelib.php');
 
-    if ($oldversion < 2016032200) {
-
-        $httpurl = get_config('filter_mathjaxloader', 'httpurl');
-        // Don't change the config if it has been manually changed to something besides the default setting value.
-        if ($httpurl === "http://cdn.mathjax.org/mathjax/2.5-latest/MathJax.js") {
-            set_config('httpurl', 'http://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js', 'filter_mathjaxloader');
-        }
-
-        $httpsurl = get_config('filter_mathjaxloader', 'httpsurl');
-        // Don't change the config if it has been manually changed to something besides the default setting value.
-        if ($httpsurl === "https://cdn.mathjax.org/mathjax/2.5-latest/MathJax.js") {
-            set_config('httpsurl', 'https://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js', 'filter_mathjaxloader');
-        }
-
-        upgrade_plugin_savepoint(true, 2016032200, 'filter', 'mathjaxloader');
-    }
-
-    // Moodle v3.1.0 release upgrade line.
-    // Put any upgrade step following this.
-
     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.
@@ -78,8 +58,10 @@ function xmldb_filter_mathjaxloader_upgrade($oldversion) {
         }
         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');
@@ -97,6 +79,7 @@ function xmldb_filter_mathjaxloader_upgrade($oldversion) {
 
         upgrade_plugin_savepoint(true, 2017040300, 'filter', 'mathjaxloader');
     }
+
     if ($oldversion < 2017042602) {
 
         $httpsurl = get_config('filter_mathjaxloader', 'httpsurl');