Compare commits

..

586 Commits

Author SHA1 Message Date
Huong Nguyen 2f497f8138 weekly release 5.1.3+ 2026-03-27 15:40:28 +07:00
Huong Nguyen 9e034f8312 NOBUG: Add upgrade notes 2026-03-27 15:40:28 +07:00
Huong Nguyen e6d4476bed Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-03-27 15:40:14 +07:00
Huong Nguyen 7bfe4e5875 Merge branch 'MDL-81241-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-03-26 10:51:13 +07:00
Huong Nguyen e3e4d1dddf Merge branch 'MDL-88279-501' of https://github.com/meirzamoodle/moodle into MOODLE_501_STABLE 2026-03-26 07:49:28 +07:00
AMOS bot 9e476db4d1 Automatically generated installer lang files 2026-03-26 00:07:53 +00:00
Huong Nguyen 0b0e171563 Merge branch 'MDL-77558-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE 2026-03-25 14:37:56 +07:00
Huong Nguyen 63aa2d72af Merge branch 'MDL-85451-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-03-25 14:29:57 +07:00
Huong Nguyen 5269c824b3 Merge branch 'MDL-87560-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-25 14:27:26 +07:00
David Woloszyn dca4195a7c MDL-77558 mod_forum: Mark posts as read in forum grader 2026-03-25 17:37:07 +11:00
Huong Nguyen 2447afb150 Merge branch 'MDL-87548-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2026-03-25 10:50:21 +07:00
Huong Nguyen 672760c211 Merge branch 'MDL-88197-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-25 09:22:47 +07:00
Huong Nguyen 443dc0e29c Merge branch 'MDL-88076-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-25 09:15:06 +07:00
Huong Nguyen 209c8f1c0e Merge branch 'MDL-87626-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE 2026-03-25 09:04:24 +07:00
Huong Nguyen 0305c55494 Merge branch 'MDL-87803-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-25 08:59:24 +07:00
Huong Nguyen e1a5ffb198 Merge branch 'MDL-87580-task-autolinking-MOODLE_501_STABLE' of https://github.com/brendanheywood/moodle into MOODLE_501_STABLE 2026-03-25 08:48:27 +07:00
Paul Holden 07f8fb90ba MDL-87560 assignsubmission_onlinetext: indicate truncated summary.
Only when the submission has been truncated should we show the
"View more" link, otherwise it appears to not do anything for already
short text.
2026-03-24 12:48:05 +00:00
meirzamoodle bb79d059ae MDL-88279 core_session: fix cluster test retry count assertion
Update session_redis_cluster_test to expect 3 connection attempts
instead of 5, matching the $maxretries default value in redis.php.
2026-03-24 14:40:54 +07:00
Huong Nguyen a2f060e24b Merge branch 'MDL-88070-501' of https://github.com/sh-csg/moodle into MOODLE_501_STABLE 2026-03-24 08:40:22 +07:00
Huong Nguyen 2b5e84f107 Merge branch 'MDL-87748-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-03-24 08:37:43 +07:00
Huong Nguyen 9145e64e24 Merge branch 'MDL-51152-501' of https://github.com/christianabila/moodle into MOODLE_501_STABLE 2026-03-24 08:32:26 +07:00
Huong Nguyen 31db1386ca Merge branch 'MDL-66415-501' of https://github.com/raortegar/moodle into MOODLE_501_STABLE 2026-03-23 13:46:07 +07:00
Huong Nguyen 688e04192d Merge branch 'MDL-87941-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-23 13:41:31 +07:00
Huong Nguyen 3237df4e25 Merge branch 'MDL-87438-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2026-03-23 13:37:02 +07:00
David Woloszyn c232b64d9b MDL-87626 core_search: Fix typo in input value check
Co-authored-by: Uli Wessmann
2026-03-23 13:41:23 +11:00
AMOS bot dd99f2eb86 Automatically generated installer lang files 2026-03-21 00:07:57 +00:00
Luca Bösch 492da54f6c MDL-87748 assign: format checkbox with Bootstrap classes. 2026-03-20 08:55:54 +01:00
Mihail Geshoski e7f245f9a5 Merge branch 'MDL-87715-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-03-20 15:46:35 +08:00
Mihail Geshoski 834a3727b4 Merge branch 'MDL-87661-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-20 15:42:20 +08:00
Mihail Geshoski 217bdb4c6f Merge branch 'MDL-88122-navbar_empty-breadcrumb-M5.1' of https://github.com/stopfstedt/moodle into MOODLE_501_STABLE 2026-03-20 15:35:03 +08:00
Mihail Geshoski 833600b648 Merge branch 'MDL-87320-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-03-20 15:25:23 +08:00
Mihail Geshoski b2ca897a48 Merge branch 'MDL-84780-501' of https://github.com/sh-csg/moodle into MOODLE_501_STABLE 2026-03-20 15:20:51 +08:00
Mihail Geshoski f18b6e98a9 Merge branch 'MDL-83459_501_create_submission_switchids_cmid' of https://github.com/sharpchi/moodle into MOODLE_501_STABLE 2026-03-20 15:15:56 +08:00
Shamim Rezaie 194574e656 weekly release 5.1.3+ 2026-03-20 15:10:51 +11:00
Shamim Rezaie 725360af8a NOBUG: Add upgrade notes 2026-03-20 15:10:51 +11:00
Shamim Rezaie 63531cebe9 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-03-20 15:10:35 +11:00
Stefan Hanauska 1d134892c8 MDL-84780 course: Disabled modules are not allowed 2026-03-19 20:48:22 +01:00
Luca Bösch fd39d575c5 MDL-87320 forum: Correctly escape group names. 2026-03-19 17:43:16 +01:00
Brendan Heywood e1bd6dc81d MDL-87580 task: Fix task output autolinking 2026-03-20 01:41:46 +11:00
Jun Pataleta bca2a8ad97 MDL-81241 theme_boost: Accessibility test for the footer region links 2026-03-19 20:29:23 +08:00
Jun Pataleta 7784e41de2 MDL-81241 theme_boost: Ensure footer links have sufficient spacing 2026-03-19 20:29:23 +08:00
Safat 4f77d7808e MDL-87959 tool_installaddon: Add extra behat for chooser footer 2026-03-19 12:13:47 +07:00
Mihail Geshoski db97df13b6 Merge branch 'MDL-87321-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-19 10:49:15 +08:00
Huong Nguyen f743ec6fd8 Merge branch 'MDL-88080-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-19 09:11:15 +07:00
Huong Nguyen 277d3168b8 Merge branch 'MDL-88054-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-19 08:57:57 +07:00
Mihail Geshoski f2f8137964 Merge branch 'MDL-87810-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-03-19 09:49:50 +08:00
Huong Nguyen a19da6770a Merge branch 'MDL-87935-501' of https://github.com/yusufwib01/moodle into MOODLE_501_STABLE 2026-03-19 08:23:34 +07:00
Huong Nguyen 518259180f Merge branch 'MDL-87592-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2026-03-19 08:18:55 +07:00
Mark Sharp 201111ffd7 MDL-83459 mod_assign: change switchids to use cmid 2026-03-18 15:40:51 +00:00
Ilya Tregubov c96c7e461f Merge branch 'MDL-75067-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-18 14:01:49 +01:00
Mihail Geshoski b3f12c9761 Merge branch 'MDL-87096-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-18 17:36:24 +08:00
Andi Permana 5f8baebeed MDL-87592 auth_email: preserve wantsurl through MFA email confirmation 2026-03-18 16:06:07 +07:00
Raquel Ortega 4c2b7dc647 MDL-66415 core_message: Improve bulk messaging result handling 2026-03-18 09:35:40 +01:00
Jun Pataleta a9ce091f0c MDL-85451 block_timeline: Add accessibility Behat test coverage 2026-03-18 13:24:31 +08:00
Jun Pataleta ec0b0aa279 MDL-85451 block_timeline: Group the due date filter options
To ensure that the filter options for the day filter menu matches
the menu pattern and pass accessibility checks, we need to:
* Group the due date filter options, labelled by the "Due date"
"heading".
* Set the "Due date" heading with a presentation role to prevent
assistive technologies from announcing it when the user goes through
the menu options.
2026-03-18 13:24:30 +08:00
Jun Pataleta a52c55e79d MDL-85451 block_timeline: Fix heading levels 2026-03-18 13:24:30 +08:00
Paul Holden 1f9050a2e6 Merge branch 'MDL-88226-501' of https://github.com/dravek/moodle into MOODLE_501_STABLE 2026-03-17 10:29:46 +00:00
David Carrillo 83c98546b1 MDL-88226 phpunit: Fix fragile tests using adhoc task manager
- Reset task manager state when resetting all data
- mod_quiz: Fix fragile adhoc task manager tests
- mod_assign: Fix fragile adhoc task manager tests
2026-03-16 16:36:14 +01:00
Paul Holden 3ec133e062 MDL-88076 tool_mfa: improved parsing of redirect exclusions config.
Ensure all permutations of EOL markers are covered/trimmed.
2026-03-16 11:36:39 +00:00
Huong Nguyen 502d0cae12 Merge branch 'MDL-86839-MOODLE_501_STABLE' of https://github.com/PhMemmel/moodle into MOODLE_501_STABLE 2026-03-16 16:33:11 +07:00
Shamim Rezaie 3300ba4676 Merge branch 'MDL-82625-501' of https://github.com/cameron1729/moodle into MOODLE_501_STABLE 2026-03-16 18:28:30 +11:00
Huong Nguyen 0c4f57af6e Merge branch 'MDL-87795-501' of https://github.com/albertgasset/moodle into MOODLE_501_STABLE 2026-03-16 09:45:56 +07:00
Huong Nguyen f2fb5fe428 Merge branch 'MDL-82439_501' of https://github.com/jonof/moodle into MOODLE_501_STABLE 2026-03-16 08:48:52 +07:00
Jake Dallimore ace24d32cd Merge branch 'MDL-83096-501' of https://github.com/jason-platts/moodle into MOODLE_501_STABLE 2026-03-16 09:42:56 +08:00
yusufwib01 634892618d MDL-87935 aiprovider_ollama: fix incorrect error response handling 2026-03-14 02:46:43 +07:00
Sara Arjona 59d23170cb Merge branch 'MDL-88211-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-13 15:25:08 +01:00
Paul Holden 7d66057a0b MDL-88211 backup: remove redundant usage of task manager from tests.
The asynchronouse backup/restore tests are only concerned with testing
the implementation of the accompanying task class directly. When they
try to do so at a distance via the task manager API they introduce
random failures by failing to account for different queued tasks that
come from other components (e.g. Workplace).
2026-03-13 12:40:01 +00:00
Daniel Poggenpohl 0859682802 MDL-83096 mod_lti: Register view completion when launching in new window 2026-03-13 09:05:27 +00:00
Mihail Geshoski 24a56ecc51 Merge branch 'MDL-87516-501' of https://github.com/NashTechOpenUniversity/moodle into MOODLE_501_STABLE 2026-03-13 15:35:38 +08:00
Huong Nguyen 850eeefc28 Merge branch 'MDL-87959-501' of https://github.com/safatshahin/moodle into MOODLE_501_STABLE 2026-03-13 14:05:47 +07:00
Philipp Memmel d6d77abe39 MDL-86839 grunt: Make grunt watch respect --force option 2026-03-13 07:34:02 +01:00
Sara Arjona 0f6459b758 Merge branch 'MDL-88109-501' of https://github.com/sarjona/moodle into MOODLE_501_STABLE 2026-03-13 06:45:40 +01:00
Safat 0960437fb4 MDL-87959 admin: Add marketplace availability in notifications 2026-03-13 15:52:57 +11:00
Safat cff57e5846 MDL-87959 tool_installaddon: Add support for marketplace 2026-03-13 15:52:57 +11:00
Mihail Geshoski 667dcf4ce2 Merge branch 'MDL-81608-501-2' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-03-13 09:53:55 +08:00
Huong Nguyen 02e244d1a9 Merge branch 'MDL-87100-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-13 08:38:13 +07:00
Huong Nguyen f8c9c0a475 Merge branch 'MDL-86843-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-03-13 08:29:03 +07:00
Jonathon Fowler 056a95d77d MDL-82439 iplookup: catch and handle GeoIp2 exceptions 2026-03-13 07:42:36 +10:00
Albert Gasset 46d1003ea3 MDL-87795 message_airnotifier: Handle 404 (unregistered token) responses 2026-03-12 11:51:26 +01:00
Paul Holden aecebb12df MDL-88197 customfield_number: observe required field configuration. 2026-03-12 09:19:24 +00:00
Luca Bösch 01ed3b0410 MDL-86843 tool_task: style tables correctly. 2026-03-12 09:54:20 +01:00
Andi Permana 3392f0a438 MDL-87548 repository_wikimedia: Handle HTTP 429 rate limiting 2026-03-12 15:29:32 +07:00
Huong Nguyen ff013f4395 Merge branch 'MDL-86616_501' of https://github.com/timhunt/moodle into MOODLE_501_STABLE 2026-03-12 14:43:44 +07:00
Mihail Geshoski f2ec040259 Merge branch 'MDL-74519-501' of https://github.com/AnupamaSarjoshi/moodle into MOODLE_501_STABLE 2026-03-12 14:08:33 +08:00
Mihail Geshoski 464b8b4670 Merge branch 'MDL-85637-501' of https://github.com/izendegi/moodle into MOODLE_501_STABLE 2026-03-12 14:03:32 +08:00
Safat e376beef97 Merge branch 'MDL-87708-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-03-12 15:13:04 +11:00
Huong Nguyen 9bd72fd9d5 MDL-87708 tool_moodlenet: Remove post install task 2026-03-12 10:44:50 +07:00
Huong Nguyen 63568873cc MDL-87708 admin: Improve MoodleNet removal warnings message 2026-03-12 10:16:21 +07:00
Andi Permana f3be834fa7 MDL-87708 tool_moodlenet: Clean up moodle.net configs in stable
Part of MDL-87351

- Discourage tool_moodlenet unless pointing to custom installation
- Clear activity chooser footer if set to MoodleNet
- Cleanup happens for: moodle.net URL, www.moodle.net, or empty config
- Preserves custom/local MoodleNet installations
2026-03-12 10:16:21 +07:00
hieuvu bcab539678 MDL-87516 core_question: update behaviour when update question attempt. 2026-03-12 09:14:34 +07:00
Stefan Topfstedt fec7949f37 MDL-88122 navigation: omit empty breadcrumb container in output. 2026-03-11 17:44:48 -07:00
Sara Arjona dac2009c4b MDL-88109 router: Use unencoded URL for location redirect 2026-03-11 16:46:39 +01:00
Hai Nguyen Van 74d9a08142 MDL-86616 filepicker: link restrictions to input via aria-describedby 2026-03-11 11:02:01 +00:00
Iñigo Zendegi b726820c4f MDL-85637 question: Optimize max question version SQL query 2026-03-11 11:41:03 +01:00
Anupama Sarjoshi aebe923947 MDL-74519 badges: Fix date validation in course completion criteria
Ensures that badge awarding correctly evaluates the configured by-date
condition in course and course set completion criteria.

Co-authored by: Diego Campos
2026-03-11 10:00:34 +05:30
Mihail Geshoski 2c2ea4565c Merge branch 'MDL-87955-501-2' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-03-11 11:55:11 +08:00
AMOS bot 366d1ee79b Automatically generated installer lang files 2026-03-11 00:09:35 +00:00
Jun Pataleta 441130b7d6 MDL-81608 quiz: Add Add menu spacing to prevent overlap 2026-03-10 17:57:56 +08:00
Jun Pataleta f06752f9c5 MDL-81608 quiz: Set more meaningful button labels
* Add/remove page break buttons
* Move question buttons
2026-03-10 17:57:56 +08:00
Jun Pataleta 468a24606d MDL-81608 core: Add alt text to the YUI spinner image
The accessibility Behat test added to the quiz editing page uncovered
that the loading spinner icon does not have an appropriate alt text.
2026-03-10 17:57:55 +08:00
Jun Pataleta cbd7b7da6d MDL-81608 quiz: Adjust size and spacing for the quiz edit page buttons
* In order to meet WCAG 2.2 criterion for minimum target size and make
it easier for users who have mobility limitations to use the quiz
editing page, we need to use the Bootstrap icon button styles and
increase the size and the spacing for the following buttons:
  * Add/remove page break button
  * Lock/unlock question dependency button
* This commit also makes the icons within the buttons as decorative and
moves the accessible name to the button elements themselves via the
aria-label attribute.
* Adjust Behat tests for checking the dependency buttons.
* Add accessibility Behat coverage for the quiz edit page.
2026-03-10 17:57:55 +08:00
Jun Pataleta 94a88b461a MDL-81608 core: Label YUI dragdrop handles appropriately
The YUI dragdrop handles rely on the button's title attribute for its
accessible label. We should label it appropriately using the
aria-label attribute.
2026-03-10 17:57:55 +08:00
Huong Nguyen 746f530b34 Merge branch 'MDL-86698-501' of https://github.com/mickhawkins/moodle into MOODLE_501_STABLE 2026-03-10 08:26:17 +07:00
Paul Holden b145ff806d MDL-87661 course: add required library to drag/drop upload handler. 2026-03-09 17:10:11 +00:00
Paul Holden 9785d83f50 MDL-88080 admin: initially focus the form element failing validation.
This appears to replicate what the ancient admin_externalpage_print_header
method did prior to being deprecated in db785816, however its replacement
didn't support the same $focus argument - hence doing so manually now.
2026-03-09 11:57:59 +00:00
Huong Nguyen 052044b828 Merge branch 'MDL-88093-failed-task-lang-MOODLE_501_STABLE' of https://github.com/brendanheywood/moodle into MOODLE_501_STABLE 2026-03-09 09:10:57 +07:00
Huong Nguyen 48ec158921 Merge branch 'MDL-87883-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-09 09:06:14 +07:00
Huong Nguyen 6594769211 Merge branch 'MDL-79324-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-03-09 09:03:16 +07:00
Paul Holden cffe464dd5 MDL-87803 blocks: set ARIA label to string content value.
Previously it was a string instance, which meant it wasn't encoded
correctly when used as part of the delete block confirmation.
2026-03-06 14:46:07 +00:00
Michael Hawkins a06a065c52 MDL-86698 core: Update security.txt expiry 2026-03-06 19:09:15 +08:00
Luca Bösch 4ffe3d32f0 MDL-87715 scorm: activity overview button class. 2026-03-06 11:11:11 +01:00
Luca Bösch 1348e4ec67 MDL-87715 quiz: activity overview button class. 2026-03-06 11:11:11 +01:00
Luca Bösch 5c620aa060 MDL-79324 quiz: Don't show "Back" button in secure layout. 2026-03-06 11:07:02 +01:00
Jake Dallimore 65b51ef436 weekly release 5.1.3+ 2026-03-06 12:10:09 +08:00
Jake Dallimore 6b95c7c714 NOBUG: Add upgrade notes 2026-03-06 12:10:09 +08:00
Jake Dallimore cf3eb729cb Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-03-06 12:10:03 +08:00
Brendan Heywood 1d468570c2 MDL-88093 task: Send task failure messages in correct language 2026-03-05 22:04:37 +11:00
Muhammad Arnaldo d64fa92eee MDL-87438 core: Fix multiselect saving with empty choices 2026-03-05 14:23:13 +07:00
Jake Dallimore 77f7c83df6 Merge branch 'MDL-86154_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-03-05 11:55:40 +08:00
Adrian Greeve d75f86fe17 Merge branch 'MDL-88009-501' of https://github.com/andrewnicols/moodle into MOODLE_501_STABLE 2026-03-05 10:51:02 +08:00
Huong Nguyen 7fd20ffc27 Merge branch 'MDL-87657-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-05 09:22:44 +07:00
Mihail Geshoski c1ab96af59 Merge branch 'MDL-86691_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-03-05 09:44:08 +08:00
Jun Pataleta dd682468f1 MDL-87810 quiz: Accessibility Behat coverage for quiz attempt 2026-03-05 08:47:32 +08:00
Jun Pataleta 7fedd96eda MDL-87810 question: Fix colour contrast issues on question view
* Lighten the background for the question formulation container.
* Lighten the background for the question feedback container while
  slightly darkening the text font as well.
2026-03-05 08:47:31 +08:00
Jun Pataleta dade115522 Merge branch 'MDL-87949-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-04 21:26:25 +08:00
Jun Pataleta ef3c60e021 MDL-87955 mod_forum: Manage subscribers accessibility improvements
* Replace the layout table with responsive Bootstrap columns.
* Add accessible labels for the existing and potential subscribers
  select boxes.
* Add accessibility Behat coverage.
* Remove unused CSS styles
2026-03-04 12:58:03 +08:00
Huong Nguyen ae7baf3c9f Merge branch 'MDL-85322-501' of https://github.com/durenadev/moodle into MOODLE_501_STABLE 2026-03-04 09:20:05 +07:00
Huong Nguyen 8baa9039ab Merge branch 'MDL-86112_501' of https://github.com/jonof/moodle into MOODLE_501_STABLE 2026-03-04 08:53:30 +07:00
Huong Nguyen 9c21b3ba17 Merge branch 'MDL-87905-m501' of https://github.com/sammarshallou/moodle into MOODLE_501_STABLE 2026-03-04 08:49:59 +07:00
AMOS bot a345c57b18 Automatically generated installer lang files 2026-03-04 00:07:54 +00:00
Daniel Ureña df9a67b8a5 MDL-85322 block_html: trust dashboard content in WS context 2026-03-03 11:55:06 +01:00
Huong Nguyen d4412078e2 Merge branch 'MDL-88021-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-03-03 17:28:50 +07:00
Sara Arjona 8923d6f2ef Merge branch 'MDL-87980-501' of https://github.com/laurentdavid/moodle into MOODLE_501_STABLE 2026-03-03 10:30:10 +01:00
Mihail Geshoski 0f28bf440a Merge branch 'MDL-87315_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-03-03 16:37:08 +08:00
Safat 0e66dce507 Merge branch 'MDL-88030-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-03-03 14:49:24 +11:00
Jonathon Fowler 1beba61e80 MDL-86112 courseformat: avoid a JS error modal when marking as done
When viewing an "available but not shown" cm with self-completion set,
then clicking the 'Mark as done' button in the activity header.
2026-03-03 11:40:39 +10:00
Huong Nguyen c1de9b3835 Merge branch 'MDL-87993-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-03-03 08:36:07 +07:00
Huong Nguyen 2a5c67dca4 Merge branch 'MDL-87945-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-03-03 08:33:26 +07:00
Huong Nguyen be95a3b985 Merge branch 'MDL-60912-501' of https://github.com/AnupamaSarjoshi/moodle into MOODLE_501_STABLE 2026-03-03 08:30:45 +07:00
Paul Holden f49d624992 MDL-75067 behat: always interact with filepicker inside active dialogue.
This allows for tests to interact with multiple elements of this type
during scenarios, whereas previously Behat would always try to interact
with the first (even when referring to latter file managers).
2026-03-02 11:03:38 +00:00
Mark Johnson 329725c21e MDL-86154 questions: Delete unused questions after restore
After restoring an activity using shared questions without the question
bank they belong to, we may have left over question categories in the
target course context. These were being deleted, but any questions they
contained weren't.

This replaces the bulk delete of question category records with a call
to `question_category_delete_safe()` for each category, which will also
delete the questions they contain.
2026-02-27 14:31:40 +00:00
Mark Johnson 9547082cdd MDL-87315 backup: Update set reference question category ID on restore.
When a question bank is restored along with a quiz that uses a random
question from that bank,
the set reference for the random question should now point to the
category in the restored bank,
rather than original.

This uses the question bank context IDs recorded using the method added
in MDL-86136
to decide if the set reference's question context is being restored, and
update it
accordingly.

This required adding a new restore step in the restore_root_task, since
when a restore is
performed asynchronously, the temp IDs table is deleted after the
prechecks are performed
when the task is queued. Adding this step allows the IDs to be recorded
again when the restore
runs.
2026-02-27 13:45:50 +00:00
Laurent David fd40e07271 MDL-87980 mod_assign: Fix empty description display 2026-02-27 11:46:22 +01:00
Paul Holden a722fe5493 MDL-88054 tool_mfa: ensure factor preference icons are circular.
They were once, but then weren't. Probably same cause as that fixed
in 0ff8f7b2 for all emoticons.
2026-02-27 09:57:33 +00:00
Ilya Tregubov d949fd6801 Merge branch 'MDL-87967-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-27 09:05:01 +01:00
Ilya Tregubov 0bc9ae17a2 Merge branch 'MDL-29421-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-27 08:52:29 +01:00
Huong Nguyen e5429f2831 weekly release 5.1.3+ 2026-02-27 09:38:36 +07:00
Huong Nguyen c688be33a3 NOBUG: Add upgrade notes 2026-02-27 09:38:36 +07:00
Huong Nguyen 82296e9420 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-02-27 09:38:24 +07:00
AMOS bot 1783fa7882 Automatically generated installer lang files 2026-02-27 00:07:45 +00:00
Mark Johnson ae002508f5 MDL-86691 questions: Fix incorrect set reference category contexts
During the upgrade to 5.x, question categories are moved to new
mod_qbank module contexts, and any set references using those categories
are updated using `move_question_set_references()`. However, this
function wasn't updating the `cat` arribute of the set reference's
filter condition, leaving it pointing at the original context ID.

This change update `move_question_set_references()` so that when a set
reference is moved (during the 5.x upgrade, or otherwise) the `cat`
parameter will be updated correctly.

For sites that have already gone through the 5.x upgrade, this fix also
adds a CLI script to find and fix incorrect question_set_reference
records. I considered adding this as an upgrade step, but it potentially
has a lot of records to check, and it's redundant if the site is being
upgraded to 5.x with this fix already in place.
2026-02-26 15:48:39 +00:00
sam marshall 2329be1d56 MDL-87905 Behat: pause incorrectly thinks terminal not interactive
When run from behat/cli/run.php, the pause command may fail because
it thinks the terminal is not interactive, even if the input and
output is passed through from an interactive terminal run.
2026-02-26 09:34:04 +00:00
Anupama Sarjoshi 6b8eb56fd8 MDL-60912 core_completion: Fix course completion percentage calculation
This change ensures that course completion percentage calculations
include only completion activities visible to the user on the course
home page. Hidden or unavailable activities, and those in hidden
sections, are no longer included in the calculation.
2026-02-26 12:34:46 +05:30
Huong Nguyen f82060b773 Merge branch 'MDL-86866-501' of https://github.com/andrewnicols/moodle into MOODLE_501_STABLE 2026-02-26 09:16:17 +07:00
Huong Nguyen 05441e21c7 Merge branch 'MDL-87588-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2026-02-26 09:09:23 +07:00
Huong Nguyen 1c8e047af5 Merge branch 'MDL-88020-501' of https://github.com/rxdelacruz/moodle into MOODLE_501_STABLE 2026-02-26 09:03:37 +07:00
Huong Nguyen 453f78c48a Merge branch 'MDL-88036-501' of https://github.com/mioyu-ubiq/moodle into MOODLE_501_STABLE 2026-02-26 09:01:29 +07:00
Mihail Geshoski a2b0c71769 Merge branch 'MDL-85902-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-02-26 09:57:11 +08:00
Mihail Geshoski 0302dd16a2 Merge branch 'MDL-86524_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-02-26 09:44:15 +08:00
AMOS bot dd643df554 Automatically generated installer lang files 2026-02-26 00:07:44 +00:00
Paul Holden f533f30d3b MDL-29421 forms: return formatted frozen content for editor element.
This was partly addressed in 82d491ca, however formatted content and
embedded images were rendered incorrectly.
2026-02-25 17:08:47 +00:00
Paul Holden df10e4006a MDL-87949 gradingform_rubric: accessible "Click to edit" colour.
Darken it to meet the WCAG AA standards for accessible text. This
colour is also consistent with the accompanying buttons.
2026-02-25 16:25:42 +00:00
Ilya Tregubov 32d6f97428 Merge branch 'MDL-78112-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-25 12:06:46 +01:00
Stefan Hanauska c7c1280a74 MDL-88070 javascript: Respect removeOnClose 2026-02-25 12:02:54 +01:00
Andrew Nicols ed8e9db1d3 MDL-86866 core: Do not log route errors
Doing so leads to excessive noise in logs and unit tests.

At the moment we have no way to control these logs. This will change
when we bring in a PSR-4 logger and can use that instead, but at the
moment we do not have any such logger in core.
2026-02-25 18:31:40 +08:00
Andrew Nicols 6123fa55e2 MDL-86866 router: Use response_aware handler for status code 2026-02-25 18:31:29 +08:00
Mark Johnson 5f5f44f2ca MDL-86524 quiz: Update restore test with current tag filter format
The test for restoring a random question from Moodle 3.9 using tags was
still testing for the pre-4.3 filtercondition format. This needed
updating to look for the current format.
2026-02-25 09:01:15 +00:00
Mark Johnson 87be7803f8 MDL-86524 qbank_tagquestion: Map qtagids filter to restored tag IDs
When restoring a set reference containing a qtagids filter, check that
the tag ID still exists. If not, or we're restoring to a different site,
replace the ID in the filter with the mapped ID from the restore.

If a tag ID is not found, it is removed from the filter. If no matching
tags are found, the entire filter is removed.
2026-02-25 09:01:15 +00:00
Mark Johnson cc5c32ba1b MDL-86524 qbank_managecateories: Fix questionscontextid on restore
If a backup contains a set reference where the questionscontextid does
not match the context of the question category (as the category was
moved, but the reference was not updated correctly), then we might end
up restoring a quiz with a broken question.

This change ensures that we update the context to match the question
category during the restore.
2026-02-25 09:01:14 +00:00
rxdelacruz 7bfee7ef70 MDL-88020 enrol_self: Add Behat test for cohort restriction 2026-02-25 14:53:43 +08:00
mioyu-ubiq f4da6d1a68 MDL-88036 mod_scorm: Fix duplicate scenario name 2026-02-25 14:32:41 +08:00
Huong Nguyen 53acc89d7a Merge branch 'MDL-87190-501' of https://github.com/timhunt/moodle into MOODLE_501_STABLE 2026-02-25 10:57:57 +07:00
Mihail Geshoski 0e3165f0b3 Merge branch 'mdl-84045_501' of https://github.com/james-cnz/moodle into MOODLE_501_STABLE 2026-02-25 10:50:00 +08:00
Hai Nguyen Van 5adb6a957e MDL-87190 filepicker: clarify error when dragging a folder into picker 2026-02-24 20:43:02 +00:00
Mihail Geshoski 60658f3b4a Merge branch 'MDL-88011-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-02-24 23:56:19 +08:00
Andrew Nicols 9acf96dcab MDL-86866 core: Web 404 responses should generate HTML not JSON 2026-02-24 22:14:20 +08:00
Jun Pataleta 3ae6a15959 MDL-85902 course: Better labeling of activity navigation components
* The arrow icons produced by the `$OUTPUT->larrow()` and
`$OUTPUT->rarrow()` functions are announced by screen readers, and they
do not make sense when heard by screen reader users. To fix this,
we need to override the accessible names of the previous and next
activity links using the `aria-label` attribute and specify whether the
activity links point to the previous or next activities.
* The label for the jump menu "Jump to" is not informative enough to
convey the element's purpose. Let's improve it's accessible label to
"Jump to activity".
2026-02-24 21:30:22 +08:00
Jun Pataleta 7e9b1fe543 MDL-85902 course: Convert activity navigation to a navigation landmark 2026-02-24 20:59:21 +08:00
Mihail Geshoski d3675d3e88 Merge branch 'MDL-87492_MOODLE_501_STABLE' of https://github.com/cwarwicker/moodle into MOODLE_501_STABLE 2026-02-24 14:32:44 +08:00
Mihail Geshoski 73b0dc1f92 Merge branch 'MDL-87452-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-02-24 14:17:12 +08:00
Huong Nguyen 83230435e7 Merge branch 'MDL-86809-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE 2026-02-24 11:04:16 +07:00
Huong Nguyen e5c4305bdb Merge branch 'MDL-87442-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2026-02-24 10:44:42 +07:00
Huong Nguyen a8b365a33b Merge branch 'MDL-87456-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-02-24 09:49:56 +07:00
Huong Nguyen 4c2efc6857 Merge branch 'MDL-87807-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-24 09:47:33 +07:00
AMOS bot 305348b934 Automatically generated installer lang files 2026-02-24 00:07:47 +00:00
Mark Johnson 0bbd13319b MDL-86524 questions: Update set reference context after category move
When a category is restored, it is first created in the course context,
them moved it its module context once that has been restored.

When this happens, we also need to update set references that refer to
that category to use the new context. However, this was using the legacy
category fields so was not updating them correctly. It now does.
2026-02-23 11:23:59 +00:00
Mark Johnson b1a42309af MDL-86524 questions: Delegate filter condition restore to plugins
The core backup code was taking sole responsibility for conversion and
mapping of IDs in set reference filter conditions during restores.
However, it was only actually processing category conditions, not tags
or any of the more recently added conditions.

This adds a new `restore_filtercondition` method to the
`core_question\local\bank\condition` class which can be overridden by
each condition to convert IDs and add mappings during the restore
process.

It also adds a new `maptags` option to
`convert_legacy_set_reference_filter_condition`, so that if we are
doing the conversion during a restore, we don't also create or map
the tags as they are converted. We record ID mappings of the tags
as the are restored, then these can be used by the
`qbank_tagquestions` plugins later to map the converted condition.
2026-02-23 11:23:58 +00:00
Huong Nguyen 6bfb789b98 MDL-88030 library: Bump Behat and PHPUnit
- Bump Behat to 3.29.0
 - Bump PHPUnit to 11.5.55

Generated with PHP 8.2, following the instructions at:
https://moodledev.io/general/development/tools/composer
2026-02-23 16:20:33 +07:00
Huong Nguyen 5f135b2df3 MDL-88011 core: Fix wrong version bumpings 2026-02-23 15:29:02 +07:00
Luca Bösch c0a02a2467 MDL-87456 workshop: Amend action links to switch phase. 2026-02-20 20:51:11 +01:00
Luca Bösch 5136621016 MDL-87452 lesson: Align the checkboxes and labels with BS5. 2026-02-20 19:50:43 +01:00
Andi Permana 6148cd38c0 MDL-87442 theme_boost: Fix "With selected" select in mod_choice 2026-02-20 16:32:46 +07:00
Jun Pataleta 1378548e0a MDL-87993 mod_forum: Fix invalid use of SRLogger
* The SRLogger module is meant for reactive instances. Let's use a
visually hidden toast instead.
2026-02-20 16:13:02 +08:00
Jun Pataleta 5b88e5ab72 MDL-87993 core: Use visually hidden core/toast for the srlogger 2026-02-20 16:13:01 +08:00
Jun Pataleta 1a2afd7fe4 MDL-87993 core: Allow toasts to be visually hidden 2026-02-20 16:13:00 +08:00
Jun Pataleta fc59f17616 MDL-88021 core: Set jsdoc destination within the public folder 2026-02-20 15:43:26 +08:00
Juan Segarra Montesinos 330d80faa7 MDL-86809 completion: Disable receiveagradegroup when marked as done
Co-authored-by: Yusuf Wibisono <yusuf.wibisono@moodle.com>
2026-02-20 15:12:46 +11:00
Mihail Geshoski 09b2103fde weekly release 5.1.3+ 2026-02-20 09:17:14 +08:00
Mihail Geshoski da1db5d6d1 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-02-20 09:17:06 +08:00
AMOS bot eff0a78549 Automatically generated installer lang files 2026-02-20 00:07:47 +00:00
james-cnz 4b2323803a MDL-84045 core_courseformat: Restore section return functionality 2026-02-19 22:37:09 +13:00
Andrew Nicols 2890b9ee94 MDL-88009 theme_boost: Disable input sanitisation for footer
The footer popover contains content that we have curated from a range of
places and which does not include user-provided content. It should be
safe to use the content unsanitised.
2026-02-19 15:37:20 +08:00
Huong Nguyen e13983d257 Merge branch 'MDL-87441-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-02-19 08:54:26 +07:00
Huong Nguyen 142e29146e Merge branch 'mdl-87187-51' of https://github.com/matthewhilton/moodle into MOODLE_501_STABLE 2026-02-19 08:50:47 +07:00
Huong Nguyen b5bc0e315f Merge branch 'MDL-85301-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE 2026-02-19 08:47:33 +07:00
AMOS bot 3f16028620 Automatically generated installer lang files 2026-02-19 00:07:45 +00:00
Huong Nguyen f5e04493c3 Merge branch 'MDL-87482-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-02-18 15:40:02 +07:00
Mihail Geshoski e27ed6dc7a Merge branch 'MDL-85111-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2026-02-18 10:07:34 +08:00
AMOS bot 6e53ba123c Automatically generated installer lang files 2026-02-18 00:07:49 +00:00
Sara Arjona 2acc3bf43c Merge branch 'MDL-87919-501' of https://github.com/aanabit/moodle into MOODLE_501_STABLE 2026-02-17 16:54:54 +01:00
Amaia Anabitarte fa08851ccb MDL-87919 theme: Fix restricted selected subsections in course index 2026-02-17 15:26:37 +01:00
Luca Bösch 5a282f931f MDL-87441 user: Align the "With selected users..." label with BS5. 2026-02-17 08:21:43 +01:00
Shamim Rezaie f656a642a2 weekly release 5.1.3+ 2026-02-17 12:55:38 +11:00
Shamim Rezaie b4e8f59497 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-02-17 12:55:28 +11:00
Matthew Hilton 17ddfe2ecb MDL-87187 bigbluebuttonbn: Fix record time during backup/restore 2026-02-17 09:09:51 +10:00
Paul Holden 32a5269449 MDL-87967 output: ensure disabled toggle elements show tooltip. 2026-02-16 14:35:48 +00:00
Muhammad Arnaldo 67affb8216 MDL-87588 mod_bigbluebuttonbn: Fix missing recordings_deleted column 2026-02-16 17:46:51 +07:00
Paul Holden f978082766 MDL-78112 forms: support frozen filemanager form elements.
Replace editable content with a list of files stored in the file area.
2026-02-16 10:20:54 +00:00
Trisha Milan 011bffc2f5 MDL-82625 navigation: Add disableactive flag to combobox widget
- Introduced disableactive flag to prevent setting active state on combobox items when a page redirect occurs.
This resolves the issue where incorrect checkmarks remain after using the browser's back button due to bfcache.
- Added Behat test cases to verify correct checkmark behaviour in tertiary navigation when navigating and using
the browser's back/forward buttons.
2026-02-16 15:51:19 +08:00
Mihail Geshoski da5db34c27 Merge branch 'MDL-85658-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-02-16 13:46:58 +08:00
Mihail Geshoski 6f56529070 Merge branch 'MDL-87338-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-02-16 13:38:17 +08:00
Mihail Geshoski 95e211146a Merge branch 'MDL-87552-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-02-16 13:29:22 +08:00
Andre Krueger 1f60fd4673 MDL-85301 aiprovider_ollama: Fix basic authentication 2026-02-16 08:07:08 +11:00
AMOS bot b650870377 Automatically generated installer lang files 2026-02-14 00:07:44 +00:00
Jun Pataleta 34e1fc9dc6 Merge branch 'MDL-87450-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-02-13 20:34:56 +08:00
Huong Nguyen a167f6dbca Merge branch 'MDL-87597-501' of https://github.com/yusufwib01/moodle into MOODLE_501_STABLE 2026-02-13 15:08:43 +07:00
Mihail Geshoski b299f81736 Merge branch 'MDL-87600-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-02-13 14:17:33 +08:00
Jun Pataleta 0bdb88ae9e Merge branch 'MDL-76437-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-13 13:49:45 +08:00
Huong Nguyen 8c0aa1afc9 Merge branch 'MDL-86985-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE 2026-02-13 11:10:29 +07:00
Jun Pataleta 713a9da8b0 Merge branch 'MDL-87946-501' of https://github.com/andrewnicols/moodle into MOODLE_501_STABLE 2026-02-13 11:59:47 +08:00
Luca Bösch dd83c1c09a MDL-86985 mod_data: Stripe and hover effect restored to presets table 2026-02-13 14:52:43 +11:00
Luca Bösch f7e8903a7f MDL-87450 forum: Remove Bootstrap 5 table borders. 2026-02-13 11:05:20 +08:00
Huong Nguyen 0728bb6609 Merge branch 'MDL-87458-501' of https://github.com/jfederico/moodle into MOODLE_501_STABLE 2026-02-13 09:53:15 +07:00
Huong Nguyen 872fc36cc4 Merge branch 'MDL-87075-501' of https://github.com/raortegar/moodle into MOODLE_501_STABLE 2026-02-13 09:50:11 +07:00
Huong Nguyen fe1f5425af Merge branch 'MDL-81441-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-13 09:45:53 +07:00
Huong Nguyen 67d24e7032 Merge branch 'MDL-77209-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-02-13 09:42:22 +07:00
Andrew Nicols caae1d5720 MDL-87946 github: Update GHA Database connections
Both the pgsql and mysql steps we were using use `docker:stable`, which
has been abandoned and is not a recommended approach. They are also both
abandoned.

This commit moves to using a service entry for postgres; and a manual
step for mysql.

We can't use the services entry for MySQL because GHA does not support
providing arguments to service containers.
2026-02-13 10:32:32 +08:00
Huong Nguyen 36493b1ac1 MDL-87482 core: Better text comparison 2026-02-13 09:32:00 +07:00
Andi Permana c32438bc79 MDL-87482 core_message: Fix compatibility with libxml2 >= 2.14.0 2026-02-13 09:32:00 +07:00
Muhammad Arnaldo f1e95a5749 MDL-85111 qtype_essay: improve word counting logic
Improve handling of < (less-than) symbol when it's attached to a non-HTML word.

Co-authored-by: Tim Hunt <T.J.Hunt@open.ac.uk>
2026-02-13 09:13:35 +07:00
Huong Nguyen 99a219de52 MDL-85658 core_h5p: Implement contentLoaded event handler
This improvement will help to avoid random errors
because H5P content needs time to be rendered.
2026-02-13 08:54:39 +07:00
Huong Nguyen 3a713edefe MDL-85658 behat: Wait until the iframe is completely loaded 2026-02-13 08:54:39 +07:00
Paul Holden f0fde99c46 MDL-87941 core: handle param validation of boolean type false. 2026-02-12 17:34:25 +00:00
Paul Holden 06df92cfa2 MDL-87657 cohort: fix permission check for viewing system cohorts.
The problem manifested itself in custom reports using the corresponding
cohort field filter, which didn't reload current form values due to
incorrect context tree comparison.
2026-02-12 17:01:22 +00:00
Paul Holden 7808e616a5 MDL-87945 webservice: handle invalid functions when adding to service.
Same thing as we did for API docs in 6e9e73aba0.
2026-02-12 16:05:16 +00:00
Christian Abila e89fb4e371 MDL-87075 core: Filter roles to include visibility permissions 2026-02-12 14:52:00 +01:00
Huong Nguyen a58f0efab6 Merge branch 'MDL-87095-501' of https://github.com/lameze/moodle into MOODLE_501_STABLE 2026-02-12 10:24:27 +07:00
Huong Nguyen e1c848c189 Merge branch 'MDL-83325_501' of https://github.com/timhunt/moodle into MOODLE_501_STABLE 2026-02-12 10:20:57 +07:00
Huong Nguyen 2ea2956e8f Merge branch 'MDL-87113-501' of https://github.com/timhunt/moodle into MOODLE_501_STABLE 2026-02-12 08:59:46 +07:00
Huong Nguyen 437d6a0a02 Merge branch 'MDL-87358-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-12 08:56:50 +07:00
Huong Nguyen 26c7e57040 Merge branch 'MDL-78343-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE 2026-02-12 08:46:39 +07:00
Simey Lameze 8b1c4127e3 MDL-87095 behat: reorganize lesson question importing tests 2026-02-12 09:22:34 +08:00
Kim Jared Lucas 69fce2b7e4 MDL-87095 mod_lesson: add behat test for importing questions into lesson 2026-02-12 09:22:34 +08:00
AMOS bot 89eea57353 Automatically generated installer lang files 2026-02-12 00:09:42 +00:00
jfedericobn d0a69c7642 MDL-87458 bigbluebuttonbn: reset removes local recording references only 2026-02-11 16:37:58 +00:00
yusufwib01 e49fc4d750 MDL-87597 core_message: Fix double-click to reopen message drawer 2026-02-11 23:29:05 +07:00
Huong Nguyen 7830b1787a Merge branch 'MDL-86659-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-11 17:01:38 +07:00
Hai Nguyen Van 235bdcab3b MDL-87113 filepicker: fix progress stuck after invalid upload 2026-02-11 09:56:13 +00:00
Michael Kotlyar 8629444b93 MDL-87492 mod_quiz: Add grade_submission task
This task simply calls $attempt->process_grade_submission for a quiz
attempt. The need for this task has arisen due to some instances of
moodle sites needing to process many and/or complex quiz attempts. This
change only utilises this task for the mod_quiz upgrade script.
2026-02-11 09:38:15 +00:00
Sara Arjona a7deebb231 Merge branch 'MDL-87874-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-11 07:06:33 +01:00
Sara Arjona 0cfcfff4b8 Merge branch 'MDL-87825-501' of https://github.com/sebastsg/moodle into MOODLE_501_STABLE 2026-02-11 07:00:53 +01:00
Huong Nguyen 29e2d74c96 Merge branch 'MDL-87448-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-02-11 12:54:00 +07:00
Huong Nguyen 247b2d95b5 Merge branch 'MDL-87535-501_take2' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-02-11 11:47:54 +07:00
Huong Nguyen 0d25477253 Merge branch 'MDL-86714-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-11 11:45:43 +07:00
Huong Nguyen 1e0c48661b Merge branch 'MDL-68062-501' of https://github.com/sh-csg/moodle into MOODLE_501_STABLE 2026-02-11 11:24:40 +07:00
Huong Nguyen 4850fd3039 Moodle release 5.1.3 2026-02-11 08:44:36 +07:00
AMOS bot bfe4f1f987 Automatically generated installer lang files 2026-02-11 00:07:50 +00:00
Huong Nguyen e409f30dcb weekly release 5.1.2+ 2026-02-10 08:41:24 +07:00
Huong Nguyen 1f2f2e7f71 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-02-10 08:41:15 +07:00
Shamim Rezaie ed3851ff6b Merge branch 'MDL-87892-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-02-09 21:07:41 +11:00
Huong Nguyen b1bff07931 MDL-87892 admin: Fix MoodleNet check 2026-02-09 16:32:38 +07:00
Shamim Rezaie a8f3b08f67 Merge branch 'MDL-87892-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-02-09 19:37:00 +11:00
Huong Nguyen 44e0e1a780 MDL-87892 admin: Fix MoodleNet removal warning
Instead of introducing a new optional parameter,
we will make all the changes in the Admin Renderer
2026-02-09 09:04:17 +07:00
AMOS bot c2d762866b Automatically generated installer lang files 2026-02-09 00:07:48 +00:00
AMOS bot 25cac9b43b Automatically generated installer lang files 2026-02-07 00:07:50 +00:00
Paul Holden 0686806ef3 MDL-87874 customfield: expand size of decimal data field type.
This change now makes the decimal field size equivalent to that of
the corresponding integer field size.
2026-02-06 15:57:12 +00:00
Paul Holden cadb42dc64 MDL-87874 customfield_number: validation for maximum size constraints. 2026-02-06 15:53:14 +00:00
Michael Kotlyar 34e60be48e MDL-87492 mod_quiz: 'autosubmit' quiz attempt handling fix
Some quiz attempts throw an exception when they are being processed and
are stuck in a 'submitted' state. This change wraps the process in a
transaction so that there are no partial changes made in such cases.
2026-02-06 14:16:01 +00:00
Paul Holden 4e03ffe31d MDL-87883 core: correct license page link in project readme. 2026-02-06 09:09:02 +00:00
Jun Pataleta 22287e4b1a MDL-77209 core: Make More help links open on a new tab 2026-02-06 10:55:31 +08:00
David Woloszyn 2d9bddeebf MDL-78343 core_table: Improved HTML tag detection in format_text 2026-02-06 13:37:47 +11:00
Huong Nguyen ac802ad804 Moodle release 5.1.2 2026-02-06 09:16:30 +07:00
Huong Nguyen a379081818 NOBUG: Add upgrade notes 2026-02-06 09:16:30 +07:00
AMOS bot 550afb2a72 Automatically generated installer lang files 2026-02-06 00:07:49 +00:00
Luca Bösch 13ee4216e3 MDL-87448 qbank: put margins to the end of the buttons to align better. 2026-02-05 17:40:12 +01:00
Luca Bösch 5f42fc85eb MDL-87535 quiz: Quiz dates shouldn't show on secure page. 2026-02-05 17:34:31 +01:00
Luca Bösch aa9093c3ad MDL-87552 groupings: Striped hovering lines table with BS 5. 2026-02-05 17:25:15 +01:00
Luca Bösch 3988c4c620 MDL-87338 core_table: avoid string concatenation. 2026-02-05 17:21:13 +01:00
Mihail Geshoski e99b368c60 weekly release 5.1.1+ 2026-02-05 20:23:16 +08:00
Mihail Geshoski 70d026f0f7 NOBUG: Add upgrade notes 2026-02-05 20:23:16 +08:00
Mihail Geshoski 833f55cf11 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-02-05 20:23:05 +08:00
Paul Holden f36fd442e8 MDL-87870 admin: tighten up validation of colour picker values. 2026-02-05 17:25:37 +07:00
Paul Holden 60564c02ab MDL-87870 filter_tex: escape background argument content. 2026-02-05 17:25:37 +07:00
Mihail Geshoski b39f3bb7f9 Merge branch 'MDL-86444_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-02-05 17:25:35 +07:00
Huong Nguyen 31a3e0eaa9 Merge branch 'MDL-87352-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2026-02-05 17:24:53 +07:00
Mihail Geshoski aac49cd591 Merge branch 'MDL-87258-501' of https://github.com/rjnl/moodle into MOODLE_501_STABLE 2026-02-05 17:24:53 +07:00
Jun Pataleta 557432b764 Merge branch 'MDL-87757-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2026-02-05 17:24:53 +07:00
yusufwib01 8683b4a049 MDL-86785 tiny_equation: Add timeout handling for mimetex execution 2026-02-05 11:45:53 +08:00
Adrian Greeve 49b29449af Merge branch 'MDL-87559-501' of https://github.com/abias/moodle into MOODLE_501_STABLE 2026-02-05 11:45:53 +08:00
Mihail Geshoski a80b692453 Merge branch 'MDL-86437-501' of https://github.com/snake/moodle into MOODLE_501_STABLE 2026-02-05 11:45:53 +08:00
Huong Nguyen 5100441bba MDL-87843 filter_tex: Switch to use Colour picker setting
This will allow users to easily choose the background colour
and prevent invalid input values
2026-02-05 11:45:53 +08:00
Paul Holden ff5c46a912 MDL-87612 repository: safer unserializing of file source data. 2026-02-05 11:45:53 +08:00
Mihail Geshoski 860feade62 Merge branch 'MDL-87789-501' of https://github.com/snake/moodle into MOODLE_501_STABLE 2026-02-05 11:45:53 +08:00
AMOS bot e193924db6 Automatically generated installer lang files 2026-02-05 00:07:50 +00:00
Jun Pataleta 3702195fb2 MDL-87600 gradereport_overview: Fix heading levels on the index page 2026-02-04 18:48:24 +08:00
Muhammad Arnaldo 364a762fa7 MDL-87757 block_myoverview: Remove navigation role and behat update
- Removal of the navigation role on main and zero-state template
- Add accessibility extra tests for the block_overview
2026-02-04 10:11:41 +07:00
Muhammad Arnaldo fa810327f7 MDL-87757 block_myoverview: fix the heading-order 2026-02-04 10:11:41 +07:00
Andi Permana b25ed2333f MDL-87352 admin: Add MoodleNet removal warnings 2026-02-04 10:07:30 +07:00
AMOS bot 6803fd0988 Automatically generated installer lang files 2026-02-04 00:07:44 +00:00
Sebastian Gundersen 373ca27d34 MDL-87825 customfield_number: Handle shared fields in cron 2026-02-03 15:36:22 +01:00
Alexander Bias 03341aea9f MDL-87559 core: Set $SCRIPT even outside public/ 2026-02-03 10:39:40 +01:00
Jun Pataleta a7c5fad12d Merge branch 'MDL-83984_501' of https://github.com/santoshndev/moodle into MOODLE_501_STABLE 2026-02-03 17:06:49 +08:00
Jake Dallimore 3132cc9d4f Merge branch 'MDL-85535-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-02-03 16:59:16 +08:00
Andrew Nicols 2eb32285d7 Merge branch 'MDL-87199-501' of https://github.com/abias/moodle into MOODLE_501_STABLE 2026-02-03 16:51:07 +08:00
Jun Pataleta 274d5ccc62 Merge branch 'MDL-87185-501' of https://github.com/durenadev/moodle into MOODLE_501_STABLE 2026-02-03 16:50:14 +08:00
Jun Pataleta a105edc53f Merge branch 'MDL-87593-501-enfix' of https://github.com/mudrd8mz/moodle into MOODLE_501_STABLE 2026-02-03 16:47:47 +08:00
Jun Pataleta 582f2e673a Merge branch 'MDL-77162-filepicker-a11y-tab-order-repository-keyboard-selection-M5.1' of https://github.com/stopfstedt/moodle into MOODLE_501_STABLE 2026-02-03 16:44:14 +08:00
Jun Pataleta e9e6e0921d Merge branch 'MDL-87694-course-participant-profile-missing-heading-M5.1' of https://github.com/stopfstedt/moodle into MOODLE_501_STABLE 2026-02-03 15:18:21 +08:00
Rajneel Totaram 0a580ed98d MDL-87258 mod_lti: request access for PNA
Since Chrome 142, public servers accessing resources on local networks
gets blocked by default. Users need to allow access for such requests
to proceed.

This patch introduces a new config setting `ltiallowlocalnetwork`.
This determines if the local-network-access flag should be added to LTI
iframes so that Chrome users get prompted when a local network access
is being made.
2026-02-03 18:49:16 +12:00
Mihail Geshoski bb13942ace Merge branch 'MDL-87606_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-02-03 14:21:57 +08:00
Mihail Geshoski 3c681534da Merge branch 'MDL-86609_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-02-03 14:10:25 +08:00
Mihail Geshoski 036fc9ef9a Merge branch 'MOODLE_501_STABLE_MDL-84966' of https://github.com/niko-hoogeveen/moodle into MOODLE_501_STABLE 2026-02-03 13:44:14 +08:00
Mihail Geshoski 03a901b1fb Merge branch 'MDL-85653_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-02-03 13:36:13 +08:00
Mihail Geshoski 6390af2f48 Merge branch 'MDL-87029-501' of https://github.com/cameron1729/moodle into MOODLE_501_STABLE 2026-02-03 13:26:28 +08:00
Huong Nguyen 078308b04a Merge branch 'MDL-87334-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-02-03 11:07:41 +07:00
Huong Nguyen c708bb044e Merge branch 'MDL-87289-501' of https://github.com/raortegar/moodle into MOODLE_501_STABLE 2026-02-03 10:43:48 +07:00
Huong Nguyen e001ebc339 Merge branch 'MDL-87396-501' of https://github.com/izendegi/moodle into MOODLE_501_STABLE 2026-02-03 10:37:06 +07:00
Huong Nguyen f9a69ed13c Merge branch 'MDL-87520-501-branch-number' of https://github.com/mudrd8mz/moodle into MOODLE_501_STABLE 2026-02-03 10:32:34 +07:00
Huong Nguyen f1f1738ca8 Merge branch 'MDL-86905-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2026-02-03 10:27:03 +07:00
Huong Nguyen 8bdec47d8e Merge branch 'MDL-87675-501' of https://github.com/nvhai248/moodle into MOODLE_501_STABLE 2026-02-03 10:23:32 +07:00
AMOS bot dc662df705 Automatically generated installer lang files 2026-02-03 00:07:58 +00:00
Stefan Topfstedt d5f21323ce MDL-87694 core: prevent empty heading in context header. 2026-02-02 10:35:44 -08:00
Stefan Topfstedt 608266dace MDL-87694 profile: change page title to "Login required".
This realigns the page title with public/user/view.php.
2026-02-02 10:20:40 -08:00
Stefan Topfstedt f8349fb154 MDL-87694 profile: set top level heading when accessed as guest user. 2026-02-02 10:12:45 -08:00
Jun Pataleta 3408c2366d MDL-87694 core: headinglevel display option for $OUTPUT->confirm()
`\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.
2026-02-02 09:18:50 -08:00
Paul Holden 320eee740a MDL-86714 mod_scorm: parse reference file post-restore/import.
Ensure the SCORM reference file is setup and ready to use after
restoring from backup file. Additional support for ancient 1.9
backups.
2026-02-02 10:28:57 +00:00
Paul Holden ae877e4633 MDL-76437 gradingform_rubric: consistent styles for add buttons.
Remove duplication/redundancy for same elements from the theme.
2026-02-02 10:28:05 +00:00
Paul Holden 5881cd48de MDL-87096 enrol_database: observe activity dates display in courses.
Co-authored-by: Owen Herbert <owenherbert@catalyst-au.net>
2026-02-02 10:27:10 +00:00
cescobedo 6d9979bd1b Merge branch 'MDL-87761-501' of https://github.com/sarjona/moodle into MOODLE_501_STABLE 2026-02-02 11:16:10 +01:00
Jun Pataleta 9360d678e5 MDL-86905 core: Use proper link markup for email links 2026-02-02 18:04:10 +08:00
Huong Nguyen 5182889f53 Merge branch 'MDL-78342-501' of https://github.com/davewoloszyn/moodle into MOODLE_501_STABLE 2026-02-02 08:49:53 +07:00
Safat 918cc4f6c4 Merge branch 'MDL-86601-adhoc-index-MOODLE_501_STABLE' of https://github.com/bwalkerl/moodle into MOODLE_501_STABLE 2026-02-02 12:12:43 +11:00
Cameron Ball 6862165efa MDL-87029 quiz: Refetch attempts in overdue task to avoid stale submits
The update_overdue_attempts scheduled task iterated attempts from a recordset
and could act on stale attempt data if the web request finished an attempt
before cron processed it, causing duplicate submissions.

Change get_list_of_overdue_attempts() to return only attempt ids plus computed user
timeclose/timelimit, and refetch each attempt by id inside the task loop before
constructing quiz_attempt. This keeps quiz_attempt APIs unchanged and ensures cron
operates on current state
2026-01-31 15:38:34 +08:00
AMOS bot 052cf0a509 Automatically generated installer lang files 2026-01-31 00:07:50 +00:00
David Mudrák 9556da6637 MDL-87520 core: Fix interpretation of CFG->branch in update API client
Starting in version 3.10, Moodle started to use a different format for
the $CFG->branch that has not been fixed here. This is to make sure that
we request the correct version of the plugin.
2026-01-30 14:18:16 +01:00
Benjamin Walker 5af16f7c62 MDL-86601 core_task: Add index for scheduled task lastruntime 2026-01-30 22:52:31 +10:00
Benjamin Walker 223e070e9c MDL-86601 core_task: Add index for large adhoc task queues 2026-01-30 22:52:31 +10:00
Sara Arjona 3e49f34657 MDL-87761 course: Add warning for phasing out subsection descriptions
The Description field within the Subsection edit form has been deprecated since Moodle 5.2 onwards (see MDL-87279).
To ensure a smooth transition and avoid data loss for our users, the XD team have prepared a solution to notify them
of this upcoming change in stable versions.
2026-01-30 12:25:11 +01:00
Iñigo Zendegi a73abacee5 MDL-87396 mod_assign: Add availability check to notifications 2026-01-30 11:41:12 +01:00
Paul Holden 604a1e0cdd MDL-87334 course: looser check for empty return field editing course.
When the course format switcher reloads the form, the `$returnto`
parameter is of type string: "0", rather of type integer per initial
load.

In this case the `empty(...)` check is verifying all of empty string,
0 (integer) and "0" (string). Hurrah for PHP type checking.
2026-01-30 10:37:51 +00:00
Paul Holden d2eb4436ab MDL-87321 courseformat: explicit empty string check renaming module. 2026-01-30 10:36:45 +00:00
Paul Holden 7632786af9 MDL-87807 availability_grade: ensure required library is loaded.
Changes from 8cf9b221 introduced usage of a non-autoloaded class, so
we should be resilient and ensure the library in which it resides is
always loaded.
2026-01-30 10:09:06 +00:00
Huong Nguyen 50e44173c8 weekly release 5.1.1+ 2026-01-30 15:39:13 +07:00
Huong Nguyen 8084242767 NOBUG: Add upgrade notes 2026-01-30 15:39:13 +07:00
Huong Nguyen e05e7093c9 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-01-30 15:39:02 +07:00
Andrew Nicols ebf862e0d3 NOBUG: Bump composer.lock 2026-01-30 13:06:41 +08:00
Niko Hoogeveen c94a1593e3 MDL-84966 core_question: Updated question bank tag filter
Fixes a bug where filtering questions by tag the 'NONE'
option was not being respected. Automated testing was also added
to cover tag filtering conditions in the question bank
2026-01-29 09:41:57 -05:00
Daniel Ureña d5cad3ef39 MDL-87185 block_site_main_menu: Add required library 2026-01-29 13:14:55 +01:00
fgo 65da222994 MDL-86437 enrol_lti: Cast scoreGiven to float for LTI compliance
scoreGiven was being passed as a string (e.g. "8.0"), which caused
validation errors in some LMS implementations such as Rustici Engine.
According to the LTI specification, scoreGiven must be numeric if
present.

Co-authored-by: Jake Dallimore <jake@moodle.com>
2026-01-29 14:58:43 +08:00
Jake Dallimore 3382df5406 MDL-87789 libraries: patch lti1p3 for certification support
https://github.com/packbackbooks/lti-1-3-php-library/issues/169 blocks
us from using the new build.1edtech certification tooling. This issue
commits that fix as a library customisation/hack for stables, so the
suite can be used for annual/bi-annual re-certification.
2026-01-29 13:24:56 +08:00
Huong Nguyen f9280e786b Merge branch 'MDL-87521_501' of https://github.com/timhunt/moodle into MOODLE_501_STABLE 2026-01-29 11:03:56 +07:00
Huong Nguyen 8d4df26b01 Merge branch 'MDL-87087-501' of https://github.com/lameze/moodle into MOODLE_501_STABLE 2026-01-29 11:01:29 +07:00
Huong Nguyen 884a9b436c Merge branch 'MDL-86994-501' of https://github.com/mioyu-ubiq/moodle into MOODLE_501_STABLE 2026-01-29 10:58:20 +07:00
Huong Nguyen bada40d4a4 Merge branch 'MDL-87018-501' of https://github.com/bhect0/moodle into MOODLE_501_STABLE 2026-01-29 10:55:02 +07:00
Huong Nguyen b0130619d6 Merge branch 'MDL-87104-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-29 10:46:28 +07:00
Huong Nguyen 207f222e80 Merge branch 'MDL-87323-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-29 10:41:30 +07:00
Huong Nguyen 8e959ef40d Merge branch 'MDL-86988-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-29 10:39:09 +07:00
Angelia Dela Cruz 846ddd3979 MDL-87087 tool_dataprivacy: Test data request by authorized user 2026-01-29 11:08:26 +08:00
AMOS bot 5370fa4d1d Automatically generated installer lang files 2026-01-29 00:07:47 +00:00
David Mudrák b23182d82d MDL-87593 lang: Fix strings order 2026-01-28 21:44:45 +01:00
Tim Hunt 2048b6330c MDL-83325 filepicker: tidy up display of restrictions 2026-01-28 20:13:35 +00:00
Paul Holden 8c49fdeb1d MDL-87714 customfield_number: upgrade note for recalculate parameters. 2026-01-28 16:34:04 +00:00
Paul Holden 37ec33f56c Merge branch 'MDL-87714-501' of https://github.com/sebastsg/moodle into MOODLE_501_STABLE 2026-01-28 16:07:12 +00:00
Sebastian Gundersen e4c3310295 MDL-87714 customfield: Add component/area to field number provider
Fixes ambiguous instances when using shared custom fields.
The component and area parameters are now used to get the proper handler.
2026-01-28 16:24:35 +01:00
Tim Hunt 915c263266 MDL-87521 behat: better error reporting if an adhoc task fails 2026-01-28 14:56:24 +00:00
Paul Holden 0f332fb0ff Merge branch 'MDL-87736-501' of https://github.com/yerairogo/moodle into MOODLE_501_STABLE 2026-01-28 14:45:07 +00:00
Paul Holden dd8a1647c3 MDL-87059 customfield: upgrade note regarding shortname uniqueness. 2026-01-28 14:39:48 +00:00
Paul Holden b56a9953cf Merge branch 'MDL-87059-501' of https://github.com/dravek/moodle into MOODLE_501_STABLE 2026-01-28 14:26:14 +00:00
Paul Holden 5cb2949d50 Merge branch 'MDL-87437-501' of https://github.com/tasiobg/moodle into MOODLE_501_STABLE 2026-01-28 14:16:27 +00:00
Yerai Rodríguez 75ad63dd97 MDL-87736 customfield: Retrieve instances data using component and area 2026-01-28 13:22:30 +01:00
Jun Pataleta 2448dbd55f Merge branch 'MDL-86639-501' of https://github.com/yusufwib01/moodle into MOODLE_501_STABLE 2026-01-28 16:17:30 +08:00
mioyu-ubiq 0222576f3f MDL-86994 mod_assign: Behat to test permitted users can edit submission 2026-01-28 13:47:22 +08:00
Hai Nguyen Van 002e281598 MDL-87675 core: Add delay for displaying the YUI alert dialogue message
* Add a slight delay for displaying the message content of the YUI
alert dialogue's ARIA live container to allow screen readers to
announce the alert message accordingly.
2026-01-28 03:19:03 +00:00
David Mudrák 17f2636bc2 MDL-87593 lang: Use the fixed strings in tests 2026-01-27 21:40:36 +01:00
Helen Foster cff7c05eb8 MDL-87593 lang: Import fixed English strings (en_fix) 2026-01-27 21:40:05 +01:00
Mark Johnson 3f9e294f21 MDL-87606 backup: Include child questions in backups
Since MDL-41924, because we are not always including the whole category
of each question being used in a backup, we have been missing some child
questions.

This expands the query selecting questions belonging to categories
where only annotated questions are being included, to also select any
children of those questions.
2026-01-27 14:52:13 +00:00
Paul Holden 687d6670b3 MDL-87100 core: infer upgrade note issue number from current branch. 2026-01-27 10:55:00 +00:00
Paul Holden 0690e7b230 MDL-86659 enrol_flatfile: remove spurious get_config parameters. 2026-01-27 10:40:21 +00:00
Paul Holden e80b3cf70a MDL-87104 message: remove border from contact request sent profile info. 2026-01-27 10:26:04 +00:00
Raquel Ortega 86de0a0c8d MDL-87289 mod_forum: Disable mediaplugin filter in emails 2026-01-26 21:33:40 +01:00
Paul Holden 50a0403d37 MDL-81441 tiny_media: broaden content search for used draft files.
Changes in 47f9106c (upon which this code is based) assumed used files
were always enclosed within quote marks. However this isn't the case
for embedded H5P files which output the filepath directly and rely on
subsequent filtering to turn into appropriate markup.
2026-01-26 10:53:01 +00:00
Huong Nguyen 59d0bb149d Merge branch 'MDL-84862-501-test' of https://github.com/yusufwib01/moodle into MOODLE_501_STABLE 2026-01-26 15:32:28 +07:00
David Carrillo 7b93ccde37 MDL-87059 customfield: Check if custom field shortname is unique
- If is a shared field check that shortname does not exist anywhere.
- If is an entity field check that the shortname does not exist
within the same entity fields or in the shared fields.
2026-01-26 08:02:39 +01:00
Huong Nguyen 96dd901bea weekly release 5.1.1+ 2026-01-26 10:38:54 +07:00
Huong Nguyen 316507507b Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-01-26 10:38:43 +07:00
Héctor Benedicte e11305428c MDL-87018 qtype_essay: Add unit tests for un_summarise_response(). 2026-01-23 19:29:22 +01:00
AMOS bot 489eb64b4a Automatically generated installer lang files 2026-01-23 00:08:05 +00:00
Mihail Geshoski 555eef58af MDL-87287 qbank_tagquestion: Fix version bump 2026-01-22 14:25:06 +07:00
Safat 2e011c622f Merge branch 'MDL-86527-501' of https://github.com/HuongNV13/moodle into MOODLE_501_STABLE 2026-01-22 15:39:43 +11:00
Huong Nguyen a686188165 Merge branch 'MDL-87188-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-22 11:02:45 +07:00
Huong Nguyen 880314b565 Merge branch 'MDL-87317-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-01-22 10:52:55 +07:00
Huong Nguyen 638de36275 Merge branch 'MDL-87318-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-01-22 10:46:19 +07:00
Huong Nguyen bc72e1c107 Merge branch 'MDL-86797-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-22 10:44:18 +07:00
Huong Nguyen 23a8fdc812 Merge branch 'MDL-87092-501' of https://github.com/Kredelirious/moodle into MOODLE_501_STABLE 2026-01-22 10:36:45 +07:00
Huong Nguyen 6511e183f5 Merge branch 'MDL-87090-501' of https://github.com/lameze/moodle into MOODLE_501_STABLE 2026-01-22 10:32:28 +07:00
Jun Pataleta 775858e46a Merge branch 'MDL-86136_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-01-22 08:32:47 +08:00
Jun Pataleta ff9fb97d72 Merge branch 'MDL-87478_501' of https://github.com/mohdtahseen1/moodle into MOODLE_501_STABLE 2026-01-22 08:22:46 +08:00
Mark Johnson c00b4eda45 MDL-86136 question: Amend version restore test
Restoring a whole course will now restore the backed-up questions to a
new qbank instance. This means the test to restore versions was not
restoring the deleted versions to the original question bank entires,
but to a new question bank entry in the new qbank.

Updating the test to just backup and restore the quiz resolves this.
2026-01-21 09:30:31 +00:00
Mark Johnson ca12b96b77 MDL-86136 qbank: Restore entire activity when it is in the backup
When restoring a backup containing a question bank, we were always
trying to match questions to an existing category on the target course
if once existed. This meant that when duplicating a qbank on the same
course, or importing the same qbank to another course multiple times,
only one of the restored qbanks would end up with questions in it.

This adds a mechanism to record any activities in the backup which
support `FEATURE_PUBLISHES_QUESTIONS`. Then when we restore the
questions, we check if we will be restoring the original activity they
belong to, and skip trying to match them to an existing category. This
means a new copy of each question will be created, and moved to the new
copy of the qbank once it is created.
2026-01-21 09:30:31 +00:00
Muhammad Arnaldo 2bc458c4f9 MDL-86527 behat: Avoid element-click-intercepted failures
Adjusts the window size to accommodate wide tables.
This ensures that columns which were previously clipped
are now visible and accessible to the Behat driver.
2026-01-21 15:05:05 +07:00
Muhammad Arnaldo 6945ab9db5 MDL-86527 behat: Fix random failures when toggling edit mode 2026-01-21 15:05:05 +07:00
Adrian Greeve 3768fa2928 Merge branch 'MDL-87625-501' of https://github.com/andrewnicols/moodle into MOODLE_501_STABLE 2026-01-21 14:19:15 +08:00
Adrian Greeve ad9a1630a1 Merge branch 'MDL-87659-501' of https://github.com/andrewnicols/moodle into MOODLE_501_STABLE 2026-01-21 13:13:35 +08:00
Andrew Nicols 3252c42592 MDL-87659 core: Hide "Details" section of checks Table when there are none 2026-01-21 13:01:41 +08:00
Huong Nguyen f41ee92e8e Merge branch 'MDL-79221-MOODLE_501_STABLE' of https://github.com/PhMemmel/moodle into MOODLE_501_STABLE 2026-01-21 10:14:12 +07:00
Huong Nguyen 77937f2359 Merge branch 'MDL-51447-501' of https://github.com/AnupamaSarjoshi/moodle into MOODLE_501_STABLE 2026-01-21 10:10:40 +07:00
AMOS bot e3ccaace4a Automatically generated installer lang files 2026-01-21 00:08:12 +00:00
Yusuf Wibisono b612105230 MDL-86639 groups: Wrap group names in grade report group selector 2026-01-21 03:29:16 +07:00
Ilya Tregubov 157a8eb014 Merge branch 'MDL-76975-501' of https://github.com/yerairogo/moodle into MOODLE_501_STABLE 2026-01-20 19:45:50 +02:00
Paul Holden 7d620f8fdc MDL-86797 courseformat: account for hidden delegated section info. 2026-01-20 09:56:34 +00:00
Kim Jared Lucas ba75b60c85 MDL-87092 tool_uploaduser: suspend enrolments via CSV upload 2026-01-20 11:17:44 +08:00
Mihail Geshoski d70681c2f5 Merge branch 'MDL-87287_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-01-20 10:35:32 +08:00
AMOS bot 1bc6eba524 Automatically generated installer lang files 2026-01-20 00:08:12 +00:00
Anupama Sarjoshi 6200a07627 MDL-51447 block_completionstatus: Show only user-visible activities
This change ensures that the completion details page displays only the
activities that are visible to the user on the course home page.
Activities that are hidden, unavailable or  located in hidden sections
are no longer shown in the completion details view.

Co-authored by: ewallah <info@ewallah.net>
2026-01-19 17:56:56 +05:30
Anupama Sarjoshi ccdacb8443 MDL-51447 core_completion: Get visible activities with completion on 2026-01-19 17:56:56 +05:30
Simey Lameze a84be34f83 MDL-87090 behat: improve new test 2026-01-19 14:12:25 +08:00
mioyu-ubiq 5ec89b95ec MDL-87090 mod_assign : Teacher can override assignment deadlines 2026-01-19 14:12:25 +08:00
David Woloszyn 189be87f2e MDL-78342 core_table: Remove line break conversion in format_text 2026-01-19 13:08:29 +11:00
Mark Johnson 61868b9510 MDL-86609 qbank_bulkmove: Select the current category by default 2026-01-16 14:26:37 +00:00
Nicolas Dunand 13317f2598 MDL-86609 qbank_bulkmove: Target qbank categoryid fix. 2026-01-16 14:25:36 +00:00
Luca Bösch 54dfeb3d11 MDL-87318 gradereport_grader: No over-thick table borders with BS 5. 2026-01-16 11:01:22 +01:00
Luca Bösch 908784bca9 MDL-87317 report_progress: Striped hovering table rows with BS 5. 2026-01-16 11:00:27 +01:00
Jake Dallimore 28c9a52455 weekly release 5.1.1+ 2026-01-16 16:38:17 +08:00
Jake Dallimore 86ee7eef07 NOBUG: Add upgrade notes 2026-01-16 16:38:17 +08:00
Jake Dallimore b4ed92a701 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-01-16 16:38:11 +08:00
Paul Holden 2ae416b6f5 MDL-85300 grade: account for tooltip size in bulk move dialogue.
Now that it actually works, it obscures some elements required in
Behat scenarios. Resize the icon, and place it to the right.
2026-01-16 15:23:29 +08:00
Jake Dallimore c5faeaee4c Merge branch 'MDL-87634-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Jayce Birrell d3c9ccba3c MDL-86802 core: make CURLOPT_USERPWD optional for put()
Microsoft does not always allow an Authorization header when using PUT but the put() method sets one
by default - https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0#remarks.
I've included an optional argument to prevent this, but the default
value will maintain the pre-existing logic to avoid other regressions.
2026-01-16 15:23:29 +08:00
Jake Dallimore 72c6f8882c Merge branch 'MDL-86423-501' of https://github.com/martygilbert/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Jake Dallimore ec07626137 Merge branch 'MDL-87635-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Jake Dallimore 28f156895a Merge branch 'MDL-86869_501-single-view-overload' of https://github.com/alexkeel/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Paul Holden 7adddedf4f MDL-50364 forms: consistent access to language strings from JS. 2026-01-16 15:23:29 +08:00
Paul Holden 6ba03f474a Merge branch 'MDL-50364-501' of https://github.com/Jayce0808/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Ilya Tregubov 44fbef243d Merge branch 'MDL-85300-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Andrew Nicols 7c19447345 Merge branch 'MDL-87608-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Mi Jia Looi 5e946c1f4f MDL-86017 mod_folder: Cast customdata to object in folder_cm_info_view() 2026-01-16 15:23:29 +08:00
Huong Nguyen f793334a44 Merge branch 'MDL-87070-501' of https://github.com/andelacruz/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Huong Nguyen 4c47af3dee Merge branch 'MDL-85431_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Huong Nguyen 3ab3c72aa7 Merge branch 'MDL-87443-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:29 +08:00
Huong Nguyen efee80655a Merge branch 'MDL-87229-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2026-01-16 15:23:28 +08:00
Huong Nguyen a4da1b6b4e Merge branch 'MDL-87056-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:28 +08:00
Huong Nguyen 36186e9af7 Merge branch 'MDL-86565-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:28 +08:00
Huong Nguyen 37c54215d1 Merge branch 'MDL-87120-501' of https://github.com/paulholden/moodle into MOODLE_501_STABLE 2026-01-16 15:23:27 +08:00
Paul Holden 5f99938670 MDL-87358 h5plib_v127: add unicode modifier to title matching regex.
See https://github.com/h5p/h5p-php-library/issues/276

Co-authored-by: Фидан Сынбулатов
2026-01-15 11:04:58 +00:00
AMOS bot 92788c9c31 Automatically generated installer lang files 2026-01-15 00:08:05 +00:00
Mark Johnson 42cb48db5c MDL-85653 qbank_editquestion: Display static status when not editable 2026-01-14 15:41:39 +00:00
Mark Johnson 3bb83daec9 MDL-86444 questions: Fix question categories with incorrect top parents 2026-01-14 10:28:15 +00:00
Paul Holden 34db25536e MDL-87634 core: exclude fixtures from Github actions "one-by-one" job.
This is consistent with the PHPUnit general configuration in 20aa9d05.
2026-01-14 09:25:27 +00:00
Yerai Rodríguez ab57c200c5 MDL-76975 admin: Move AMD dependencies to data attribute to fix overflow
Since there is a 1024 length limit on arguments passed through
js_call_amd(), we pass it through a HTML element dataset to avoid
this limit.
2026-01-14 08:28:48 +01:00
Guest 3b72da69af MDL-87478 mod_quiz: Make pre-create attempts work with previews 2026-01-13 15:12:52 +05:30
AMOS bot 358dd1dc29 Automatically generated installer lang files 2026-01-13 00:08:07 +00:00
Andrew Nicols 3f436bb599 MDL-87625 core: Use the /r.php basepath to guess routes 2026-01-13 08:06:18 +08:00
Paul Holden d7431b36a2 MDL-87635 backup: ensure required test libraries are loaded. 2026-01-12 17:22:16 +00:00
Simey Lameze 159a155062 MDL-87088 behat: add missing @javascript tags to required tests 2026-01-12 13:37:44 +07:00
Angelia Dela Cruz 0c0b4b69c9 MDL-87070 mod_glossary: Behat to confirm glossary autolinking works 2026-01-12 11:08:07 +08:00
Huong Nguyen a4b47235d6 Merge branch 'MDL-87088-501' of https://github.com/andelacruz/moodle into MOODLE_501_STABLE 2026-01-12 09:53:15 +07:00
dustinhuynh 90e25dfc45 MDL-86058 core_task: enable disable tasks with a pending upgrade 2026-01-12 09:44:05 +07:00
Stefan Topfstedt 3c7933178d MDL-77162 filepicker: default to selecting first repository available. 2026-01-09 08:34:59 -08:00
Paul Holden 738fb91dec MDL-87443 reportbuilder: use Toast selector in Behat scenarios. 2026-01-09 12:08:02 +00:00
Paul Holden badca8a618 MDL-87443 behat: define new Toast message named selector. 2026-01-09 12:08:02 +00:00
Mark Johnson 0fa3a9d6aa MDL-87287 qbank_deletequestion: Update behat test for tag deletion
Now that deleting a tagged question correctly deletes it tag instances,
the tag is no longer available for re-selection after all the tagged
questions are deleted.
2026-01-09 10:40:50 +00:00
Mark Johnson 8b0edbf074 MDL-87287 mod_qbank: De-duplicate tags when migrating questions
In the migration from old-style question banks to mod_qbank, if a
question had the same tag from multiple contexts, when it was moved to
its new question bank context, the migration tried to set multiple tag
instances for the same tag to the same context, which caused a unique
key violation.

This add some checking to the
`question_move_question_tags_to_new_context` function to identify any
tag instances to be moved that will trigger this key violation, and
delte any duplicates before the move so we end up with a single instance
of the tag in the new context. This is done in a transaction that will
only be committed once the remaining tags have been moved.

This required adding the `tiuserid` field to the data returned by
`core_tag_tag::get_items_tag` so we can assemble the unique key without
having to do additional queries.

This also extends the test coverage for the `transfer_questions` task
to ensure tags are moved to the new contexts, and tags on stale questions
are deleted.
2026-01-09 10:40:29 +00:00
Mark Johnson 27db562110 MDL-87287 qbank_tagquestion: Remove all tags from deleted questions
When we deleted a question, we were including the userid of the user who
performed the delete when checking for question tags. This means that if
user A tagged the question, and user B deleted the question, it might
leave the tags behind.

This fixes the issue by deleting tags regardless of the user, and adds
an upgrade step to find and remove orphaned tag instances.
2026-01-09 10:40:27 +00:00
Tasio de4bcb1b4c MDL-87437 course: Make course cached image entries domain-agnostic 2026-01-09 10:12:28 +01:00
Luca Bösch 47ce3408a1 MDL-87229 course: Show nice "Other users" table in courses. 2026-01-09 07:12:35 +01:00
Jayce Birrell 1e2abd1466 MDL-50364 forms: calendar header renderer uses translated months 2026-01-09 13:53:07 +10:30
Angelia Dela Cruz 1e5e225a86 MDL-87088 core_grades: Behat to view grades from user menu 2026-01-09 10:26:11 +08:00
Huong Nguyen 774796cf77 weekly release 5.1.1+ 2026-01-09 08:41:18 +07:00
Huong Nguyen 9e72cc3998 NOBUG: Add upgrade notes 2026-01-09 08:41:17 +07:00
Huong Nguyen 928b6719dc Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2026-01-09 08:41:06 +07:00
Christian Abila 878ef73e8c MDL-51152 Enrolments: clarify web service 2026-01-08 09:27:24 +01:00
Christian Abila 106c11ddde MDL-51152 Enrolments: remove unused parameter roleid 2026-01-08 09:27:24 +01:00
Jun Pataleta 1c4022b77d MDL-85535 forum: Render discussion navigation at the bottom only 2026-01-08 11:34:49 +08:00
Jun Pataleta 158bff3dcb MDL-85535 forum: Label discussion navigation 2026-01-08 11:34:49 +08:00
Marty 8642510801 MDL-86423 mod_assign: Reduce duesoon/overdue notifications.
Students shouldn't receive overdue notifications if there is nothing to
submit in Moodle.

Students shouldn't receive due soon notifications if they a) already
have a grade or b) meet activity completion status.
2026-01-07 12:24:20 -05:00
Paul Holden 36a0b0572f MDL-87120 course: observe activity dates display in course requests.
Upgrade step to ensure the default value for this field in the schema
matches the default value used in the interface itself.
2026-01-07 16:43:22 +00:00
Paul Holden 0fd0462bc6 MDL-87608 javascript: fix public paths in generated jsconfig file.
This partly reverts changes from 31335206, removing the double public/
directory component of the file paths.
2026-01-07 14:30:40 +00:00
Safat 362c9de6ad Merge branch 'MDL-87406-501' of https://github.com/ericmerrill/moodle into MOODLE_501_STABLE 2026-01-07 16:27:32 +11:00
Jun Pataleta 8d8941166c Merge branch 'MDL-86436-navigation-accessibility-page-heading-menu-visible-on-larger-screens-even-if-empty-M5.1' of https://github.com/stopfstedt/moodle into MOODLE_501_STABLE 2026-01-07 11:18:15 +08:00
Jun Pataleta 0d16e46646 Merge branch 'MDL-87416-glossary-entry-page-title-missing-M5.1' of https://github.com/stopfstedt/moodle into MOODLE_501_STABLE 2026-01-07 11:14:03 +08:00
Santosh Nagargoje 4d04135186 MDL-83984 core: Fixed subpanel show/hide issue 2026-01-06 16:20:20 +05:30
Adrian Greeve cb4e4c8d45 Merge branch 'MDL-87526-501' of https://github.com/andrewnicols/moodle into MOODLE_501_STABLE 2026-01-06 09:08:39 +08:00
Stefan Topfstedt bf58e69516 MDL-87416 glossary: set page title on glossary entry details page. 2026-01-05 11:30:37 -08:00
Mark Johnson cf51659f5a MDL-85431 questions: Fix access checks on Question banks page
Users who could not manage activities on a course, but did have
permission to access particular question banks, could not access the
Question banks page.

This modifies the permission checks on that page to allow access if the
user can either manage activities, or has access to at least one
activity that publishes questions. Some additional changes to the output
were required to prevent non-editing users adding banks.

It also modifies the navigation hook to add the Question banks
navigation node if the user can access any activitiy that publishes
questions on the course. This is all based on cached modinfo and
permissions data so should remain performant.
2026-01-05 15:21:05 +00:00
Paul Holden 4a7f57ee8e Merge branch 'MDL-87529-501' of https://github.com/yerairogo/moodle into MOODLE_501_STABLE 2026-01-05 14:24:31 +00:00
Paul Holden 1cc0453ccd Merge branch 'MDL-87288-501' of https://github.com/ilyatregubov/moodle into MOODLE_501_STABLE 2026-01-05 09:46:52 +00:00
AMOS bot 9f589703a6 Automatically generated installer lang files 2026-01-03 00:08:06 +00:00
Huong Nguyen ed4f0da1c9 Merge branch 'MDL-81882-501' of https://github.com/yusufwib01/moodle into MOODLE_501_STABLE 2025-12-29 13:51:38 +07:00
Matt Porritt 923eb86a8c MDL-84862 AI: Stop provider instance creataion when no provider plugins
Prevents calls to action to create an AI provider instance from
being displayed to users when there are no AI provider plugins
installed in the instance.

Co-Authored-By: Yusuf Wibisono <yusuf.wibisono@moodle.com>
2025-12-29 12:00:49 +07:00
Huong Nguyen 5886683a33 Merge branch 'MDL-87433-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2025-12-29 09:54:37 +07:00
yusufwib01 5ef0a36d3d MDL-81882 core: Optimize block instance deletion with batch processing
Improve upgrade execution by gathering block instance IDs upfront
and processing deletions of related records in context, block_positions, user_preferences, and block_instances tables in batches.
2025-12-29 09:33:14 +07:00
AMOS bot 07e4f55804 Automatically generated installer lang files 2025-12-27 00:08:08 +00:00
AMOS bot dda4552e79 Automatically generated installer lang files 2025-12-26 00:08:05 +00:00
Muhammad Arnaldo c04a71b431 MDL-87433 course: Add sorting to get_nearest_editable_subcategory
Add ORDER BY to the query to ensure categories are returned in
consistent order. This fixes random test failures, especially
on PostgreSQL.
2025-12-24 16:14:19 +07:00
Yerai Rodríguez 120beb8aaf MDL-87529 core_customfield: reset CF handler cache after form save 2025-12-23 14:01:49 +01:00
Alex Keel d08fd3c2af MDL-86869 gradereport_singleview: Raises memory limit 2025-12-23 12:25:49 +00:00
Alex Keel 2b94d55524 MDL-86869 gradereport_singleview: Lower the max per page limit 2025-12-23 12:23:48 +00:00
Mihail Geshoski 057c314edc Merge branch 'MDL-86798_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2025-12-23 16:09:55 +08:00
Andrew Nicols d4690ea232 MDL-87526 core: Specify composer project type 2025-12-23 11:49:15 +08:00
Andrew Nicols 88763ab043 MDL-87526 core: Moodle provides moodle/lms 2025-12-23 11:49:02 +08:00
Mark Johnson 547ea9cfbf MDL-86798 questions: Check original context is on the same site
When checking if the original context exists during a restore, we need
to check that we're looking at the context on the same site, not one on
a different site that happens to have the same ID.
2025-12-19 09:31:14 +00:00
Mark Johnson cfaccbf953 MDL-86798 questions: Track next version for question bank entries
This adds a new "nextversion" field to `question_bank_entries` to track
which version number the next version of the question should have. This
ensures that version numbers are not re-used if the latest version is
deleted.

To use this field, you must call
`\core_question\versions::get_next_version()`. This will initialise the
field to the correct value if it is currently null.

If you create a new version using this value, you must then call
`\core_question\versions::increment_next_version()` to increment the
counter.
2025-12-19 09:31:14 +00:00
Mark Johnson 11d0802ca6 MDL-86798 questions: Restore backups containing deleted versions
If the latest version of a question is deleted, and then a new one
created, the new one will currently re-use the version number of the
deleted version. If the original version was backed up before it was
deleted, then restored later, we end up with two questions in one
question bank entry that have the same version.

To resolve this, when restoring a question, we check if there is already
a version with the same version number, then bump it and any higher
versions up by 1. We also check for any references specifically using
these versions and bump those up by 1. Any references using "always
latest" will be fine, as the latest version will still be the latest.

Additionally, if deleting a version made it hidden, and the backup
contains the same version in "ready" state, restoring the backup will
restore it to "ready" state.
2025-12-19 09:29:03 +00:00
Huong Nguyen 1cd17816c5 weekly release 5.1.1+ 2025-12-19 09:04:16 +07:00
Ilya Tregubov 8bcf6f016d MDL-87288 output: Fix pagination in small containers. 2025-12-18 09:37:45 +01:00
Huong Nguyen bd39c5df46 Merge branch 'MDL-87340-501' of https://github.com/lameze/moodle into MOODLE_501_STABLE 2025-12-18 11:44:37 +08:00
Huong Nguyen 85f08eccd7 Merge branch 'MDL-86420-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2025-12-18 11:44:36 +08:00
Huong Nguyen e35c3f18e5 Merge branch 'MDL-86086-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2025-12-18 11:44:36 +08:00
Adrian Greeve 700ffe79a3 Merge branch 'MDL-87461-501' of https://github.com/andrewnicols/moodle into MOODLE_501_STABLE 2025-12-18 11:44:36 +08:00
Huong Nguyen 0bfc7f5abd Merge branch 'MDL-87094-501' of https://github.com/andelacruz/moodle into MOODLE_501_STABLE 2025-12-18 11:44:36 +08:00
Mihail Geshoski 39a16ed55a Merge branch 'MDL-72833_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2025-12-18 11:44:36 +08:00
Andi Permana 7414e469b8 MDL-86086 theme_boost: Add visual distinction for readonly form inputs 2025-12-17 19:07:45 +07:00
Andrew Nicols a55b9cfc70 MDL-87461 core: Update composer lock file 2025-12-17 16:14:52 +08:00
Andrew Nicols f4187d49da MDL-87461 core: Add Composer config to indicate the use of public dir 2025-12-17 16:14:51 +08:00
Mihail Geshoski cacdd7e63a Merge branch 'MDL-86416-501' of https://github.com/junpataleta/moodle into MOODLE_501_STABLE 2025-12-17 12:14:29 +08:00
Paul Holden 08d6834166 MDL-85300 output: ensure Bootstrap tooltips load for dynamic content.
Prior to this change, tooltips would only load for elements in the
initial page DOM without considering those elements subsequently added
dynamically (e.g. via AJAX).

Co-authored-by: Sebastian Gundersen <sebastian@sgundersen.com>
2025-12-16 13:30:11 +00:00
Amaia Anabitarte 740c3dafcc Merge branch 'MDL-87436-501' of https://github.com/laurentdavid/moodle into MOODLE_501_STABLE 2025-12-15 13:41:16 +01:00
Mark Johnson b50f58ec0d MDL-72833 quiz: Respect locale in maxmark setting
Editing maxmarks in quiz slots was not respecting the decimal separator
from the set language's locale, meaning if your language formats numbers
like 1,10 editing didn't work correctly.

This sets the correct parameter to ensure the locale is respected.
2025-12-15 11:47:24 +00:00
Jun Pataleta 3fd9808fc6 Merge branch 'MDL-87341_501' of https://github.com/TomoTsuyuki/moodle into MOODLE_501_STABLE 2025-12-15 18:48:45 +08:00
Angelia Dela Cruz 9155d32f36 MDL-87094 core_course: Behat to restrict available activities/resources 2025-12-15 12:00:38 +08:00
Huong Nguyen e6d7fcb2e0 Merge branch 'MOODLE_501_STABLE_MDL-73396' of https://github.com/niko-hoogeveen/moodle into MOODLE_501_STABLE 2025-12-15 08:55:35 +07:00
Huong Nguyen c5b69126d7 Merge branch 'MDL-86679-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2025-12-15 08:46:48 +07:00
Tomo Tsuyuki 0f0904243d MDL-87341 javascript: Fix aria label issue for navigation bar 2025-12-15 10:24:46 +11:00
Stefan Topfstedt 95d96c926b MDL-86436 navigation: hide empty page heading menu.
Co-authored-by: Jun Pataleta <jun@moodle.com>
2025-12-12 18:06:09 -08:00
Stefan Topfstedt e39521934c MDL-87415 profile: set page title when accessed as guest user. 2025-12-12 13:42:38 -08:00
Philipp Memmel c629f23da8 MDL-79221 core_grade: Fix fetching context for disabled mod plugins
Activity plugins that are disabled on the site are not included in
the modinfo cache. That's why their context must not be fetched via
modinfo cache.
2025-12-12 06:42:56 +01:00
Philipp Memmel 3f5b13cd49 MDL-79221 core_completion: Do not update state for disabled plugins 2025-12-12 06:42:56 +01:00
Shamim Rezaie 019e03af94 weekly release 5.1.1+ 2025-12-12 14:06:13 +11:00
Shamim Rezaie 8e477788f8 Merge branch 'install_501_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_501_STABLE 2025-12-12 14:06:02 +11:00
Mihail Geshoski b32b09d584 MDL-87412 qbank_managecategories: Use single quotes in scenario name 2025-12-12 09:45:21 +07:00
Huong Nguyen 1a1033c601 Merge branch 'MDL-84736-501' of https://github.com/lameze/moodle into MOODLE_501_STABLE 2025-12-12 09:45:21 +07:00
Huong Nguyen fcde530bc4 Merge branch 'MDL-86830-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Huong Nguyen 8117d73b98 Merge branch 'MDL-86808-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Mihail Geshoski 6ca58162c6 Merge branch 'MDL-87182-501' of https://github.com/srobotta/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Mihail Geshoski 7b348fa333 Merge branch 'MDL-87121_501' of https://github.com/santoshndev/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Mihail Geshoski a6587c7986 Merge branch 'MDL-87412_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Mihail Geshoski 19b193784b Merge branch 'MDL-87142-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Jun Pataleta 6f44b00e42 Merge branch 'MDL-86807-501' of https://github.com/muhammadarnaldo/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Jun Pataleta 90eb487eec Merge branch 'MDL-86805-501' of https://github.com/andimendunia/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Huong Nguyen 2d111226fe Merge branch 'MDL-86593-501' of https://github.com/christianabila/moodle into MOODLE_501_STABLE 2025-12-12 09:45:20 +07:00
Muhammad Arnaldo 832acc2639 MDL-86420 enrol_database: avoid duplicate key violations 2025-12-11 10:25:18 +07:00
AMOS bot 7eb5190921 Automatically generated installer lang files 2025-12-11 00:08:10 +00:00
Niko Hoogeveen 2e1e274ed4 MDL-73396 auth_oauth2: link non-suspended account on oauth login
Added checks to link non-suspended user accounts to OAuth2, when logging
in. Also added an additional check to prevent users from using OAuth2
to login if their account is suspended in Moodle.
2025-12-10 14:22:52 -05:00
Niko Hoogeveen a217af7ae5 MDL-73396 core_user: prioritize active users when retrieving by email
Updated get_users_by_email() to return active users at the
start of the returned array, suspended users last.
2025-12-10 14:22:52 -05:00
Laurent David 1e893d4b21 MDL-87436 core_courseformat: Fix id collision when testing
* When testing using phpunit/sqlsrv the ids of sections and cm are colliding
so we need to make sure we separate them as a reference to a cm can be the
same as a reference to a section
2025-12-10 12:10:41 +01:00
Simey Lameze de2ab7ccb7 MDL-84736 behat: add custom step to edit a criterion
This commit also improves the existing tests and add a new scenario to
verify the 'Do not mark for regrade' behavior.
2025-12-09 17:21:40 +08:00
Angelia Dela Cruz f6a31e8fdc MDL-84736 gradingform : Behat for editing guide with graded submissions 2025-12-09 17:21:40 +08:00
Stefan Topfstedt 7cf1291cc2 MDL-86416 navigation: Render the more menu only when it's not empty
Co-authored-by: Jun Pataleta <jun@moodle.com>
2025-12-09 11:17:38 +08:00
Huong Nguyen f380071cb5 Merge branch 'MDL-87379_501' of https://github.com/santoshndev/moodle into MOODLE_501_STABLE 2025-12-09 09:28:18 +07:00
Mihail Geshoski 3055d89c46 Merge branch 'MDL-82281-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2025-12-09 09:45:32 +08:00
Mihail Geshoski 4ef04a9d94 Merge branch 'MDL-86677-501' of https://github.com/lucaboesch/moodle into MOODLE_501_STABLE 2025-12-09 09:28:08 +08:00
Mihail Geshoski a633683bb3 Merge branch 'mdl-87356_501' of https://github.com/james-cnz/moodle into MOODLE_501_STABLE 2025-12-09 09:17:16 +08:00
Simey Lameze 0c4b848a48 MDL-87340 mod_quiz: make test_question_shuffle more robust
This commit improves test_question_shuffle by retrying if questions
remains in the same order after shuffling.
2025-12-09 07:34:04 +08:00
Stephan Robotta 010daf38da MDL-87182 quiz: no CBM metric when no data available 2025-12-08 08:46:06 +01:00
Muhammad Arnaldo 038963316f MDL-86807 core_courseformat: RTL chevron in move activity modal 2025-12-08 11:50:31 +07:00
Andi Permana 94a34a82f7 MDL-86830 calendar: Fix double encoding in event title attributes 2025-12-08 10:42:42 +07:00
Luca Bösch d8f2f5e6a0 MDL-87142 question: dropdown-toggle class for question format select 2025-12-07 20:12:35 +01:00
Luca Bösch bdcc11503d MDL-86677 mod_quiz: let the summary of attempt table be striped 2025-12-07 20:11:41 +01:00
Luca Bösch 35ca47e57a MDL-82281 quiz: Amend test to circumvent modinfo cache fails. 2025-12-07 20:10:52 +01:00
Andi Permana 840c045bc1 MDL-86805 courseindex: Fix RTL keyboard navigation in tree 2025-12-06 01:09:20 +07:00
Mark Johnson 7ca1574c28 MDL-87412 qbank_managecategories: Fix "new child" drop target
This fixes the CSS rule that reveals the "as new child of" drop target
shown when dragging a category over another category which has no
children.

We can't behat test the actual drag-drop reordering behaviour with behat
as reactive DragDrop components aren't testable, but we can trigger the
CSS to add a regression test for the drop target's appearance.
2025-12-05 14:11:06 +00:00
Santosh Nagargoje bae9d77604 MDL-87379 theme_classic: Fixed mobile responsiveness issue 2025-12-05 15:31:36 +05:30
Muhammad Arnaldo 91bdc56a5d MDL-86808 theme_boost: misaligned footer popover in RTL
Limit the fallback placement vertically.
2025-12-05 16:27:54 +07:00
Andi Permana 00cd643c33 MDL-86679 core: Fix MS field mappings in upgrade script 2025-12-05 15:44:10 +07:00
Andi Permana 73118e777f MDL-86679 oauth2: Support MS personal and work account names 2025-12-05 15:42:33 +07:00
Eric Merrill eb50526c35 MDL-87406 customfield: Use recordset for processing upgrade 2025-12-04 23:59:52 -05:00
Santosh Nagargoje 8e952904e8 MDL-87121 quiz: Fixed random question performance issue 2025-12-04 17:26:22 +05:30
James C 0df4a0d4ce MDL-87356 qtype_random: orphaned random questions should be deleted
This patch reinstates the task from MDL-63260 and MDL-66273.
2025-12-04 23:01:50 +13:00
Christian Abila 56e76851cc MDL-86593 Course completion: Disabled modules not returned as criteria
- Filter out disabled modules
- Update get_criteria method signature
- Fix code style
2025-12-04 07:04:11 +01:00
Paul Holden 6fd62e9bfc MDL-87323 report_outline: fix report viewed event URL. 2025-11-27 17:28:43 +00:00
Paul Holden 8d07409ff8 MDL-86988 mod_resource: ensure required library always present.
The `resourcelib` library is explicitly required by the modules
own local library, and neither the add/update methods use it
directly.
2025-11-26 15:10:59 +00:00
Paul Holden 7c6aaf9d4b MDL-87188 gradereport_grader: prevent overlapping collapsed columns. 2025-11-26 11:40:27 +00:00
Paul Holden e06a6ee9c8 MDL-86565 courseformat: don't link to sections user cannot see. 2025-11-18 10:31:58 +00:00
Alexander Bias 92a8d0e9c0 MDL-87199 core: In public/config.php, use require_once() 2025-11-17 07:44:03 +01:00
Paul Holden 26a16e4e27 MDL-87056 enrol: update instance modified date when toggling status. 2025-11-11 12:36:18 +00:00
Stefan Hanauska 824fe6ec83 MDL-68062 badges: Allow revoke by every user with the correct role
Co-authored-by: Rajneel Totaram <rjnlfj@yahoo.com>
2025-11-04 14:12:35 +01:00
682 changed files with 14518 additions and 2646 deletions
+1 -1
View File
@@ -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"
+21 -20
View File
@@ -56,6 +56,14 @@ jobs:
image: redis
ports:
- 6379:6379
postgres:
image: ${{ matrix.db == 'pgsql' && 'postgres:15' || '' }}
env:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
strategy:
fail-fast: false
matrix:
@@ -71,27 +79,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.4
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: 15
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.4 \
--skip-log-bin \
--collation-server=utf8mb4_bin
- name: Configuring git vars
uses: rlespinasse/github-slug-action@v4
+1 -1
View File
@@ -89,7 +89,7 @@ const templates = {
module.exports = {
opts: {
destination: "./jsdoc/",
destination: "./public/jsdoc/",
template: "node_modules/docdash",
},
plugins,
+2 -2
View File
@@ -18,7 +18,7 @@ import inquirer from 'inquirer';
import chalk from 'chalk';
import { createNote } from './note.mjs';
import { getInitialValues } from './helpers.mjs';
import { getInitialValues, getIssueNumberFromBranchName } from './helpers.mjs';
import * as Prompts from './prompts.mjs';
import logger from './logger.mjs';
@@ -29,7 +29,7 @@ export default async (options) => {
// Fetch information.
const messages = [];
const { issueNumber } = await inquirer.prompt([
Prompts.getIssuePrompt(),
Prompts.getIssuePrompt(getIssueNumberFromBranchName()),
], initialValues);
let selection = {};
+14
View File
@@ -14,6 +14,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
import chalk from 'chalk';
import branchName from 'current-git-branch';
import { isStandardComponent, isCommunityComponent, rewritePlugintypeAsSubsystem } from './components.mjs';
import { isValidNoteName } from './noteTypes.mjs';
import logger from './logger.mjs';
@@ -59,6 +60,19 @@ export const formatIssueNumber = (input) => {
return input;
};
/**
* Get the issue number, based off current git branch name
*
* @returns {string|null}
*/
export const getIssueNumberFromBranchName = () => {
const issueNumber = String(branchName()).match(/^[a-zA-Z]*-\d+/);
if (issueNumber) {
return formatIssueNumber(issueNumber[0]);
}
return null;
};
/**
* Validate a component.
*
+2 -2
View File
@@ -43,8 +43,8 @@ module.exports = (grunt) => {
const componentData = fetchComponentData().components;
for (const [thisPath, component] of Object.entries(componentData)) {
jsconfigData.compilerOptions.paths[`${component}/*`] = [`public/${thisPath}/amd/src/*`];
jsconfigData.include.push(`public/${thisPath}/amd/src/**/*`);
jsconfigData.compilerOptions.paths[`${component}/*`] = [`${thisPath}/amd/src/*`];
jsconfigData.include.push(`${thisPath}/amd/src/**/*`);
}
grunt.file.write('jsconfig.json', JSON.stringify(jsconfigData, null, " ") + "\n");
+5 -1
View File
@@ -49,6 +49,10 @@ module.exports = grunt => {
// Spawn the task in a child process so that it doesn't kill this one
// if it failed.
var args = [task, filesOption];
if (grunt.option('force')) {
args.push('--force');
}
grunt.util.spawn(
{
// Spawn with the grunt bin.
@@ -58,7 +62,7 @@ module.exports = grunt => {
cwd: grunt.moodleEnv.fullRunDir,
stdio: 'inherit'
},
args: [task, filesOption]
args: args
},
function(err, res, code) {
if (code !== 0) {
@@ -0,0 +1,7 @@
issueNumber: MDL-51152
notes:
enrol_manual:
- message: >-
The unused parameter 'roleid' has been removed from the external
function `unenrol_users()`
type: removed
@@ -0,0 +1,8 @@
issueNumber: MDL-75067
notes:
tool_behat:
- message: >-
The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods
now accept optional `$container` parameter to define the parent node to
look within
type: improved
@@ -0,0 +1,8 @@
issueNumber: MDL-85322
notes:
block_html:
- message: >-
Treat Dashboard (pagetype 'my-index') as trusted in web services so
get_content_for_external preserves embedded HTML (e.g. iframes) on user
Dashboard.
type: changed
@@ -0,0 +1,12 @@
issueNumber: MDL-86524
notes:
core_question:
- message: >-
During restore of a question_set_reference, mapping of IDs in the
filtercondition is now delegated to qbank plugins.
If your qbank plugin defines a filter condition that uses database
IDs, add an override of `restore_filtercondition()` to the `condition`
class, which checks the condition's data and replaces the IDs with
mapped values if required. See
`qbank_managecategories\category_condition` for an example.
type: improved
@@ -0,0 +1,10 @@
issueNumber: MDL-86839
notes:
core:
- message: >
"grunt watch" now accepts a force flag. Run "grunt watch -f" or "grunt
watch --force" to prevent grunt from cancelling builds when errors
occur. This is especially useful during development, because js build
files will be built even if, for example, jslint errors are still
present in the files.
type: improved
@@ -0,0 +1,7 @@
issueNumber: MDL-87100
notes:
core:
- message: >-
When creating upgrade notes, the issue number will be inferred from the
current Git branch name by default
type: improved
@@ -0,0 +1,7 @@
issueNumber: MDL-87795
notes:
core_user:
- message: >-
Added new optional parameter `userid` to the `user_remove_user_device`
function.
type: improved
@@ -0,0 +1,8 @@
issueNumber: MDL-87993
notes:
core:
- message: >-
The `core/toast` JS module now accepts a `visuallyHidden` configuration
parameter to render visually hidden toast messages for screen reader
users.
type: improved
+1 -1
View File
@@ -32,7 +32,7 @@ Moodle also offers hosting through both [MoodleCloud][11], and our [partner netw
## License
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. For more information on our license see
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. See our [license page][12] for more information.
[1]: https://moodle.org
[2]: https://moodle.com
+101
View File
@@ -6,6 +6,107 @@ 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).
## 5.1.3+
### core
#### Added
- "grunt watch" now accepts a force flag. Run "grunt watch -f" or "grunt watch --force" to prevent grunt from cancelling builds when errors occur. This is especially useful during development, because js build files will be built even if, for example, jslint errors are still present in the files.
For more information see [MDL-86839](https://tracker.moodle.org/browse/MDL-86839)
- When creating upgrade notes, the issue number will be inferred from the current Git branch name by default
For more information see [MDL-87100](https://tracker.moodle.org/browse/MDL-87100)
- The `core/toast` JS module now accepts a `visuallyHidden` configuration parameter to render visually hidden toast messages for screen reader users.
For more information see [MDL-87993](https://tracker.moodle.org/browse/MDL-87993)
### core_question
#### Added
- During restore of a question_set_reference, mapping of IDs in the filtercondition is now delegated to qbank plugins. If your qbank plugin defines a filter condition that uses database IDs, add an override of `restore_filtercondition()` to the `condition` class, which checks the condition's data and replaces the IDs with mapped values if required. See `qbank_managecategories\category_condition` for an example.
For more information see [MDL-86524](https://tracker.moodle.org/browse/MDL-86524)
### core_user
#### Added
- Added new optional parameter `userid` to the `user_remove_user_device` function.
For more information see [MDL-87795](https://tracker.moodle.org/browse/MDL-87795)
### block_html
#### Changed
- Treat Dashboard (pagetype 'my-index') as trusted in web services so get_content_for_external preserves embedded HTML (e.g. iframes) on user Dashboard.
For more information see [MDL-85322](https://tracker.moodle.org/browse/MDL-85322)
### enrol_manual
#### Removed
- The unused parameter 'roleid' has been removed from the external function `unenrol_users()`
For more information see [MDL-51152](https://tracker.moodle.org/browse/MDL-51152)
### tool_behat
#### Added
- The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods now accept optional `$container` parameter to define the parent node to look within
For more information see [MDL-75067](https://tracker.moodle.org/browse/MDL-75067)
## 5.1.2
### core
#### Added
- There is a new Behat `toast_message` named selector to more easily assert the presence of Toast messages on the page
For more information see [MDL-87443](https://tracker.moodle.org/browse/MDL-87443)
#### 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)
### core_customfield
#### Added
- Added new `\core_customfield\api::is_shortname_unique(...)` method to determine whether a shortname is available for use inside a given handler
For more information see [MDL-87059](https://tracker.moodle.org/browse/MDL-87059)
### core_question
#### Fixed
- In order to prevent re-use of question version numbers after a version is deleted, the `nextversion` column was added to `question_bank_entries`. This serves as a counter incremented each time a version is created.
Do not query this field directly. Instead use `core_question\versions::get_next_version()` to read the value, which will initialise it based on the existing versions if it is not set yet. By default, it will increment the version number automatically, unless you pass `increment: false`. Because of this, it is advisable to call it inside a transaction, that is only committed after the version number is used in a `question_versions` record.
For more information see [MDL-86798](https://tracker.moodle.org/browse/MDL-86798)
### customfield_number
#### Changed
- In order to fully support shared custom field categories, additional component/area/itemid parameters have been added to the following:
* The `customfield_number_recalculate_value` external method
* The abstract `\customfield_number\provider_base::recalculate()` method
* The `\customfield_number\task\recalculate` helpers for queueing task instances
For more information see [MDL-87714](https://tracker.moodle.org/browse/MDL-87714)
## 5.1.1
### core
+5 -5
View File
@@ -125,11 +125,6 @@ if ($options['list']) {
exit(0);
}
if (moodle_needs_upgrading()) {
mtrace("Moodle upgrade pending, cannot manage tasks.");
exit(1);
}
if ($disable = $options['disable']) {
if (!$task = \core\task\manager::get_scheduled_task($disable)) {
mtrace("Task '$disable' not found");
@@ -157,6 +152,11 @@ if ($disable = $options['disable']) {
exit(1);
}
} else if ($execute = $options['execute']) {
if (moodle_needs_upgrading()) {
mtrace("Moodle upgrade pending, cannot execute tasks.");
exit(1);
}
if (!$task = \core\task\manager::get_scheduled_task($execute)) {
mtrace("Task '$execute' not found");
exit(1);
+7 -1
View File
@@ -2,8 +2,11 @@
"name": "moodle/moodle",
"license": "GPL-3.0-or-later",
"description": "Moodle - the world's open source learning platform",
"type": "project",
"type": "moodle-core",
"homepage": "https://moodle.org",
"provide": {
"moodle/lms": "5.1"
},
"require-dev": {
"phpunit/phpunit": "^11",
"mikey179/vfsstream": "1.6.*",
@@ -26,6 +29,9 @@
"core_phpunit\\": "public/lib/phpunit/classes/"
}
},
"extra": {
"haspublicdir": true
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
Generated
+681 -432
View File
File diff suppressed because it is too large Load Diff
+325
View File
@@ -19,6 +19,7 @@
"chalk": "^5.3.0",
"commander": "^12.0.0",
"cross-env": "^7.0.3",
"current-git-branch": "^2.0.2",
"docdash": "^2.0.2",
"eslint": "^8.56.0",
"eslint-plugin-jsdoc": "^48.0.4",
@@ -2261,6 +2262,13 @@
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=",
"dev": true
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
"integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
"dev": true,
"license": "MIT"
},
"node_modules/@sindresorhus/is": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
@@ -2270,6 +2278,19 @@
"node": ">=4"
}
},
"node_modules/@sindresorhus/merge-streams": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
"integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@types/linkify-it": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz",
@@ -4134,6 +4155,16 @@
"uuid": "^3.3.3"
}
},
"node_modules/current-git-branch": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/current-git-branch/-/current-git-branch-2.0.2.tgz",
"integrity": "sha512-uf6Yvp3WGEGlJbsoseXIun9pvfUyts3nbHm6bn6/JBHiBgDbVifd/0dsJXLCo6ndRJGBlLxXhK+YNPBVouFdyA==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-git-repository": "^3.0.0"
}
},
"node_modules/date-now": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
@@ -7386,6 +7417,16 @@
"node": "*"
}
},
"node_modules/human-signals": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
"integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -7975,6 +8016,222 @@
"node": ">=8"
}
},
"node_modules/is-git-repository": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-git-repository/-/is-git-repository-3.0.0.tgz",
"integrity": "sha512-iCIObmkwkU6b15KB7rltAb60rIRXN4qAjVmBkqry8EWdwcJ9B7KUDCYrLIV2HOHENHYy9bm/MsDHDisoAZrpUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"execa": "^9.5.2"
}
},
"node_modules/is-git-repository/node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/is-git-repository/node_modules/execa": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz",
"integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sindresorhus/merge-streams": "^4.0.0",
"cross-spawn": "^7.0.6",
"figures": "^6.1.0",
"get-stream": "^9.0.0",
"human-signals": "^8.0.1",
"is-plain-obj": "^4.1.0",
"is-stream": "^4.0.1",
"npm-run-path": "^6.0.0",
"pretty-ms": "^9.2.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^4.0.0",
"yoctocolors": "^2.1.1"
},
"engines": {
"node": "^18.19.0 || >=20.5.0"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/is-git-repository/node_modules/figures": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
"integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-unicode-supported": "^2.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-git-repository/node_modules/get-stream": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
"integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sec-ant/readable-stream": "^0.4.1",
"is-stream": "^4.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-git-repository/node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-git-repository/node_modules/is-stream": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-git-repository/node_modules/is-unicode-supported": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-git-repository/node_modules/npm-run-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
"integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^4.0.0",
"unicorn-magic": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-git-repository/node_modules/npm-run-path/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-git-repository/node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-git-repository/node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-git-repository/node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-git-repository/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/is-git-repository/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -10283,6 +10540,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parse-ms": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
"integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parse-passwd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
@@ -10606,6 +10876,22 @@
"node": ">=4"
}
},
"node_modules/pretty-ms": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
"integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"parse-ms": "^4.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -12059,6 +12345,19 @@
"node": ">=0.10.0"
}
},
"node_modules/strip-final-newline": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
"integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-indent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz",
@@ -12737,6 +13036,19 @@
"node": ">=4"
}
},
"node_modules/unicorn-magic": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
"integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
@@ -13251,6 +13563,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yoctocolors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
"integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yuglify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/yuglify/-/yuglify-2.0.0.tgz",
+1
View File
@@ -19,6 +19,7 @@
"chalk": "^5.3.0",
"commander": "^12.0.0",
"cross-env": "^7.0.3",
"current-git-branch": "^2.0.2",
"docdash": "^2.0.2",
"eslint": "^8.56.0",
"eslint-plugin-jsdoc": "^48.0.4",
+2 -2
View File
@@ -119,8 +119,8 @@ foreach ($settingspage->children as $childpage) {
$outputhtml .= html_writer::tag('div', '<!-- -->', array('class' => 'clearer'));
$outputhtml .= $setting->output_html($data);
if ($childpage->has_dependencies()) {
$opts = ['dependencies' => $childpage->get_dependencies_for_javascript()];
$PAGE->requires->js_call_amd('core/showhidesettings', 'init', [$opts]);
$context = ['dependencies' => json_encode($childpage->get_dependencies_for_javascript())];
echo $OUTPUT->render_from_template('core_admin/settings_showhide', $context);
}
}
$outputhtml .= html_writer::end_tag('fieldset');
@@ -0,0 +1,74 @@
<?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/>.
namespace core_admin\admin;
use admin_setting;
/**
* Render a notification as part of other admin settings.
*
* @package core_admin
* @subpackage admin
* @copyright 2025 Matt Porritt <matt.porritt@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_notification extends admin_setting {
/**
* Constructor.
*
* @param string $name The name of the setting.
* @param string $notification The notification to display.
* @param string $type The type of notification.
* @param bool $cancelable Whether the notification can be canceled.
*/
public function __construct(
string $name,
/** @var string The notification to display. */
protected string $notification,
/** @var string The type of notification. */
protected string $type = 'info',
/** @var bool Whether the notification can be canceled. */
protected bool $cancelable = false
) {
$this->nosave = true;
parent::__construct($name, '', '', '');
}
#[\Override]
public function get_setting(): bool {
return true;
}
#[\Override]
public function get_defaultsetting(): bool {
return true;
}
#[\Override]
public function write_setting($data): string {
// Do not write any setting.
return '';
}
#[\Override]
public function output_html($data, $query = ''): string {
global $OUTPUT;
return $OUTPUT->notification($this->notification, $this->type, $this->cancelable);
}
}
+53 -7
View File
@@ -282,13 +282,30 @@ class core_admin_renderer extends plugin_renderer_base {
*
* @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 = '';
@@ -313,6 +330,8 @@ class core_admin_renderer extends plugin_renderer_base {
$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->marketplace_integration_notice();
$output .= $this->userfeedback_encouragement($showfeedbackencouragement);
$output .= $this->services_and_support_content($showservicesandsupport);
$output .= $this->campaign_content($showcampaigncontent);
@@ -2326,6 +2345,33 @@ class core_admin_renderer extends plugin_renderer_base {
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 '';
}
/**
* Display a notice about Moodle Marketplace integration.
*
* @return string HTML to output.
*/
protected function marketplace_integration_notice(): string {
$installer = tool_installaddon_installer::instance();
$url = $installer->get_marketplace_url();
$notice = get_string('marketplaceavailablenotice', 'admin', $url->out());
return $this->warning($notice, 'info');
}
/**
* Renders the theme selector list.
*
+2 -3
View File
@@ -32,7 +32,6 @@ $adminroot = admin_get_root(); // need all settings here
$adminroot->search = $query; // So we can reference it in search boxes later in this invocation
$statusmsg = '';
$errormsg = '';
$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') {
@@ -41,7 +40,7 @@ if ($data = data_submitted() and confirm_sesskey() and isset($data->action) and
if (!empty($adminroot->errors)) {
$errormsg = get_string('errorwithsettings', 'admin');
$firsterror = reset($adminroot->errors);
$focus = $firsterror->id;
$PAGE->set_focuscontrol($firsterror->id);
} else {
// No errors. Did we change any setting? If so, then redirect with success.
if ($count) {
@@ -55,7 +54,7 @@ $PAGE->set_primary_active_tab('siteadminnode');
// and finally, if we get here, then there are matching settings and we have to print a form
// to modify them
echo $OUTPUT->header($focus);
echo $OUTPUT->header();
// Display a warning if site is not registered.
if (empty($query)) {
+3 -4
View File
@@ -72,6 +72,7 @@ if ($data = data_submitted() and confirm_sesskey() and isset($data->action) and
} else {
$errormsg = get_string('errorwithsettings', 'admin');
$firsterror = reset($adminroot->errors);
$PAGE->set_focuscontrol($firsterror->id);
}
$settingspage = $adminroot->locate($section, true);
}
@@ -165,10 +166,8 @@ if (empty($SITE->fullname)) {
$PAGE->requires->js_call_amd('core_form/changechecker', 'watchFormById', ['adminsettings']);
if ($settingspage->has_dependencies()) {
$opts = [
'dependencies' => $settingspage->get_dependencies_for_javascript()
];
$PAGE->requires->js_call_amd('core/showhidesettings', 'init', [$opts]);
$context = ['dependencies' => json_encode($settingspage->get_dependencies_for_javascript())];
echo $OUTPUT->render_from_template('core_admin/settings_showhide', $context);
}
echo $OUTPUT->footer();
+18 -8
View File
@@ -32,19 +32,29 @@ if ($hassiteconfig) {
$providers->add(new admin_setting_heading('availableproviders',
get_string('availableproviders', 'core_ai'),
get_string('availableproviders_desc', 'core_ai')));
// Add call to action to add a new provider.
$providers->add(new \core_admin\admin\admin_setting_template_render(
name: 'addnewprovider',
templatename: 'core_ai/admin_add_provider',
context: ['addnewproviderurl' => new moodle_url('/ai/configure.php')]
));
$providers->add(new \core_ai\admin\admin_setting_provider_manager(
if (!empty(core_plugin_manager::instance()->get_plugins_of_type("aiprovider"))) {
// Add call to action to add a new provider.
$providers->add(new \core_admin\admin\admin_setting_template_render(
name: 'addnewprovider',
templatename: 'core_ai/admin_add_provider',
context: ['addnewproviderurl' => new moodle_url('/ai/configure.php')]
));
$providers->add(new \core_ai\admin\admin_setting_provider_manager(
'aiprovider',
\core_ai\table\aiprovider_management_table::class,
'manageaiproviders',
new lang_string('manageaiproviders', 'core_ai'),
));
));
} else {
$providers->add(new \core_admin\admin\admin_setting_notification(
name:'noproviderplugins',
notification: get_string('noproviderplugins', 'core_ai'),
type: 'danger'
));
}
$ADMIN->add('ai', $providers);
// Add settings page for AI placement settings.
@@ -0,0 +1,51 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_admin/settings_showhide
Passes the necessary data to show/hide dependant settings.
Context variables required for this template:
* dependencies - JSON-serialized data structure describing the dependencies between settings
Example context (json):
{
"dependencies": {
"s__setting1":{
"eq":[
[
"s__setting0"
]
]
},
"s__setting2":{
"eq":[
[
"s__setting1"
]
]
}
}
}
}}
<span id="settings-showhide-{{uniqid}}" data-dependencies="{{dependencies}}"></span>
{{#js}}
require(['core/showhidesettings'], function(ShowHideSettings) {
ShowHideSettings.init("settings-showhide-{{uniqid}}");
});
{{/js}}
+8
View File
@@ -1,5 +1,13 @@
# tool_behat Upgrade notes
## 5.1.3+
### Added
- The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods now accept optional `$container` parameter to define the parent node to look within
For more information see [MDL-75067](https://tracker.moodle.org/browse/MDL-75067)
## 5.0
### Added
+5
View File
@@ -224,6 +224,11 @@ if (empty($parallelrun)) {
$cmds['singlerun'] = $runtestscommand;
echo "Running single behat site:" . PHP_EOL;
// The inner PHP process is not marked as having an interactive terminal even if it's passed
// through from this one which does, so we need to pass it through as an environment variable.
if (function_exists('posix_isatty') && posix_isatty(STDOUT)) {
putenv('MOODLE_BEHAT_RUNNING_IN_TTY=1');
}
passthru("php $runtestscommand", $status);
$exitcodes['singlerun'] = $status;
chdir($cwd);
@@ -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 |
@@ -0,0 +1,3 @@
define("tool_installaddon/footer",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.footerClickListener=void 0;_exports.footerClickListener=()=>null}));
//# sourceMappingURL=footer.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"footer.min.js","sources":["../src/footer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Activity chooser footer handlers for tool_installaddon.\n *\n * @module tool_installaddon/footer\n * @copyright 2026 Safat Shahin <safat.shahin@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// The footer is a plain link, so the chooser does not need extra click handling.\nexport const footerClickListener = () => null;\n"],"names":[],"mappings":"2LAwBmC,IAAM"}
@@ -0,0 +1,25 @@
// 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/>.
/**
* Activity chooser footer handlers for tool_installaddon.
*
* @module tool_installaddon/footer
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// The footer is a plain link, so the chooser does not need extra click handling.
export const footerClickListener = () => null;
@@ -70,6 +70,25 @@ class tool_installaddon_installer {
$url = 'https://moodle.org/plugins/get.php';
}
return $this->get_external_service_url($url);
}
/**
* Returns URL to Moodle Marketplace.
*
* @return moodle_url
*/
public function get_marketplace_url(): moodle_url {
return $this->get_external_service_url('https://marketplace.moodle.com/');
}
/**
* Returns URL to external service with optional encoded site information.
*
* @param string $url The external service base URL.
* @return moodle_url
*/
protected function get_external_service_url(string $url): moodle_url {
if (!$this->should_send_site_info()) {
return new moodle_url($url);
}
@@ -16,20 +16,23 @@
declare(strict_types=1);
namespace tool_moodlenet\task;
namespace tool_installaddon\task;
/**
* Ad-hoc task to perform post install tasks.
* We use this to set the active activity chooser footer plugin to tool_moodlenet.
* We use this to set the active activity chooser footer plugin to tool_installaddon.
* We couldn't do this directly in install.php, because there is an admin_apply_default_settings() call after all plugins are
* installed and that would reset whatever value we had set earlier to 'hidden'.
*
* @package tool_moodlenet
* @copyright 2022 Shamim Rezaie <shamim@moodle.com>
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_install extends \core\task\adhoc_task {
public function execute() {
set_config('activitychooseractivefooter', 'tool_moodlenet');
/**
* Sets the active activity chooser footer plugin after install defaults are applied.
*/
public function execute(): void {
set_config('activitychooseractivefooter', 'tool_installaddon');
}
}
@@ -0,0 +1,37 @@
<?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/>.
/**
* Install script for tool_installaddon.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Perform the post-install procedures.
*/
function xmldb_tool_installaddon_install(): void {
// Use an ad-hoc task to set the active activity chooser footer plugin to tool_installaddon.
// We couldn't do this in admin/settings/courses.php for 2 reasons:
// - First, because it would be a breach of component communications principles to do so there.
// - Second, because we can't call get_plugins_with_function() during install and upgrade (or it will return []).
// We couldn't do this directly here either, because there is an admin_apply_default_settings() call after all plugins are
// installed and that would reset whatever value we set here to 'hidden'.
$postinstall = new tool_installaddon\task\post_install();
core\task\manager::queue_adhoc_task($postinstall);
}
@@ -0,0 +1,41 @@
<?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/>.
/**
* Upgrade script for tool_installaddon.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Upgrade the plugin.
*
* @param int $oldversion
* @return bool always true
*/
function xmldb_tool_installaddon_upgrade(int $oldversion): bool {
if ($oldversion < 2025100601) {
// Set the activity chooser active footer to include marketplace regardless of the previous setting.
// We are deliberately setting this to increase awareness of marketplace.
set_config('activitychooseractivefooter', 'tool_installaddon');
upgrade_plugin_savepoint(true, 2025100601, 'tool', 'installaddon');
}
return true;
}
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
$string['acknowledgement'] = 'Acknowledgement';
$string['acknowledgementtext'] = 'I understand that it is my responsibility to have full backups of this site prior to installing additional plugins. I accept and understand that plugins (especially but not only those originating in unofficial sources) may contain security holes, can make the site unavailable, or cause private data leaks or loss.';
$string['activitychooserfootertext'] = 'Browse more activities on';
$string['featuredisabled'] = 'The plugin installer is disabled on this site.';
$string['installaddon'] = 'Install plugin!';
$string['installaddons'] = 'Install plugins';
@@ -44,6 +45,8 @@ $string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
$string['installfromziptype'] = 'Plugin type';
$string['installfromziptype_help'] = 'For plugins that correctly declare their component name, the installer is able to detect the plugin type automatically. If the auto-detection fails, choose the correct type of plugin manually. Warning: The installation procedure can fail badly if an incorrect plugin type is specified.';
$string['installfromziptype_link'] = 'Development:Plugins';
$string['marketplaceadminlinktext'] = 'Browse new plugins';
$string['marketplacelink'] = 'Moodle Marketplace';
$string['permcheck'] = 'Make sure the plugin type root location is writable by the web server process.';
$string['permcheckerror'] = 'Error while checking for write permission';
$string['permcheckprogress'] = 'Checking for write permission ...';
+48
View File
@@ -0,0 +1,48 @@
<?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/>.
/**
* Public API for tool_installaddon.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_course\local\entity\activity_chooser_footer;
/**
* Build activity chooser footer content for Marketplace.
*
* @param int $courseid The selected course id.
* @param int $sectionid The selected section id.
* @return activity_chooser_footer
*/
function tool_installaddon_custom_chooser_footer(int $courseid, int $sectionid): activity_chooser_footer {
global $OUTPUT;
$installer = tool_installaddon_installer::instance();
$marketplaceurl = $installer->get_marketplace_url();
$renderedfooter = $OUTPUT->render_from_template('tool_installaddon/chooser_footer', [
'url' => $marketplaceurl->out(false),
]);
return new activity_chooser_footer(
'tool_installaddon/footer',
$renderedfooter
);
}
@@ -26,6 +26,12 @@
defined('MOODLE_INTERNAL') || die();
if ($hassiteconfig and empty($CFG->disableupdateautodeploy)) {
$installer = tool_installaddon_installer::instance();
$ADMIN->add('modules', new admin_externalpage(
'tool_installaddon_marketplace',
get_string('marketplaceadminlinktext', 'tool_installaddon'),
$installer->get_marketplace_url()->out(false)
), 'modsettings');
$ADMIN->add('modules', new admin_externalpage('tool_installaddon_index',
get_string('installaddons', 'tool_installaddon'),
@@ -0,0 +1,30 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_installaddon/chooser_footer
Activity chooser footer link to Marketplace.
Example context (json):
{
"url": "https://marketplace.moodle.com/?site=hash"
}
}}
<div class="w-100 d-flex px-2">
<span class="my-auto me-1">{{#str}} activitychooserfootertext, tool_installaddon {{/str}}</span>
{{> tool_installaddon/marketplace_link }}
</div>
@@ -0,0 +1,36 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_installaddon/marketplace_link
Reusable link to Moodle Marketplace.
Example context (json):
{
"url": "https://marketplace.moodle.com/?site=hash"
}
}}
<a class="d-inline my-auto" href="{{url}}" target="_blank" rel="noopener noreferrer">
{{#str}} marketplacelink, tool_installaddon {{/str}}
<i
class="fa fa-external-link"
role="img"
aria-label="{{#str}} opensinnewwindow, core {{/str}}"
title="{{#str}} opensinnewwindow, core {{/str}}"
>
</i>
</a>
@@ -0,0 +1,37 @@
@tool @tool_installaddon @javascript
Feature: Marketplace activity chooser footer
In order to browse plugins from the activity chooser
As a teacher
I need to see the Marketplace footer link when installaddon is the active footer plugin
Background:
Given the following config values are set as admin:
| activitychooseractivefooter | tool_installaddon |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | format |
| Course 1 | C1 | 0 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
Scenario: Activity chooser footer includes Marketplace link
Given I log in as "teacher1"
When I am on "Course 1" course homepage with editing mode on
And I open the activity chooser
Then "Browse more activities on" "text" should exist in the "Add an activity or resource" "dialogue"
And "Marketplace" "link" should exist in the "Add an activity or resource" "dialogue"
Scenario: Back button works from activity help when the Marketplace footer is active
Given I log in as "teacher1"
When I am on "Course 1" course homepage with editing mode on
And I open the activity chooser
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
Then "help" "core_course > Activity chooser screen" should be visible
When I click on "Back" "button" in the "Add an activity or resource" "dialogue"
Then "modules" "core_course > Activity chooser screen" should be visible
And "help" "core_course > Activity chooser screen" should not be visible
And "Marketplace" "link" should exist in the "Add an activity or resource" "dialogue"
And "Back" "button" should not be visible
@@ -0,0 +1,46 @@
<?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/>.
/**
* Provides a mock testable_tool_installaddon_installer_without_site_info class.
*
* @package tool_installaddon
* @subpackage fixtures
* @category test
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/testable_installer.php');
/**
* Testable subclass with site-info sharing disabled.
*
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_tool_installaddon_installer_without_site_info extends testable_tool_installaddon_installer {
/**
* Disable site info sharing.
*
* @return bool
*/
protected function should_send_site_info() {
return false;
}
}
@@ -18,12 +18,14 @@
namespace tool_installaddon;
use testable_tool_installaddon_installer;
use testable_tool_installaddon_installer_without_site_info;
use tool_installaddon_installer;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__.'/fixtures/testable_installer.php');
require_once(__DIR__ . '/fixtures/testable_installer.php');
require_once(__DIR__ . '/fixtures/testable_installer_without_site_info.php');
/**
* Unit tests for the {@link tool_installaddon_installer} class
@@ -32,16 +34,14 @@ require_once(__DIR__.'/fixtures/testable_installer.php');
* @category test
* @copyright 2013 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_installaddon_installer
*/
final class installer_test extends \advanced_testcase {
public function test_get_addons_repository_url(): void {
$installer = testable_tool_installaddon_installer::instance();
$url = $installer->get_addons_repository_url();
$query = parse_url($url, PHP_URL_QUERY);
$this->assertEquals(1, preg_match('~^site=(.+)$~', $query, $matches));
$site = rawurldecode($matches[1]);
$site = json_decode(base64_decode($site), true);
$site = $this->decode_site_info_from_url($url);
$this->assertIsArray($site);
$this->assertEquals(3, count($site));
$this->assertSame('Nasty site', $site['fullname']);
@@ -49,6 +49,34 @@ final class installer_test extends \advanced_testcase {
$this->assertSame("2.5'; DROP TABLE mdl_user; --", $site['majorversion']);
}
/**
* Tests that marketplace URL includes expected host, scheme and site payload.
*/
public function test_get_marketplace_url(): void {
$installer = testable_tool_installaddon_installer::instance();
$addonsurl = $installer->get_addons_repository_url();
$marketplaceurl = $installer->get_marketplace_url();
$this->assertSame('marketplace.moodle.com', parse_url($marketplaceurl, PHP_URL_HOST));
$this->assertSame('https', parse_url($marketplaceurl, PHP_URL_SCHEME));
$this->assertSame(
$this->decode_site_info_from_url($addonsurl),
$this->decode_site_info_from_url($marketplaceurl),
);
}
/**
* Tests that marketplace URL has no query string when site info is unavailable.
*/
public function test_get_marketplace_url_without_site_info(): void {
$installer = testable_tool_installaddon_installer_without_site_info::instance();
$marketplaceurl = $installer->get_marketplace_url();
$this->assertSame('marketplace.moodle.com', parse_url($marketplaceurl, PHP_URL_HOST));
$this->assertSame('https', parse_url($marketplaceurl, PHP_URL_SCHEME));
$this->assertEmpty(parse_url($marketplaceurl, PHP_URL_QUERY));
}
public function test_decode_remote_request(): void {
$installer = testable_tool_installaddon_installer::instance();
@@ -132,7 +160,7 @@ $plugin->version = 2014121300;
$versionphp = file_get_contents($fixtures.'/github/moodle-repository_mahara-master/version.php');
$this->assertEquals('repository_mahara', $installer->testable_detect_plugin_component_from_versionphp($versionphp));
$versionphp = file_get_contents($fixtures.'/nocomponent/baz/version.php');
$versionphp = file_get_contents($fixtures . '/nocomponent/baz/version.php');
$this->assertFalse($installer->testable_detect_plugin_component_from_versionphp($versionphp));
}
@@ -143,15 +171,28 @@ $plugin->version = 2014121300;
$storage1 = $installer->make_installfromzip_storage();
$this->assertTrue(is_dir($storage1));
$this->assertTrue(is_writable($storage1));
file_put_contents($storage1.'/hello.txt', 'Find me if you can!');
file_put_contents($storage1 . '/hello.txt', 'Find me if you can!');
// Check we get unique directory on each call.
$storage2 = $installer->make_installfromzip_storage();
$this->assertTrue(is_dir($storage2));
$this->assertTrue(is_writable($storage2));
$this->assertFalse(file_exists($storage2.'/hello.txt'));
$this->assertFalse(file_exists($storage2 . '/hello.txt'));
// Check both are in the same parent directory.
$this->assertEquals(dirname($storage1), dirname($storage2));
}
/**
* Decodes the encoded 'site' URL query parameter.
*
* @param moodle_url $url
* @return array
*/
private function decode_site_info_from_url(\moodle_url $url): array {
$query = parse_url($url, PHP_URL_QUERY);
$this->assertEquals(1, preg_match('~^site=(.+)$~', $query, $matches));
$site = rawurldecode($matches[1]);
return json_decode(base64_decode($site), true);
}
}
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for tool_installaddon lib.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_installaddon;
use core_course\local\entity\activity_chooser_footer;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/admin/tool/installaddon/lib.php');
/**
* Test installaddon lib functions.
*/
final class lib_test extends \advanced_testcase {
/**
* Tests chooser footer generation for marketplace link content.
*
* @covers ::tool_installaddon_custom_chooser_footer
*/
public function test_tool_installaddon_custom_chooser_footer(): void {
$this->resetAfterTest();
$footer = \tool_installaddon_custom_chooser_footer(1, 1);
$this->assertInstanceOf(activity_chooser_footer::class, $footer);
$this->assertSame('tool_installaddon/footer', $footer->get_footer_js_file());
$this->assertStringContainsString('https://marketplace.moodle.com/', $footer->get_footer_template());
$this->assertStringContainsString('site=', $footer->get_footer_template());
}
}
@@ -0,0 +1,47 @@
<?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/>.
declare(strict_types=1);
namespace tool_installaddon\task;
/**
* Unit tests for the post install task.
*
* @package tool_installaddon
* @category test
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_installaddon\task\post_install
*/
final class post_install_test extends \advanced_testcase {
/**
* Tests that the task sets the activity chooser footer plugin to tool_installaddon.
*/
public function test_execute_sets_activitychooseractivefooter_config(): void {
$this->resetAfterTest();
set_config('activitychooseractivefooter', 'hidden');
$task = new post_install();
$task->execute();
$this->assertSame(
'tool_installaddon',
get_config('core', 'activitychooseractivefooter'),
);
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'tool_installaddon';
$plugin->version = 2025100600;
$plugin->version = 2025100601;
$plugin->requires = 2025092600;
$plugin->maturity = MATURITY_STABLE;
+7 -2
View File
@@ -598,6 +598,10 @@ class manager {
$urls = [
new \moodle_url('/login/logout.php'),
new \moodle_url('/admin/tool/mfa/guide.php'),
// Allow email self-registration confirmation to complete so that
// auth_email can restore wantsurl from the auth_email_wantsurl user
// preference before MFA intercepts on the next request.
new \moodle_url('/login/confirm.php'),
];
foreach ($factors as $factor) {
$urls = array_merge($urls, $factor->get_no_redirect_urls());
@@ -605,8 +609,9 @@ class manager {
// Allow forced redirection exclusions.
if ($exclusions = get_config('tool_mfa', 'redir_exclusions')) {
foreach (explode("\n", $exclusions) as $exclusion) {
$urls[] = new \moodle_url($exclusion);
$exclusions = preg_split('/\n|\r/', $exclusions, -1, PREG_SPLIT_NO_EMPTY);
foreach ($exclusions as $exclusion) {
$urls[] = new \moodle_url(trim($exclusion));
}
}
+5
View File
@@ -38,3 +38,8 @@ input.tool-mfa-verification-code,
.mfa-verify-form .form-group.fitem > span > input {
width: 100%;
}
#mfalist .icon-circle .icon {
width: 64px;
height: 64px;
}
+48 -21
View File
@@ -20,6 +20,7 @@ namespace tool_mfa;
* Tests for MFA manager class.
*
* @package tool_mfa
* @covers \tool_mfa\manager
* @author Peter Burnett <peterburnett@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -30,9 +31,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests getting the factor total weight
*
* @covers ::get_total_weight
* @covers ::setup_user_factor
*/
public function test_get_total_weight(): void {
$this->resetAfterTest(true);
@@ -76,8 +74,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests getting the factor status
*
* @covers ::get_status
*/
public function test_get_status(): void {
$this->resetAfterTest(true);
@@ -117,8 +113,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests checking if passed enough factors
*
* @covers ::passed_enough_factors
*/
public function test_passed_enough_factors(): void {
$this->resetAfterTest(true);
@@ -151,6 +145,30 @@ final class manager_test extends \advanced_testcase {
$this->assertEquals(true, \tool_mfa\manager::passed_enough_factors());
}
/**
* Test reading no redirect URLs configuration
*/
public function test_get_no_redirect_urls(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Ensure all EOL markers are covered.
$config = "/user/profile.php\r" .
"/course/index.php\n" .
"/admin/settings.php\r\n" .
"/mod/bar/view.php ";
set_config('redir_exclusions', $config, 'tool_mfa');
// Cast URLs to string for simpler comparison.
$noredirecturls = array_map(fn(\moodle_url $url) => (string) $url, manager::get_no_redirect_urls());
$this->assertContains((string) (new \moodle_url('/user/profile.php')), $noredirecturls);
$this->assertContains((string) (new \moodle_url('/course/index.php')), $noredirecturls);
$this->assertContains((string) (new \moodle_url('/admin/settings.php')), $noredirecturls);
$this->assertContains((string) (new \moodle_url('/mod/bar/view.php')), $noredirecturls);
}
/**
* The data provider for whether urls should be redirected or not
*
@@ -178,7 +196,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests whether it should require mfa
*
* @covers ::should_require_mfa
* @param string $urlstring
* @param string $webroot
* @param bool $status
@@ -197,8 +214,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests whether it should require the mfa checks
*
* @covers ::should_require_mfa
*/
public function test_should_require_mfa_checks(): void {
// Setup test and user.
@@ -285,8 +300,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests should require the mfa redirection loop
*
* @covers ::should_require_mfa
*/
public function test_should_require_mfa_redirection_loop(): void {
// Setup test and user.
@@ -341,9 +354,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests checking for possible setup factor
*
* @covers ::possible_factor_setup
* @covers ::setup_user_factor
*/
public function test_possible_factor_setup(): void {
// Setup test and user.
@@ -377,8 +387,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests checking if a factor is ready
*
* @covers ::is_ready
*/
public function test_is_ready(): void {
// Setup test and user.
@@ -419,9 +427,6 @@ final class manager_test extends \advanced_testcase {
/**
* Tests core hooks
*
* @covers ::mfa_config_hook_test
* @covers ::mfa_login_hook_test
*/
public function test_core_hooks(): void {
// Setup test and user.
@@ -438,9 +443,31 @@ final class manager_test extends \advanced_testcase {
}
/**
* Tests circular redirect auth
* Tests that /login/confirm.php is excluded from MFA redirection.
*
* When a user follows an email self-registration confirmation link, MFA must
* not intercept the request before auth_email::user_confirm() has had a chance
* to restore the wantsurl from the auth_email_wantsurl user preference.
*
* @covers ::should_require_mfa
* @covers ::get_no_redirect_urls
*/
public function test_confirm_url_no_redirect(): void {
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$confirmurl = new \moodle_url('/login/confirm.php');
$this->assertEquals(
\tool_mfa\manager::NO_REDIRECT,
\tool_mfa\manager::should_require_mfa($confirmurl, false),
'/login/confirm.php must not trigger an MFA redirect so that auth_email can ' .
'restore wantsurl from the auth_email_wantsurl user preference first.'
);
}
/**
* Tests circular redirect auth
*/
public function test_circular_redirect_auth(): void {
// Setup test and user.
+1 -8
View File
@@ -28,12 +28,5 @@ declare(strict_types=1);
* Perform the post-install procedures.
*/
function xmldb_tool_moodlenet_install() {
// Use an ad-hoc task to set the active activity chooser footer plugin to tool_moodlenet.
// We couldn't do this in admin/settings/courses.php for 2 reasons:
// - First, because it would be a breach of component communications principles to do so there.
// - Second, because we can't call get_plugins_with_function() during install and upgrade (or it will return []).
// We couldn't do this directly here either, because there is an admin_apply_default_settings() call after all plugins are
// installed and that would reset whatever value we set here to 'hidden'.
$postinstall = new tool_moodlenet\task\post_install();
core\task\manager::queue_adhoc_task($postinstall);
}
@@ -38,6 +38,9 @@ $string['defaultmoodlenet_desc'] = 'The URL of the MoodleNet instance available
$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}';
@@ -27,8 +27,16 @@
<div class="content text-start mb-5 px-5 py-4" data-region="chooser-option-summary-content-container">
<div data-region="moodle-net">
<div class="overlay-icon-container z-index-1 d-none" data-region="spinner"></div>
<img class="w-25 mb-4" aria-hidden="true" src="{{{img}}}">
<img class="w-25 mb-4" alt="" aria-hidden="true" src="{{{img}}}">
<p>{{#str}} instancedescription, tool_moodlenet {{/str}}</p>
{{! Removal warning - always visible when MoodleNet integration is enabled }}
<div class="alert alert-warning w-75 mx-auto mb-3" role="alert">
<strong>{{#str}}removalwarning_title, tool_moodlenet{{/str}}</strong>
<p class="mb-1">{{#str}}removalwarning_service, tool_moodlenet{{/str}}</p>
<p class="mb-0">{{#str}}removalwarning_feature, tool_moodlenet{{/str}}</p>
</div>
<p class="w-75 mx-auto mb-1 mt-5">{{#str}} connectandbrowse, tool_moodlenet {{/str}}</p>
<a class="btn btn-secondary d-block w-75 mx-auto mb-4"
data-action="browse"
@@ -61,7 +61,8 @@ Feature: Basic OAuth2 functionality
And I should see "device_authorization_endpoint"
And I navigate to "Server > OAuth 2 services" in site administration
And I click on "Configure user field mappings" "link" in the "Testing service" "table_row"
And I should see "firstname" in the "givenname" "table_row"
And I should see "firstname" in the "given_name" "table_row"
And I should see "lastname" in the "family_name" "table_row"
And I should see "idnumber" in the "sub" "table_row"
And I should see "email" in the "email" "table_row"
And I should see "lang" in the "locale" "table_row"
+4 -5
View File
@@ -49,8 +49,10 @@ function xmldb_tool_recyclebin_upgrade($oldversion) {
// Automatically generated Moodle v5.0.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2025041401) {
// Automatically generated Moodle v5.1.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2025100601) {
// Changing precision of field shortname on table tool_recyclebin_category to (1333).
$table = new xmldb_table('tool_recyclebin_category');
$field = new xmldb_field('shortname', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null, 'categoryid');
@@ -73,11 +75,8 @@ function xmldb_tool_recyclebin_upgrade($oldversion) {
$dbman->change_field_precision($table, $field);
// Recyclebin savepoint reached.
upgrade_plugin_savepoint(true, 2025041401, 'tool', 'recyclebin');
upgrade_plugin_savepoint(true, 2025100601, 'tool', 'recyclebin');
}
// Automatically generated Moodle v5.1.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2025100600; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2025100601; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2025092600; // Requires this Moodle version.
$plugin->component = 'tool_recyclebin'; // Full name of the plugin (used for diagnostics).
+1 -1
View File
@@ -49,7 +49,7 @@ function tool_task_mtrace_wrapper(string $message, string $eol = ''): void {
// We autolink urls and emails here but can't use format_text as it does
// more than we need and has side effects which are not useful in this context.
$urlpattern = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/';
$urlpattern = '~\b(?:https?|ftps?)://[a-z0-9-]+(?:\.[a-z0-9-]+)*(?::\d+)?(?:/[^\s<]*)?~i';
$message = preg_replace_callback($urlpattern, function($matches) {
$url = $matches[0];
return html_writer::link($url, $url, ['target' => '_blank']);
+2 -2
View File
@@ -58,7 +58,7 @@ class tool_task_renderer extends plugin_renderer_base {
get_string('nextruntime', 'tool_task'),
];
$table->attributes['class'] = 'admintable table generaltable table-hover';
$table->attributes['class'] = 'admintable table generaltable table-striped table-hover';
$table->colclasses = [];
// For each task entry (row) show action buttons/logs link depending on the user permissions.
@@ -388,7 +388,7 @@ class tool_task_renderer extends plugin_renderer_base {
get_string('default', 'tool_task'),
];
$table->attributes['class'] = 'admintable table generaltable table-hover';
$table->attributes['class'] = 'admintable table generaltable table-striped table-hover';
$table->colclasses = [];
if (!$showloglink) {
+82
View File
@@ -0,0 +1,82 @@
<?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/>.
namespace tool_task;
/**
* Test for the lib class.
*
* @package tool_task
* @copyright 2026 Brendan Heywood <brendan@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class lib_test extends \advanced_testcase {
/**
* Data provider for mtrace
*
* @return array
*/
public static function tool_task_mtrace_wrapper_provider(): array {
return [
[
'A url http://moodle.com',
'A url <a target="_blank" href="http://moodle.com">http://moodle.com</a>',
],
[
'A url https://moodle.com',
'A url <a target="_blank" href="https://moodle.com">https://moodle.com</a>',
],
[
'A url https://moodle.com post text',
'A url <a target="_blank" href="https://moodle.com">https://moodle.com</a> post text',
],
[
'A url https://moodle.com. In a paragraph',
'A url <a target="_blank" href="https://moodle.com">https://moodle.com</a>. In a paragraph',
],
[
'A url https://localhost post text',
'A url <a target="_blank" href="https://localhost">https://localhost</a> post text',
],
[
'A url https://main.localhost post text',
'A url <a target="_blank" href="https://main.localhost">https://main.localhost</a> post text',
],
[
'email info@moodle.com after',
'email <a href="mailto:info@moodle.com">info@moodle.com</a> after',
],
[
'A sentence that ends in info@moodle.com. With another sentence.',
'A sentence that ends in <a href="mailto:info@moodle.com">info@moodle.com</a>. With another sentence.',
],
];
}
/**
* Test validations for minute field.
* @dataProvider tool_task_mtrace_wrapper_provider
* @param string $output task output
* @param string $expected html
* @covers ::tool_task_mtrace_wrapper
*/
public function test_tool_task_mtrace_wrapper(string $output, string $expected): void {
global $CFG;
require_once("{$CFG->dirroot}/{$CFG->admin}/tool/task/lib.php");
$this->expectOutputString($expected);
$result = tool_task_mtrace_wrapper($output);
}
}
@@ -0,0 +1,41 @@
@tool @tool_uploaduser @_file_upload
Feature: Admin can suspend user course enrolment via CSV upload
In order to manage enrolments in bulk
As an administrator
I need to be able to enrol and suspend users using CSV upload
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
| Course 3 | C3 |
@javascript
Scenario: Admin uploads enrol and suspend CSVs and verifies enrolment status
Given I log in as "admin"
And I navigate to "Users > 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 |
+2 -4
View File
@@ -35,8 +35,6 @@ if (isset($newsettings['frontpagesettings'])) {
$newsettingshtml = implode($newsettings);
unset($newsettings);
$focus = '';
if (empty($adminroot->errors) and $newsettingshtml === '') {
// there must be either redirect without message or continue button or else upgrade would be sometimes broken
if ($return == 'site') {
@@ -48,12 +46,12 @@ if (empty($adminroot->errors) and $newsettingshtml === '') {
if (!empty($adminroot->errors)) {
$firsterror = reset($adminroot->errors);
$focus = $firsterror->id;
$PAGE->set_focuscontrol($firsterror->id);
}
// and finally, if we get here, then there are new settings and we have to print a form
// to modify them
echo $OUTPUT->header($focus);
echo $OUTPUT->header();
if (!empty($SITE->fullname) and !empty($SITE->shortname)) {
echo $OUTPUT->box(get_string('upgradesettingsintro','admin'), 'generalbox');
+6 -2
View File
@@ -196,8 +196,12 @@ class external_service_functions_form extends moodleform {
//we add the descriptions to the functions
foreach ($functions as $functionid => $functionname) {
//retrieve full function information (including the description)
$function = \core_external\external_api::external_function_info($functionname);
if (empty($function->deprecated)) {
try {
$function = \core_external\external_api::external_function_info($functionname);
} catch (Throwable $exception) {
$function = null;
}
if ($function !== null && empty($function->deprecated)) {
$functions[$functionid] = $function->name . ':' . $function->description;
} else {
// Exclude the deprecated ones.
+6
View File
@@ -72,6 +72,12 @@ $PAGE->set_pagelayout('admin');
$PAGE->set_title($title);
$PAGE->set_heading($title);
// Explode if there are no provider plugins installed.
$plugins = core_plugin_manager::instance()->get_plugins_of_type('aiprovider');
if (empty($plugins)) {
throw new moodle_exception('noproviderplugins', 'core_ai');
}
// Provider instance form processing.
$mform = new \core_ai\form\ai_provider_form(customdata: $data);
if ($mform->is_cancelled()) {
@@ -143,7 +143,7 @@ abstract class abstract_processor extends process_base {
$errormessage = $response->getReasonPhrase();
} else {
$bodyobj = json_decode($response->getBody()->getContents());
$errormessage = $bodyobj->error->message;
$errormessage = $bodyobj->error;
}
return \core_ai\error\factory::create($status, $errormessage)->get_error_details();
@@ -55,7 +55,7 @@ class provider extends \core_ai\provider {
#[\Override]
public function add_authentication_headers(RequestInterface $request): RequestInterface {
if (empty($this->config['basicauthenabled'])) {
if (empty($this->config['enablebasicauth'])) {
return $request;
} else {
// Add the Authorization header for basic auth.
@@ -151,12 +151,21 @@ final class process_explain_text_test extends \advanced_testcase {
$responses = [
500 => new Response(500, ['Content-Type' => 'application/json']),
503 => new Response(503, ['Content-Type' => 'application/json']),
401 => new Response(401, ['Content-Type' => 'application/json'],
'{"error": {"message": "Invalid Authentication"}}'),
404 => new Response(404, ['Content-Type' => 'application/json'],
'{"error": {"message": "You must be a member of an organization to use the API"}}'),
429 => new Response(429, ['Content-Type' => 'application/json'],
'{"error": {"message": "Rate limit reached for requests"}}'),
401 => new Response(
401,
['Content-Type' => 'application/json'],
json_encode(['error' => 'Invalid Authentication']),
),
404 => new Response(
404,
['Content-Type' => 'application/json'],
json_encode(['error' => 'You must be a member of an organization to use the API']),
),
429 => new Response(
429,
['Content-Type' => 'application/json'],
json_encode(['error' => 'Rate limit reached for requests']),
),
];
$processor = new process_explain_text($this->provider, $this->action);
@@ -317,7 +326,7 @@ final class process_explain_text_test extends \advanced_testcase {
$mock->append(new Response(
401,
['Content-Type' => 'application/json'],
json_encode(['error' => ['message' => 'Invalid Authentication']]),
json_encode(['error' => 'Invalid Authentication']),
));
$processor = new process_explain_text($this->provider, $this->action);
@@ -153,17 +153,17 @@ final class process_generate_text_test extends \advanced_testcase {
401 => new Response(
401,
['Content-Type' => 'application/json'],
json_encode(['error' => ['message' => 'Invalid Authentication']]),
json_encode(['error' => 'Invalid Authentication']),
),
404 => new Response(
404,
['Content-Type' => 'application/json'],
json_encode(['error' => ['message' => 'You must be a member of an organization to use the API']]),
json_encode(['error' => 'You must be a member of an organization to use the API']),
),
429 => new Response(
429,
['Content-Type' => 'application/json'],
json_encode(['error' => ['message' => 'Rate limit reached for requests']]),
json_encode(['error' => 'Rate limit reached for requests']),
),
];
@@ -326,7 +326,7 @@ final class process_generate_text_test extends \advanced_testcase {
$mock->append(new Response(
401,
['Content-Type' => 'application/json'],
json_encode(['error' => ['message' => 'Invalid Authentication']]),
json_encode(['error' => 'Invalid Authentication']),
));
$processor = new process_generate_text($this->provider, $this->action);
@@ -151,12 +151,21 @@ final class process_summarise_text_test extends \advanced_testcase {
$responses = [
500 => new Response(500, ['Content-Type' => 'application/json']),
503 => new Response(503, ['Content-Type' => 'application/json']),
401 => new Response(401, ['Content-Type' => 'application/json'],
'{"error": {"message": "Invalid Authentication"}}'),
404 => new Response(404, ['Content-Type' => 'application/json'],
'{"error": {"message": "You must be a member of an organization to use the API"}}'),
429 => new Response(429, ['Content-Type' => 'application/json'],
'{"error": {"message": "Rate limit reached for requests"}}'),
401 => new Response(
401,
['Content-Type' => 'application/json'],
json_encode(['error' => 'Invalid Authentication']),
),
404 => new Response(
404,
['Content-Type' => 'application/json'],
json_encode(['error' => 'You must be a member of an organization to use the API']),
),
429 => new Response(
429,
['Content-Type' => 'application/json'],
json_encode(['error' => 'Rate limit reached for requests']),
),
];
$processor = new process_summarise_text($this->provider, $this->action);
@@ -317,7 +326,7 @@ final class process_summarise_text_test extends \advanced_testcase {
$mock->append(new Response(
401,
['Content-Type' => 'application/json'],
json_encode(['error' => ['message' => 'Invalid Authentication']]),
json_encode(['error' => 'Invalid Authentication']),
));
$processor = new process_summarise_text($this->provider, $this->action);
+6 -8
View File
@@ -171,7 +171,7 @@ class auth_plugin_email extends auth_plugin_base {
* @param string $confirmsecret
*/
function user_confirm($username, $confirmsecret) {
global $DB, $SESSION;
global $DB;
$user = get_complete_user_data('username', $username);
if (!empty($user)) {
@@ -179,17 +179,15 @@ class auth_plugin_email extends auth_plugin_base {
return AUTH_CONFIRM_ERROR;
} else if ($user->secret === $confirmsecret && $user->confirmed) {
// Clean up stale wantsurl preference if user clicks confirmation link again.
unset_user_preference('auth_email_wantsurl', $user);
return AUTH_CONFIRM_ALREADY;
} else if ($user->secret === $confirmsecret) { // They have provided the secret key to get in
$DB->set_field("user", "confirmed", 1, array("id"=>$user->id));
if ($wantsurl = get_user_preferences('auth_email_wantsurl', false, $user)) {
// Ensure user gets returned to page they were trying to access before signing up.
$SESSION->wantsurl = $wantsurl;
unset_user_preference('auth_email_wantsurl', $user);
}
// Clean up the wantsurl preference regardless of how confirmation was triggered
// (e.g. /login/confirm.php, admin single confirm, bulk confirm, web service).
unset_user_preference('auth_email_wantsurl', $user);
return AUTH_CONFIRM_OK;
}
} else {
+96
View File
@@ -0,0 +1,96 @@
<?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/>.
namespace auth_email;
/**
* Tests for email authentication plugin.
*
* @package auth_email
* @copyright 2026 Moodle Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \auth_plugin_email
*/
final class auth_test extends \advanced_testcase {
/**
* Test that user_confirm() cleans up the auth_email_wantsurl preference
* when confirming a user for the first time (AUTH_CONFIRM_OK).
*/
public function test_user_confirm_cleans_up_wantsurl_preference(): void {
global $DB;
$this->resetAfterTest(true);
// Create an unconfirmed user with the email auth method.
$user = $this->getDataGenerator()->create_user([
'auth' => 'email',
'confirmed' => 0,
]);
$secret = random_string(15);
$DB->set_field('user', 'secret', $secret, ['id' => $user->id]);
// Simulate the wantsurl preference saved at signup time.
set_user_preference('auth_email_wantsurl', 'https://example.com/course/view.php?id=42', $user);
$this->assertTrue(
$DB->record_exists('user_preferences', ['userid' => $user->id, 'name' => 'auth_email_wantsurl']),
'Preference should exist in DB before confirmation.'
);
$auth = get_auth_plugin('email');
$result = $auth->user_confirm($user->username, $secret);
$this->assertEquals(AUTH_CONFIRM_OK, $result);
$this->assertFalse(
$DB->record_exists('user_preferences', ['userid' => $user->id, 'name' => 'auth_email_wantsurl']),
'auth_email_wantsurl preference must be removed from DB after successful confirmation.'
);
}
/**
* Test that user_confirm() cleans up the auth_email_wantsurl preference
* even when the user is already confirmed (AUTH_CONFIRM_ALREADY).
*
* This covers the edge case where a user clicks the confirmation link
* a second time the stale preference should still be cleaned up.
*/
public function test_user_confirm_already_confirmed_cleans_up_wantsurl_preference(): void {
global $DB;
$this->resetAfterTest(true);
// Create an already-confirmed user with the email auth method.
$user = $this->getDataGenerator()->create_user([
'auth' => 'email',
'confirmed' => 1,
]);
$secret = random_string(15);
$DB->set_field('user', 'secret', $secret, ['id' => $user->id]);
// Simulate a stale wantsurl preference left over from signup.
set_user_preference('auth_email_wantsurl', 'https://example.com/course/view.php?id=42', $user);
$this->assertTrue(
$DB->record_exists('user_preferences', ['userid' => $user->id, 'name' => 'auth_email_wantsurl']),
'Preference should exist in DB before re-confirmation.'
);
$auth = get_auth_plugin('email');
$result = $auth->user_confirm($user->username, $secret);
$this->assertEquals(AUTH_CONFIRM_ALREADY, $result);
$this->assertFalse(
$DB->record_exists('user_preferences', ['userid' => $user->id, 'name' => 'auth_email_wantsurl']),
'auth_email_wantsurl preference must be removed from DB even when user is already confirmed.'
);
}
}
+2 -6
View File
@@ -197,9 +197,7 @@ class api {
$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 @@ class api {
$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.
+42 -14
View File
@@ -467,18 +467,31 @@ class auth extends \auth_plugin_base {
$mappeduser = get_complete_user_data('id', $linkedlogin->get('userid'));
if ($mappeduser && $mappeduser->suspended) {
$failurereason = AUTH_LOGIN_SUSPENDED;
$event = \core\event\user_login_failed::create([
'userid' => $mappeduser->id,
'other' => [
'username' => $userinfo['username'],
'reason' => $failurereason
]
]);
$event->trigger();
$SESSION->loginerrormsg = get_string('invalidlogin');
$client->log_out();
redirect(new moodle_url('/login/index.php'));
// Check if there's another user with the same email that is not suspended.
$moodleuser = \core_user::get_user_by_email($userinfo['email'], '*', null, IGNORE_MULTIPLE);
if ($moodleuser->id == $mappeduser->id) {
$failurereason = AUTH_LOGIN_SUSPENDED;
$event = \core\event\user_login_failed::create([
'userid' => $mappeduser->id,
'other' => [
'username' => $userinfo['username'],
'reason' => $failurereason,
],
]);
$event->trigger();
$SESSION->loginerrormsg = get_string('invalidlogin');
$client->log_out();
redirect(new moodle_url('/login/index.php'));
} else if ($moodleuser && !$moodleuser->suspended) {
// Update the OAuth2 linked login to point to the active user account.
$linkedlogin->set('userid', $moodleuser->id);
$linkedlogin->set('timemodified', time());
$linkedlogin->update();
// Update user fields and continue with login.
$userinfo = $this->update_user($userinfo, $moodleuser);
$userwasmapped = true;
}
} else if ($mappeduser && ($mappeduser->confirmed || !$issuer->get('requireconfirmation'))) {
// Update user fields.
$userinfo = $this->update_user($userinfo, $mappeduser);
@@ -508,7 +521,6 @@ class auth extends \auth_plugin_base {
redirect(new moodle_url('/login/index.php'));
}
if (!$issuer->is_valid_login_domain($oauthemail)) {
// Trigger login failed event.
$failurereason = AUTH_LOGIN_UNAUTHORISED;
@@ -524,8 +536,24 @@ class auth extends \auth_plugin_base {
if (!$userwasmapped) {
// No defined mapping - we need to see if there is an existing account with the same email.
$moodleuser = \core_user::get_user_by_email($userinfo['email'], '*', null, IGNORE_MULTIPLE);
// Ensure we don't link a login for a suspended user.
if (!empty($moodleuser) && $moodleuser->suspended) {
$failurereason = AUTH_LOGIN_SUSPENDED;
$event = \core\event\user_login_failed::create([
'userid' => $moodleuser->id,
'other' => [
'username' => $userinfo['email'],
'reason' => $failurereason,
],
]);
$event->trigger();
$SESSION->loginerrormsg = get_string('invalidlogin');
$client->log_out();
redirect(new moodle_url('/login/index.php'));
}
$moodleuser = \core_user::get_user_by_email($userinfo['email']);
if (!empty($moodleuser)) {
if ($issuer->get('requireconfirmation')) {
$PAGE->set_url('/auth/oauth2/confirm-link-login.php');
+7 -16
View File
@@ -27,19 +27,14 @@ $string['auth_oauth2description'] = 'OAuth 2 standards based authentication';
$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}
<a href="{$a->link}">Confirm your account</a>
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 request has been made to link the {$a->issuername} login
{$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}
<a href="{$a->link}">Link your accounts</a>
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.
+4 -2
View File
@@ -34,11 +34,13 @@ Feature: Test if the login form provides the correct feedback
Scenario: Set logo for loginpage
Given I log in as "admin"
And I navigate to "Appearance > Logos" in site administration
And I upload "course/tests/fixtures/image.jpg" file to "Logo" filemanager
And I upload "lib/tests/fixtures/1.jpg" file to "Logo" filemanager
And I upload "lib/tests/fixtures/2.jpg" file to "Favicon" filemanager
And I press "Save changes"
And I log out
And I follow "Log in"
Then "//img[@id='logoimage']" "xpath_element" should exist
Then "//img[@id='logoimage' and contains(@src, '/1.jpg')]" "xpath_element" should exist
And "//link[@rel='shortcut icon' and contains(@href, '/2.jpg')]" "xpath_element" should exist
Scenario: Add a custom welcome message
Given the following config values are set as admin:
@@ -32,7 +32,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
And I set the field "Activity or resource" to "Previous activity with completion"
And I press "Save and return to course"
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
@@ -58,7 +58,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
And I set the field "Activity or resource" to "Previous activity with completion"
And I press "Save and return to course"
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
@@ -83,7 +83,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
And I set the field "Activity or resource" to "Previous activity with completion"
And I press "Save and return to course"
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
@@ -112,7 +112,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
And I set the field "Activity or resource" to "Previous activity with completion"
And I press "Save changes"
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
@@ -138,7 +138,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
Then the "Activity or resource" select box should not contain "Previous activity with completion"
# Set Page2 restriction to Previous Activity with completion and delete Page1.
@@ -148,7 +148,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
And I set the field "Activity or resource" to "Previous activity with completion"
And I press "Save and return to course"
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
@@ -170,7 +170,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
And I set the field "Activity or resource" to "Previous activity with completion"
And I press "Save changes"
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
@@ -184,7 +184,7 @@ Feature: Confirm that availability_completion works with previous activity setti
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on "Displayed if student doesn't meet this condition Click to hide" "link"
And I click on "Item name displayed with access restriction information if student doesn't meet this condition Click to hide" "link"
And I set the field "Activity or resource" to "Previous activity with completion"
And I press "Save changes"
And I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
@@ -184,7 +184,8 @@ class condition extends \core_availability\condition {
*/
private static function get_cached_grade_name($courseid, $gradeitemid) {
global $DB, $CFG;
require_once($CFG->libdir . '/gradelib.php');
require_once("{$CFG->dirroot}/grade/lib.php");
// Get all grade item names from cache, or using db query.
$cache = \cache::make('availability_grade', 'items');
@@ -180,4 +180,4 @@ Feature: Display availability for activities and sections
And I should not see "Date" in the "Restrict access" "fieldset"
And I press "Add restriction..."
And I click on "Grade" "button" in the "Add restriction..." "dialogue"
And the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Displayed if student"
And the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Item name displayed"
@@ -89,13 +89,13 @@ Feature: edit_availability
And I should see "Date" in the "Restrict access" "fieldset"
And ".availability-item .availability-eye img" "css_element" should be visible
And ".availability-item .availability-delete img" "css_element" should be visible
And the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Displayed if student"
And the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Item name displayed"
# Toggle the eye icon.
When I click on ".availability-item .availability-eye img" "css_element"
Then the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Hidden entirely"
When I click on ".availability-item .availability-eye img" "css_element"
Then the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Displayed if student"
Then the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Item name displayed"
# Click the delete button.
When I click on ".availability-item .availability-delete img" "css_element"
+6 -2
View File
@@ -2649,6 +2649,7 @@ class backup_questions_structure_step extends backup_structure_step {
'questioncategoryid',
'idnumber',
'ownerid',
'nextversion',
]);
$questionversions = new backup_nested_element('question_version');
@@ -2725,7 +2726,7 @@ class backup_questions_structure_step extends backup_structure_step {
WHERE bi.backupid = ?
AND bi.itemname = 'question_categoryfinal'", [backup::VAR_BACKUPID]);
// Add all question bank entries from "complete" categories, plus annotated question bank entires
// Add all question bank entries from "complete" categories, plus annotated question bank entires and their children
// from "partial" categories.
$questionbankentry->set_source_sql(
"
@@ -2737,8 +2738,11 @@ class backup_questions_structure_step extends backup_structure_step {
UNION
SELECT qbe.*
FROM {question_bank_entries} qbe
JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
JOIN {question} q ON q.id = qv.questionid
LEFT JOIN {question_versions} parentqv ON parentqv.questionid = q.parent
JOIN {question_category_partial_temp} qcp ON qcp.itemid = qbe.questioncategoryid
JOIN {backup_ids_temp} biq ON biq.itemid = qbe.id
JOIN {backup_ids_temp} biq ON biq.itemid = qbe.id OR biq.itemid = parentqv.questionbankentryid
WHERE qcp.itemid = ?
AND qcp.backupid = ?
AND biq.backupid = ?
@@ -78,6 +78,9 @@ class restore_root_task extends restore_task {
// Unconditionally, load create all the needed outcomes
$this->add_step(new restore_outcomes_structure_step('create_scales', 'outcomes.xml'));
// If we haven't preloaded information, load all the question banks to temp_ids_table.
$this->add_step(new \core\backup\restore_load_questionbanks('load_questionbanks'));
// If we haven't preloaded information, load all the needed categories and questions (reduced) to temp_ids_table
$this->add_step(new restore_load_categories_and_questions('load_categories_and_questions'));
+132 -49
View File
@@ -27,6 +27,9 @@
defined('MOODLE_INTERNAL') || die();
use core_question\local\bank\question_version_status;
use core_question\versions;
/**
* delete old directories and conditionally create backup_temp_ids table
*/
@@ -5289,6 +5292,15 @@ class restore_create_categories_and_questions extends restore_structure_step {
$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);
@@ -5301,9 +5313,55 @@ class restore_create_categories_and_questions extends restore_structure_step {
$oldqvid = $this->latestversion->id;
$this->latestversion->questionbankentryid = $this->latestqbe->newid;
$this->latestversion->questionid = $newitemid;
// In case the backed up version was deleted and a new one created in its place, increase the version numbers of
// conflicting versions to make room for this one.
$transaction = $DB->start_delegated_transaction();
if (
$DB->record_exists(
'question_versions',
[
'questionbankentryid' => $this->latestversion->questionbankentryid,
'version' => $this->latestversion->version,
],
)
) {
// We'll update each higher version and any references one-at-a-time, starting with the highest, to avoid
// creating a duplicate questionbankentryid-version combination in question_versions.
$moveversions = $DB->get_records_select(
'question_versions',
'questionbankentryid = :questionbankentryid AND version >= :oldversion',
[
'questionbankentryid' => $this->latestversion->questionbankentryid,
'oldversion' => $this->latestversion->version,
],
'version DESC',
);
foreach ($moveversions as $moveversion) {
$DB->set_field(
'question_versions',
'version',
$moveversion->version + 1,
[
'questionbankentryid' => $moveversion->questionbankentryid,
'version' => $moveversion->version,
]
);
$DB->set_field(
'question_references',
'version',
$moveversion->version + 1,
[
'questionbankentryid' => $moveversion->questionbankentryid,
'version' => $moveversion->version,
]
);
}
// Ensure the nextversion value has been initialised, and increment it to account for the additional version.
versions::get_next_version($this->latestversion->questionbankentryid);
}
$newqvid = $DB->insert_record('question_versions', $this->latestversion);
$this->set_mapping('question_versions', $oldqvid, $newqvid);
$transaction->allow_commit();
} else {
// By performing this set_mapping() we make get_old/new_parentid() to work for all the
// children elements of the 'question' one (so qtype plugins will know the question they belong to).
@@ -5311,6 +5369,18 @@ class restore_create_categories_and_questions extends restore_structure_step {
// Also create the question_bank_entry and version mappings, if required.
$newquestionversion = $DB->get_record('question_versions', ['questionid' => $questionmapping->newitemid]);
// Restore the version to ready state if it has been hidden.
if (
$newquestionversion->status == question_version_status::QUESTION_STATUS_HIDDEN
&& $this->latestversion->status == question_version_status::QUESTION_STATUS_READY
) {
$DB->set_field(
'question_versions',
'status',
question_version_status::QUESTION_STATUS_READY,
['questionid' => $questionmapping->newitemid],
);
}
$this->set_mapping('question_versions', $this->latestversion->id, $newquestionversion->id);
if (empty($this->latestqbe->newid)) {
$this->latestqbe->oldid = $this->latestqbe->id;
@@ -5402,12 +5472,18 @@ class restore_create_categories_and_questions extends restore_structure_step {
}
$tagcontextid = $this->cachedcategory->contextid;
// Add the tag to the question.
core_tag_tag::add_item_tag('core_question',
$taginstanceid = core_tag_tag::add_item_tag(
'core_question',
'question',
$newquestion,
context::instance_by_id($tagcontextid),
$tagname
$tagname,
);
$tagid = $DB->get_field('tag_instance', 'tagid', ['id' => $taginstanceid]);
if ($tagid != $data->id) {
// The tag didn't exist already, map the new ID.
$this->set_mapping('tag', $data->id, $tagid);
}
}
}
@@ -5485,7 +5561,11 @@ class restore_move_module_questions_categories extends restore_execution_step {
// but if that context still exists on the site and the user has access then point question references
// to the originals.
$originalcontext = context::instance_by_id($contextid, IGNORE_MISSING);
if ($originalcontext && has_capability('mod/qbank:view', $originalcontext)) {
if (
$this->task->is_samesite()
&& $originalcontext
&& has_capability('mod/qbank:view', $originalcontext)
) {
$originalquestions = get_questions_category(question_get_top_category($contextid), false);
$targetcoursecontext = context_course::instance($this->get_courseid());
foreach ($originalquestions as $originalquestion) {
@@ -5608,13 +5688,19 @@ class restore_move_module_questions_categories extends restore_execution_step {
// We need to check all the question_set_references belonging to this context_module.
$references = $DB->get_records('question_set_references', ['usingcontextid' => $newcontext->newitemid]);
foreach ($references as $reference) {
$filtercondition = json_decode($reference->filtercondition);
if (!empty($filtercondition->questioncategoryid) &&
in_array($filtercondition->questioncategoryid, $categoryids)) {
// This is one of ours, update the questionscontextid.
$DB->set_field('question_set_references',
'questionscontextid', $newcontext->newitemid,
['id' => $reference->id]);
$filtercondition = json_decode($reference->filtercondition, true);
if (!array_key_exists('filter', $filtercondition)) {
$filtercondition = \core_question\question_reference_manager::convert_legacy_set_reference_filter_condition(
$filtercondition,
);
}
$questioncategoryid = $filtercondition['filter']['category']['values'][0];
if (in_array($questioncategoryid, $categoryids)) {
// This is one of ours, update the questionscontextid and filtercondition fields.
$reference->questionscontextid = $newcontext->newitemid;
$filtercondition['cat'] = "{$questioncategoryid},{$newcontext->newitemid}";
$reference->filtercondition = json_encode($filtercondition);
$DB->update_record('question_set_references', $reference);
}
}
}
@@ -5629,20 +5715,20 @@ class restore_move_module_questions_categories extends restore_execution_step {
);
}
}
// Remove any remaining course-level question categories from the restored course.
// Remove any remaining course-level question categories and their questions from the restored course.
$coursecatsql = "
SELECT qc.id AS categoryid
SELECT qc.id AS id, qc.contextid AS contextid
FROM {question_categories} qc
JOIN {context} c ON c.id = qc.contextid
WHERE c.contextlevel = :courselevel AND c.instanceid = :courseid
";
$DB->delete_records_subquery(
'question_categories',
'id',
'categoryid',
$categories = $DB->get_records_sql(
$coursecatsql,
['courselevel' => context_course::LEVEL, 'courseid' => $this->task->get_courseid()]
['courselevel' => context_course::LEVEL, 'courseid' => $this->task->get_courseid()],
);
foreach ($categories as $category) {
question_category_delete_safe($category);
}
}
}
@@ -6421,50 +6507,47 @@ trait restore_question_set_reference_data_trait {
public function process_question_set_reference($data) {
global $DB;
$data = (object) $data;
$owncontext = $data->usingcontextid == $data->questionscontextid;
$data->usingcontextid = $this->get_mappingid('context', $data->usingcontextid);
$data->itemid = $this->get_new_parentid('quiz_question_instance');
$originalbankinbackup = (bool) restore_dbops::get_backup_ids_record(
$this->get_restoreid(),
'questionbank',
$data->questionscontextid,
);
if ($context = $this->get_mappingid('context', $data->questionscontextid)) {
$data->questionscontextid = $context;
} else {
$this->log(
"question_set_reference with old id {$data->id} referenced question context "
. "{$data->questionscontextid} which was not included in the backup. Therefore, this has been "
. "restored with the old questionscontextid.",
backup::LOG_WARNING,
);
}
$filtercondition = json_decode($data->filtercondition, true);
if (!isset($filtercondition['filter'])) {
// Pre-4.3, convert the old filtercondition format to the new format.
// Don't map tags to new IDs, the plugin will do that below.
$filtercondition = \core_question\question_reference_manager::convert_legacy_set_reference_filter_condition(
$filtercondition);
$filtercondition,
false,
);
}
// Map category id used for category filter condition and corresponding context id.
$oldcategoryid = $filtercondition['filter']['category']['values'][0];
// Decide if we're going to refer back to the original category, or to the new category.
// Are we restoring to a different site?
// Has the original context or category been deleted?
// Did the old category belong to the same context as the original set reference?
// Are we allowed to use its questions?
$questionscontext = context::instance_by_id($data->questionscontextid, IGNORE_MISSING);
if (
!$this->get_task()->is_samesite()
|| !$questionscontext
|| !$DB->record_exists('question_categories', ['id' => $oldcategoryid])
|| $owncontext
|| !has_capability('moodle/question:useall', $questionscontext)
) {
$newcategoryid = $this->get_mappingid('question_category', $oldcategoryid);
$filtercondition['filter']['category']['values'][0] = $newcategoryid;
$qbankfeatureclasses = \core\component::get_plugin_list_with_class('qbank', 'plugin_feature');
if ($context = $this->get_mappingid('context', $data->questionscontextid)) {
$data->questionscontextid = $context;
} else {
$this->log('question_set_reference with old id ' . $data->id .
' referenced question context ' . $data->questionscontextid .
' which was not included in the backup. Therefore, this has been ' .
' restored with the old questionscontextid.', backup::LOG_WARNING);
foreach ($qbankfeatureclasses as $qbankfeatureclass) {
$qbankfeature = new $qbankfeatureclass();
$filters = $qbankfeature->get_question_filters();
foreach ($filters as $filter) {
$filtercondition = $filter->restore_filtercondition($filtercondition, $data, $this, $originalbankinbackup);
}
}
$filtercondition['cat'] = implode(',', [
$filtercondition['filter']['category']['values'][0],
$data->questionscontextid,
]);
$data->filtercondition = json_encode($filtercondition);
$DB->insert_record('question_set_references', $data);
+3 -20
View File
@@ -107,17 +107,12 @@ final class async_backup_test extends \advanced_testcase {
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(['backupid' => $backupid]);
$asynctask->set_userid($USER->id);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting trace output during this test.
$this->expectOutputRegex("/$backupid/");
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
$asynctask->execute();
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
@@ -151,13 +146,9 @@ final class async_backup_test extends \advanced_testcase {
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(['backupid' => $backupid]);
\core\task\manager::queue_adhoc_task($asynctask);
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
$asynctask->execute();
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
@@ -241,19 +232,11 @@ final class async_backup_test extends \advanced_testcase {
// Now queue an adhoc task and check it handles and completes gracefully.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(array('backupid' => $backupid));
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/invalid controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
$asynctask->execute();
}
}
+2 -18
View File
@@ -223,20 +223,12 @@ final class async_restore_test extends \advanced_testcase {
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_restore_task();
$asynctask->set_custom_data(['backupid' => $restoreid]);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/invalid controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
$asynctask->execute();
// Now delete the record and confirm an entirely missing controller is handled.
$DB->delete_records('backup_controllers');
@@ -244,19 +236,11 @@ final class async_restore_test extends \advanced_testcase {
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_restore_task();
$asynctask->set_custom_data(['backupid' => $restoreid]);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/Unable to find restore controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
$asynctask->execute();
}
}
@@ -32,6 +32,14 @@ use core_backup\hook\fixtures\copy_course_hook_callbacks;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class copy_course_hook_test extends advanced_testcase {
/**
* Load required test libraries
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->dirroot}/backup/util/includes/backup_includes.php");
parent::setUpBeforeClass();
}
/**
* Test the hook.
@@ -464,6 +464,39 @@ abstract class restore_dbops {
$xmlparser->process();
}
/**
* Store ids associated with any activity in the backup that supports FEATURE_PUBLISHES_QUESTIONS.
*
* @param string $restoreid The restore ID.
* @param string $activitiespath The path to the `activities` folder in the backup being restored.
*/
public static function load_questionbanks_to_tempids(string $restoreid, string $activitiespath): void {
if (!is_dir($activitiespath)) {
return;
}
// Get modules that publish questions.
$qmodules = array_filter(
array_keys(core\component::get_all_plugins_list('mod')),
fn($module) => plugin_supports('mod', $module, FEATURE_PUBLISHES_QUESTIONS),
);
foreach (scandir($activitiespath) as $activitydir) {
[$modname] = explode('_', $activitydir);
if (!in_array($modname, $qmodules)) {
continue;
}
$activityfile = "{$activitiespath}/{$activitydir}/{$modname}.xml";
if (!file_exists($activityfile)) { // Shouldn't happen ever, but...
throw new backup_helper_exception('missing_moodle_backup_xml_file', $activityfile);
}
// Parse each activity's file, storing the relevant data in the database.
$xmlparser = new progressive_parser();
$xmlparser->set_file($activityfile);
$xmlprocessor = new restore_questionbanks_parser_processor($restoreid);
$xmlparser->set_processor($xmlprocessor);
$xmlparser->process();
}
}
/**
* Check all the included categories and questions, deciding the action to perform
* for each one (mapping / creation) and returning one array of problems in case
@@ -609,9 +642,18 @@ abstract class restore_dbops {
$topcats = 0;
// get categories in context (bank)
$categories = self::restore_get_question_categories($restoreid, $contextid, $contextlevel);
// cache permissions if $targetcontext is found
if ($targetcontext = self::restore_find_best_target_context($categories, $courseid, $contextlevel)) {
if (
$contextlevel == \core\context\module::LEVEL
&& self::get_backup_ids_record($restoreid, 'questionbank', $contextid)
) {
// Don't look for an existing module context, we have the original context in the backup,
// so we'll put the categories in the course context for now and move them once the activity is restored.
$targetcontext = core\context\course::instance($courseid);
} else {
$targetcontext = self::restore_find_best_target_context($categories, $courseid, $contextlevel);
}
if ($targetcontext) {
// Cache permissions if $targetcontext is found.
$canmanagecategory = has_capability('moodle/question:managecategory', $targetcontext, $userid);
$canadd = has_capability('moodle/question:add', $targetcontext, $userid);
}
@@ -411,7 +411,7 @@ abstract class backup_helper {
}
$selectmenu = new \core\output\select_menu('coursereusetype', $menuarray, $activeurl);
$selectmenu = new \core\output\select_menu('coursereusetype', $menuarray, $activeurl, true);
$selectmenu->set_label(get_string('coursereusenavigationmenu'), ['class' => 'visually-hidden']);
$options = \html_writer::tag(
'div',
@@ -173,6 +173,7 @@ abstract class restore_prechecks_helper {
$progress->progress($majorstep++);
// Check we are able to restore and the categories and questions
restore_dbops::load_questionbanks_to_tempids($restoreid, $controller->get_plan()->get_basepath() . '/activities');
$file = $controller->get_plan()->get_basepath() . '/questions.xml';
restore_dbops::load_categories_and_questions_to_tempids($restoreid, $file);
if ($problems = restore_dbops::precheck_categories_and_questions($restoreid, $courseid, $userid, $samesite)) {
@@ -0,0 +1,59 @@
<?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/>.
defined('MOODLE_INTERNAL' || die());
require_once($CFG->dirroot . '/backup/util/xml/parser/processors/grouped_parser_processor.class.php');
/**
* Parse and store activity data for activities that publish questions.
*
* @package core
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_questionbanks_parser_processor extends grouped_parser_processor {
/**
* Store the restore ID and register paths.
*
* @param string $restoreid ID of the backup being restored.
*/
public function __construct(
/** @var string ID of the backup being restored */
protected string $restoreid,
) {
parent::__construct();
$this->add_path('/activity');
}
#[\Override]
protected function dispatch_chunk($data): void {
// Recieved one chunk, store the context ID as that's what we will match question categories against.
$itemid = $data['tags']['contextid'];
restore_dbops::set_backup_ids_record($this->restoreid, 'questionbank', $itemid);
}
#[\Override]
protected function notify_path_start($path) {
// Nothing to do.
}
#[\Override]
protected function notify_path_end($path) {
// Nothing to do.
}
}
@@ -39,6 +39,7 @@ require_once($CFG->dirroot . '/backup/util/helper/backup_anonymizer_helper.class
require_once($CFG->dirroot . '/backup/util/helper/backup_file_manager.class.php');
require_once($CFG->dirroot . '/backup/util/helper/copy_helper.class.php');
require_once($CFG->dirroot . '/backup/util/helper/restore_prechecks_helper.class.php');
require_once($CFG->dirroot . '/backup/util/helper/restore_questionbanks_parser_processor.php');
require_once($CFG->dirroot . '/backup/util/helper/restore_moodlexml_parser_processor.class.php');
require_once($CFG->dirroot . '/backup/util/helper/restore_inforef_parser_processor.class.php');
require_once($CFG->dirroot . '/backup/util/helper/restore_users_parser_processor.class.php');
+1 -1
View File
@@ -217,7 +217,7 @@ if ($award && data_submitted() && has_capability('moodle/badges:awardbadge', $co
$users = $existingselector->get_selected_users();
foreach ($users as $user) {
if (!process_manual_revoke($user->id, $USER->id, $issuerrole->roleid, $badgeid)) {
if (!process_manual_revoke($user->id, 0, $issuerrole->roleid, $badgeid)) {
echo $OUTPUT->error_text(get_string('error:cannotrevokebadge', 'badges'));
}
}
@@ -234,7 +234,7 @@ class award_criteria_course extends award_criteria {
$params['courseid'] = $this->courseid;
// Add by date parameter.
if (isset($param['bydate'])) {
if (isset($coursecriteria['bydate'])) {
$where .= ' AND cc.timecompleted <= :completebydate';
$params['completebydate'] = $coursecriteria['bydate'];
}
@@ -211,7 +211,10 @@ class award_criteria_courseset extends award_criteria {
$check_grade = ($grade->grade >= $param['grade']);
}
if (!$filtered && isset($param['bydate'])) {
// When the user list is not filtered, or the aggregation method is ANY,
// ensure the course completion date is before the bydate (if set).
$comparedates = !$filtered || $this->method == BADGE_CRITERIA_AGGREGATION_ANY;
if ($comparedates && isset($param['bydate'])) {
$cparams = array(
'userid' => $userid,
'course' => $course->id,
@@ -254,9 +257,16 @@ class award_criteria_courseset extends award_criteria {
$params = array();
if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
$coursedata = [];
foreach ($this->params as $param) {
$coursedata[] = " cc.course = :completedcourse{$param['course']} ";
$conditionsql = " cc.course = :completedcourse{$param['course']} ";
$params["completedcourse{$param['course']}"] = $param['course'];
if (isset($param['bydate'])) {
$conditionsql .= " AND cc.timecompleted <= :completedbydate{$param['course']} ";
$params["completedbydate{$param['course']}"] = $param['bydate'];
}
$coursedata[] = "({$conditionsql})";
}
if (!empty($coursedata)) {
$extraon = implode(' OR ', $coursedata);
@@ -272,6 +282,11 @@ class award_criteria_courseset extends award_criteria {
cc{$param['course']}.timecompleted > 0 ";
$where .= " AND cc{$param['course']}.course IS NOT NULL ";
$params["completedcourse{$param['course']}"] = $param['course'];
if (isset($param['bydate'])) {
$where .= " AND cc{$param['course']}.timecompleted <= :completedbydate{$param['course']} ";
$params["completedbydate{$param['course']}"] = $param['bydate'];
}
}
return array($join, $where, $params);
}
+16 -16
View File
@@ -290,27 +290,27 @@ function process_manual_award($recipientid, $issuerid, $issuerrole, $badgeid) {
/**
* Manually revoke awarded badges.
*
* @param int $recipientid
* @param int $issuerid
* @param int $issuerrole
* @param int $badgeid
* @param int $recipientid User ID of the recipient
* @param int $issuerid User ID of the issuer (if 0, issuer will be ignored)
* @param int $issuerrole Role of the issuer
* @param int $badgeid ID of the badge
* @return bool
*/
function process_manual_revoke($recipientid, $issuerid, $issuerrole, $badgeid) {
global $DB;
$params = array(
'badgeid' => $badgeid,
'issuerid' => $issuerid,
'issuerrole' => $issuerrole,
'recipientid' => $recipientid
);
$params = [
'badgeid' => $badgeid,
'issuerrole' => $issuerrole,
'recipientid' => $recipientid,
];
if (!empty($issuerid)) {
$params['issuerid'] = $issuerid;
}
if ($DB->record_exists('badge_manual_award', $params)) {
if ($DB->delete_records('badge_manual_award', array('badgeid' => $badgeid,
'issuerid' => $issuerid,
'recipientid' => $recipientid))
&& $DB->delete_records('badge_issued', array('badgeid' => $badgeid,
'userid' => $recipientid))) {
if (
$DB->delete_records('badge_manual_award', $params) &&
$DB->delete_records('badge_issued', ['badgeid' => $badgeid, 'userid' => $recipientid])
) {
// Trigger event, badge revoked.
$badge = new \badge($badgeid);
$eventparams = array(
+16 -4
View File
@@ -11,11 +11,13 @@ Feature: Award badges
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| teacher2 | Teacher | 2 | teacher2@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activity" exists:
@@ -371,14 +373,24 @@ Feature: Award badges
And I am on "Course 1" course homepage
And I navigate to "Badges" in current page administration
And I follow "Course Badge"
Then I should see "Recipients (2)"
And I select "Recipients (2)" from the "jump" singleselect
And I press "Award badge"
And I set the field "existingrecipients[]" to "Student 2 (student2@example.com)"
And I press "Revoke badge"
And I set the field "existingrecipients[]" to "Student 1 (student1@example.com)"
When I press "Revoke badge"
And I am on "Course 1" course homepage
And I navigate to "Badges" in current page administration
And I follow "Course Badge"
Then I should see "Recipients (0)"
Then I should see "Recipients (1)"
And I log out
# Now attempt to revoke a badge as another teacher.
And I am on the "Course 1" "course" page logged in as "teacher2"
And I navigate to "Badges" in current page administration
And I follow "Course Badge"
And I select "Recipients (1)" from the "jump" singleselect
And I press "Award badge"
And I set the field "existingrecipients[]" to "Student 1 (student1@example.com)"
And I press "Revoke badge"
And I am on "Course 1" course homepage
And I navigate to "Badges" in current page administration
And I follow "Course Badge"
And I should see "Recipients (0)"
@@ -0,0 +1,149 @@
@mod @mod_quiz @core_badges @core_completion @javascript
Feature: Award badges based on course completion
In order to ensure a student has learned the required material
As a teacher
I need a badge to be awarded only when the student completes the course or a set of courses.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
| Course 2 | C2 | 0 | 1 |
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 "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
| Course | C2 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
And the following "activities" exist:
| activity | name | course | idnumber | attempts | gradepass | completion | completionpassgrade | completionusegrade |
| quiz | Test quiz name C1 | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 |
| quiz | Test quiz name C2 | C2 | quiz1 | 2 | 5.00 | 2 | 1 | 1 |
And quiz "Test quiz name C1" contains the following questions:
| question | page |
| First question | 1 |
And quiz "Test quiz name C2" contains the following questions:
| question | page |
| First question | 1 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| completionstatus | Course | C1 | course-view-* | side-pre |
| completionstatus | Course | C2 | course-view-* | side-pre |
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the field "Test quiz name C1" to "1"
And I press "Save changes"
And I am on the "Course 2" course page logged in as teacher1
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the field "Test quiz name C2" to "1"
And I press "Save changes"
Scenario Outline: Badge awarded depending on the course completion by-date
Given I am on the "Course 1" course page
And the following "core_badges > Badge" exists:
| name | Course Badge 1 |
| status | 0 |
| type | 2 |
| course | C1 |
| description | Course badge 1 description |
| image | badges/tests/behat/badge.png |
And I navigate to "Badges" in current page administration
And I click on "Course Badge 1" "link"
And I select "Criteria" from the "jump" singleselect
And I set the field "type" to "Course completion"
And I set the field "Enable" to "1"
And I set the field "Day" to "1"
And I set the field "Month" to "January"
And I set the field "Year" to "<year>"
And I press "Save"
And I press "Enable access"
And I click on "Enable" "button" in the "Confirm" "dialogue"
And user "student1" has attempted "Test quiz name C1" with responses:
| slot | response |
| 1 | True |
# Completion cron won't mark the whole course completed unless the
# individual criteria was marked completed more than a second ago. So
# run it twice, first to mark the criteria and second for the course.
And I run the scheduled task "core\task\completion_regular_task"
And I wait "1" seconds
And I run the scheduled task "core\task\completion_regular_task"
When I run the scheduled task "core\task\badges_cron_task"
And I run all adhoc tasks
And I navigate to "Badges" in current page administration
And I click on "Course Badge 1" "link"
Then I should see "Recipients (<count>)"
And I select "Recipients (<count>)" from the "jump" singleselect
And I <student1_visibility> see "Student 1"
And I should not see "Student 2"
Examples:
| year | count | student1_visibility |
| ## +1 year ## %Y ## | 1 | should |
| ## -1 year ## %Y ## | 0 | should not |
Scenario Outline: Badge awarded depending on the courseset completion by-date
Given I am logged in as "admin"
And the following "core_badges > Badge" exists:
| name | Site Badge |
| status | 0 |
| description | Site badge description |
| image | badges/tests/behat/badge.png |
And I navigate to "Badges > Manage badges" in site administration
And I press "Edit" action in the "Site Badge" report row
And I select "Criteria" from the "jump" singleselect
And I set the field "type" to "Completing a set of courses"
And I set the field "courses" to "Course 1, Course 2"
And I press "Add courses"
And I expand all fieldsets
# Set a very high grade requirement to ensure that the course completion criteria won't be marked completed just by passing the quiz.
And I set the field with xpath "(//input[contains(@name, 'grade')])[1]" to "200"
And I set the field with xpath "(//input[contains(@name, 'enabled')])[2]" to "1"
And I set the field with xpath "(//select[contains(@name, 'day')])[2]" to "1"
And I set the field with xpath "(//select[contains(@name, 'month')])[2]" to "January"
And I set the field with xpath "(//select[contains(@name, 'year')])[2]" to "<year>"
And I click on "Any of the selected courses is complete" "radio"
And I press "Save"
And I press "Enable access"
And I click on "Enable" "button" in the "Confirm" "dialogue"
And user "student1" has attempted "Test quiz name C1" with responses:
| slot | response |
| 1 | True |
And user "student1" has attempted "Test quiz name C2" with responses:
| slot | response |
| 1 | True |
# Completion cron won't mark the whole course completed unless the
# individual criteria was marked completed more than a second ago. So
# run it twice, first to mark the criteria and second for the course.
And I run the scheduled task "core\task\completion_regular_task"
And I wait "1" seconds
And I run the scheduled task "core\task\completion_regular_task"
When I run the scheduled task "core\task\badges_cron_task"
And I run all adhoc tasks
And I navigate to "Badges > Manage badges" in site administration
And I follow "Site Badge"
Then I should see "Recipients (<count>)"
And I select "Recipients (<count>)" from the "jump" singleselect
And I <student1_visibility> see "Student 1"
And I should not see "Student 2"
Examples:
| year | count | student1_visibility |
| ## +1 year ## %Y ## | 1 | should |
| ## -1 year ## %Y ## | 0 | should not |
@@ -104,6 +104,9 @@ class block_completionstatus extends block_base {
// Flag to set if current completion data is inconsistent with what is stored in the database.
$pending_update = false;
// Get activities visible to the user that have completion enabled.
$visibleactivities = $info->get_user_activities_with_completion($USER->id);
// Loop through course criteria.
foreach ($completions as $completion) {
$criteria = $completion->get_criteria();
@@ -115,6 +118,11 @@ class block_completionstatus extends block_base {
// Activities are a special case, so cache them and leave them till last.
if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// Only include activities that are visible to the user.
if (!isset($visibleactivities[$criteria->moduleinstance])) {
continue;
}
$activities[$criteria->moduleinstance] = $complete;
if ($complete) {
@@ -119,10 +119,21 @@ $pendingupdate = false;
// Load criteria to display.
$completions = $info->get_completions($user->id);
// Get activities visible to the user that have completion enabled.
$visibleactivities = $info->get_user_activities_with_completion($user->id);
// Loop through course criteria.
foreach ($completions as $completion) {
$criteria = $completion->get_criteria();
// Skip display of activity completion criteria for activities the user cannot see.
if (
$criteria->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY &&
!isset($visibleactivities[$criteria->moduleinstance])
) {
continue;
}
if (!$pendingupdate && $criteria->is_pending($completion)) {
$pendingupdate = true;
}

Some files were not shown because too many files have changed in this diff Show More