diff --git a/.github/workflows/onebyone.yml b/.github/workflows/onebyone.yml index 7460f50cf2a03..3b9fe62a40d83 100644 --- a/.github/workflows/onebyone.yml +++ b/.github/workflows/onebyone.yml @@ -51,7 +51,7 @@ jobs: chunk=$(((($count % $chunks)) + 1)) echo "$testname $testfile" >> ./chunk_$chunk.txt done < <(grep "function test_" "${testfile}" | sed -r "s/^.*function (test_[a-zA-Z0-9_]+).*/\1/") - done < <(find . -name "*_test.php") + done < <(find . -name "*_test.php" -not -path "*/fixtures/*") # Generate the matrix to run tests. echo "matrix=$(ls -1 chunk_*.txt | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT echo "$count individual tests collected in $chunks files" diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 9e4d0ef9fac06..e13d9c7d7e3a3 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -77,27 +77,20 @@ jobs: # db: pgsql steps: - - name: Setting up DB mysql + - name: Run MySQL Server if: ${{ matrix.db == 'mysqli' }} - uses: moodlehq/mysql-action@v1 - with: - collation server: utf8mb4_bin - mysql version: 8.0 - mysql database: test - mysql user: test - mysql password: test - use tmpfs: true - tmpfs size: '1024M' - extra conf: --skip-log-bin - - # - name: Setting up DB pgsql - # if: ${{ matrix.db == 'pgsql' }} - # uses: m4nu56/postgresql-action@v1 - # with: - # postgresql version: 13 - # postgresql db: test - # postgresql user: test - # postgresql password: test + run: | + docker run --rm \ + -e MYSQL_DATABASE=test \ + -e MYSQL_USER=test \ + -e MYSQL_PASSWORD=test \ + -e MYSQL_ROOT_PASSWORD=test \ + -p 3306:3306 \ + -d \ + --tmpfs /var/lib/mysql:rw,noexec,nosuid,size=1024M \ + mysql:8.0 \ + --skip-log-bin \ + --collation-server=utf8mb4_bin - name: Configuring git vars uses: rlespinasse/github-slug-action@v4 diff --git a/MDL-87443-2025121117305654.yml b/MDL-87443-2025121117305654.yml new file mode 100644 index 0000000000000..0456305686b83 --- /dev/null +++ b/MDL-87443-2025121117305654.yml @@ -0,0 +1,7 @@ +issueNumber: MDL-87443 +notes: + core: + - message: >- + There is a new Behat `toast_message` named selector to more easily + assert the presence of Toast messages on the page + type: improved diff --git a/UPGRADING.md b/UPGRADING.md index b65aea2332d41..49df98eb943d0 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -6,6 +6,16 @@ More detailed information on key changes can be found in the [Developer update n The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com). +## 4.5.9 + +### core + +#### Changed + +- `\core\output\core_renderer::confirm()`'s `$displayoptions` parameter now also accepts a `headinglevel` option that developers can use to specify the heading level of the confirmation's heading. If not specified, the confirmation heading will be rendered in an `h4` tag. + + For more information see [MDL-87694](https://tracker.moodle.org/browse/MDL-87694) + ## 4.5.8 ### core diff --git a/admin/index.php b/admin/index.php index 90d5307ecb762..e6815e03fce49 100644 --- a/admin/index.php +++ b/admin/index.php @@ -941,9 +941,27 @@ $output = $PAGE->get_renderer('core', 'admin'); -echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed, $cronoverdue, $dbproblems, - $maintenancemode, $availableupdates, $availableupdatesfetch, $buggyiconvnomb, - $registered, $cachewarnings, $eventshandlers, $themedesignermode, $devlibdir, - $mobileconfigured, $overridetossl, $invalidforgottenpasswordurl, $croninfrequent, - $showcampaigncontent, $showfeedbackencouragement, $servicesandsupportcontent, - $xmlrpcwarning); +echo $output->admin_notifications_page( + $maturity, + $insecuredataroot, + $errorsdisplayed, + $cronoverdue, + $dbproblems, + $maintenancemode, + $availableupdates, + $availableupdatesfetch, + $buggyiconvnomb, + $registered, + $cachewarnings, + $eventshandlers, + $themedesignermode, + $devlibdir, + $mobileconfigured, + $overridetossl, + $invalidforgottenpasswordurl, + $croninfrequent, + $showcampaigncontent, + $showfeedbackencouragement, + $servicesandsupportcontent, + $xmlrpcwarning +); diff --git a/admin/renderer.php b/admin/renderer.php index de111cd35b88c..95298de646745 100644 --- a/admin/renderer.php +++ b/admin/renderer.php @@ -282,13 +282,30 @@ public function upgrade_confirm_abort_install_page(array $abortable, moodle_url * * @return string HTML to output. */ - public function admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed, - $cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch, - $buggyiconvnomb, $registered, array $cachewarnings = array(), $eventshandlers = 0, - $themedesignermode = false, $devlibdir = false, $mobileconfigured = false, - $overridetossl = false, $invalidforgottenpasswordurl = false, $croninfrequent = false, - $showcampaigncontent = false, bool $showfeedbackencouragement = false, bool $showservicesandsupport = false, - $xmlrpcwarning = '') { + public function admin_notifications_page( + $maturity, + $insecuredataroot, + $errorsdisplayed, + $cronoverdue, + $dbproblems, + $maintenancemode, + $availableupdates, + $availableupdatesfetch, + $buggyiconvnomb, + $registered, + array $cachewarnings = [], + $eventshandlers = 0, + $themedesignermode = false, + $devlibdir = false, + $mobileconfigured = false, + $overridetossl = false, + $invalidforgottenpasswordurl = false, + $croninfrequent = false, + $showcampaigncontent = false, + bool $showfeedbackencouragement = false, + bool $showservicesandsupport = false, + $xmlrpcwarning = '' + ) { global $CFG; $output = ''; @@ -314,6 +331,7 @@ public function admin_notifications_page($maturity, $insecuredataroot, $errorsdi $output .= $this->mobile_configuration_warning($mobileconfigured); $output .= $this->forgotten_password_url_warning($invalidforgottenpasswordurl); $output .= $this->mnet_deprecation_warning($xmlrpcwarning); + $output .= $this->moodlenet_removal_warning(); $output .= $this->userfeedback_encouragement($showfeedbackencouragement); $output .= $this->services_and_support_content($showservicesandsupport); $output .= $this->campaign_content($showcampaigncontent); @@ -2267,6 +2285,21 @@ protected function mnet_deprecation_warning($xmlrpcwarning) { return $this->warning($xmlrpcwarning); } + /** + * Display a warning about the removal of MoodleNet integration. + * + * @return string HTML to output. + */ + protected function moodlenet_removal_warning(): string { + $moodlenetenabled = get_config('tool_moodlenet', 'enablemoodlenet'); + if (!empty($moodlenetenabled)) { + $moodlenetwarning = get_string('moodlenetremovalwarning', 'admin'); + return $this->warning($moodlenetwarning); + } + + return ''; + } + /** * Renders the theme selector list. * diff --git a/admin/tool/dataprivacy/tests/behat/user_data_request.feature b/admin/tool/dataprivacy/tests/behat/user_data_request.feature new file mode 100644 index 0000000000000..a39e34a21816d --- /dev/null +++ b/admin/tool/dataprivacy/tests/behat/user_data_request.feature @@ -0,0 +1,100 @@ +@tool @tool_dataprivacy +Feature: Authorized users can request others personal data + In order to export or access another users data + As a designated role + I need the correct permissions + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | user1 | User1 | One | user1@example.com | + | user2 | User2 | Two | user2@example.com | + | officer1 | Officer1 | One | officer1@example.com | + # Create Privacy Officer Role. + And the following "role" exists: + | shortname | privacyofficer | + | name | Privacy Officer | + | context_system | 1 | + | tool/dataprivacy:managedataregistry | allow | + | tool/dataprivacy:managedatarequests | allow | + | tool/dataprivacy:makedatarequestsforchildren | allow | + | moodle/site:configview | allow | + | moodle/category:viewhiddencategories | allow | + | moodle/course:viewhiddencourses | allow | + | moodle/course:viewhiddenactivities | allow | + | moodle/course:view | allow | + # Create Parent Role. + And the following "role" exists: + | shortname | parentrole | + | name | Parent Role | + | context_user | 1 | + | moodle/user:viewdetails | allow | + | moodle/user:viewalldetails | allow | + | moodle/user:readuserblogs | allow | + | moodle/user:readuserposts | allow | + | moodle/user:viewuseractivitiesreport | allow | + | moodle/user:editprofile | allow | + | tool/policy:acceptbehalf | allow | + | tool/dataprivacy:makedatarequestsforchildren | allow | + # Add permission to allow parent to make requests on behalf of child user. + And the following config values are set as admin: + | contactdataprotectionofficer | 1 | tool_dataprivacy | + And I log in as "admin" + + @javascript + Scenario: Privacy officer can request for other user's personal data + Given I navigate to "Users > Permissions > Assign system roles" in site administration + # Assign Privacy Officer role to officer1. + And I follow "Privacy Officer" + And I set the field "addselect_searchtext" to "Officer1" + And I set the field "addselect" to "Officer1 One (officer1@example.com)" + And I press "Add" + # Navigate to home in order to navigate properly to Privacy settings. + And I am on site homepage + # Select Privacy officer in the Orivacy officer role mapping setting. + And I navigate to "Users > Privacy and policies > Privacy settings" in site administration + And I click on "Privacy Officer" "checkbox" + And I press "Save changes" + And I log in as "officer1" + And I navigate to "Users > Privacy and policies > Data requests" in site administration + # Create a new request as the designated privacy officer. + When I follow "New request" + And I set the field "User" to "User1 One" + And I set the field "Comment" to "User One data" + And I press "Save changes" + # Confirm that the new data request is successfully created for selected user with status "Awaiting approval". + Then the following should exist in the "generaltable" table: + | Type | User | Requested by | Status | Message | + | Export | User1 One | Officer1 One | Awaiting approval | User One data | + + @javascript + Scenario: Parent user can request data on behalf of child user + Given I navigate to "Users > Accounts > Browse list of users" in site administration + And I follow "User1 One" + And I click on "Preferences" "link" in the ".profile_tree" "css_element" + # Assign user2 as parent for user1. + And I follow "Assign roles relative to this user" + And I follow "Parent" + And I set the field "Potential users" to "User2 Two (user2@example.com)" + And I click on "Add" "button" in the "#page-content" "css_element" + And I log in as "user2" + And I follow "Profile" in the user menu + And I follow "Data requests" + # As parent, create a data request for a child user. + And I follow "New request" + And I click on "User" "field" + When I type "User1 One" + # Confirm that only the parent's child users can be searched and selected. + Then I should see "User1 One" + And I type "User2 Two" + And I should see "No suggestions" + And I type "Officer1 One" + And I should see "No suggestions" + And I set the field "Search" to "User1" + And I set the field "Comment" to "This is a comment" + And I press "Save changes" + # Confirm that data request was successfully made by parent on behalf of child user. + And I should see "Your request has been submitted to the privacy officer" + And the following should exist in the "generaltable" table: + | Type | Requested by | Status | Message | + | Export all of my personal data (User1 One) | User2 Two | Awaiting approval | This is a comment | diff --git a/admin/tool/moodlenet/lang/en/tool_moodlenet.php b/admin/tool/moodlenet/lang/en/tool_moodlenet.php index 0e8ea8a0504c4..5c419a3b39277 100644 --- a/admin/tool/moodlenet/lang/en/tool_moodlenet.php +++ b/admin/tool/moodlenet/lang/en/tool_moodlenet.php @@ -38,6 +38,9 @@ $string['defaultmoodlenetname'] = "MoodleNet instance name"; $string['defaultmoodlenetnamevalue'] = 'MoodleNet Central'; $string['defaultmoodlenetname_desc'] = 'The name of the MoodleNet instance available via the activity chooser.'; +$string['removalwarning_feature'] = 'If you need to continue using MoodleNet, contact your site administrator about setting up a self-hosted MoodleNet instance.'; +$string['removalwarning_service'] = 'After the date, you will no longer be able to browse or add content from MoodleNet Central.'; +$string['removalwarning_title'] = 'The MoodleNet service will be shut down on 20 April 2026.'; $string['enablemoodlenet'] = 'Enable MoodleNet integration (inbound)'; $string['enablemoodlenet_desc'] = 'If enabled, a user with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their course. In addition, a user with the capability to restore backups can select a backup file on MoodleNet and restore it into Moodle.'; $string['errorduringdownload'] = 'An error occurred while downloading the file: {$a}'; diff --git a/admin/tool/moodlenet/templates/chooser_moodlenet.mustache b/admin/tool/moodlenet/templates/chooser_moodlenet.mustache index 3e424bde81ec4..e9f8ed362eae1 100644 --- a/admin/tool/moodlenet/templates/chooser_moodlenet.mustache +++ b/admin/tool/moodlenet/templates/chooser_moodlenet.mustache @@ -27,8 +27,16 @@
- +

{{#str}} instancedescription, tool_moodlenet {{/str}}

+ + {{! Removal warning - always visible when MoodleNet integration is enabled }} + +

{{#str}} connectandbrowse, tool_moodlenet {{/str}}

Accounts > Upload users" in site administration + When I upload "lib/tests/fixtures/QA_user_enrol.txt" file to "File" filemanager + And I press "Upload users" + And I press "Upload users" + And I press "Continue" + And I upload "lib/tests/fixtures/QA_user_suspend.txt" file to "File" filemanager + And I press "Upload users" + And I set the field "Upload type" to "Update existing users only" + And I press "Upload users" + And I press "Continue" + And I am on the "Course 1" "enrolled users" page + Then the following should exist in the "participants" table: + | First name | Status | + | Learner One | Active | + | Learner Two | Active | + And I am on the "Course 2" "enrolled users" page + And the following should exist in the "participants" table: + | First name | Status | + | Learner One | Active | + | Learner Two | Suspended | + And I am on the "Course 3" "enrolled users" page + And the following should exist in the "participants" table: + | First name | Status | + | Learner One | Suspended | + | Learner Two | Active | diff --git a/auth/oauth2/classes/api.php b/auth/oauth2/classes/api.php index 5bf053cfcf540..8856c97708d3b 100644 --- a/auth/oauth2/classes/api.php +++ b/auth/oauth2/classes/api.php @@ -197,9 +197,7 @@ public static function send_confirm_link_login_email($userinfo, $issuer, $userid $data->link = $confirmationurl->out(false); $message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data); - - $data->link = $confirmationurl->out(); - $messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true); + $messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false); $user->mailformat = 1; // Always send HTML version as well. @@ -339,9 +337,7 @@ public static function send_confirm_account_email($userinfo, $issuer) { $data->link = $confirmationurl->out(false); $message = get_string('confirmaccountemail', 'auth_oauth2', $data); - - $data->link = $confirmationurl->out(); - $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true); + $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false); $user->mailformat = 1; // Always send HTML version as well. diff --git a/auth/oauth2/lang/en/auth_oauth2.php b/auth/oauth2/lang/en/auth_oauth2.php index 166cea78df4a0..4e942ac1e2b40 100644 --- a/auth/oauth2/lang/en/auth_oauth2.php +++ b/auth/oauth2/lang/en/auth_oauth2.php @@ -27,19 +27,14 @@ $string['auth_oauth2settings'] = 'OAuth 2 authentication settings.'; $string['confirmaccountemail'] = 'Hi {$a->firstname}, -A new account has been requested at \'{$a->sitename}\' -using your email address. +A new account has been requested at \'{$a->sitename}\' using your email address. -To confirm your new account, please go to this web address: +To confirm your new account, please click the link below: -{$a->link} +Confirm your account -In most mail programs, this should appear as a blue link -which you can just click on. If that doesn\'t work, -then cut and paste the address into the address -line at the top of your web browser window. -If you need help, please contact the site administrator, +If you need help, please contact the site administrator. {$a->admin} If you did not do this, someone else could be trying to compromise your account. @@ -53,16 +48,12 @@ {$a->linkedemail} to your account at \'{$a->sitename}\' using your email address. -To confirm this request and link these logins, please go to this web address: +To confirm this request and link these logins, please click the link below: -{$a->link} +Link your accounts -In most mail programs, this should appear as a blue link -which you can just click on. If that doesn\'t work, -then cut and paste the address into the address -line at the top of your web browser window. -If you need help, please contact the site administrator, +If you need help, please contact the site administrator. {$a->admin} If you did not do this, someone else could be trying to compromise your account. diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 0c292a9edd87d..182665e699afa 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -5285,6 +5285,15 @@ protected function process_question($data) { $this->set_mapping('question_bank_entry', $this->latestqbe->oldid, $this->latestqbe->newid); } + if ( + ($data->qtype === 'random') + && ($this->latestversion->status == \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN) + ) { + // Ensure that this newly created question is considered by + // \qtype_random\task\remove_unused_questions. + $this->latestversion->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT; + } + // Now store the question. $newitemid = $DB->insert_record('question', $data); $this->set_mapping('question', $oldid, $newitemid); diff --git a/blocks/myoverview/templates/main.mustache b/blocks/myoverview/templates/main.mustache index 07a9889863408..3fa3a114c34d9 100644 --- a/blocks/myoverview/templates/main.mustache +++ b/blocks/myoverview/templates/main.mustache @@ -23,7 +23,7 @@ {} }} -