Compare commits

..

536 Commits

Author SHA1 Message Date
Huong Nguyen d1321ff0d5 Moodle release 4.5.7 2025-10-04 09:03:40 +07:00
Huong Nguyen e25fbec380 NOBUG: Add upgrade notes 2025-10-04 09:03:40 +07:00
AMOS bot 8464ca1b72 Automatically generated installer lang files 2025-10-04 00:07:57 +00:00
Shamim Rezaie 03429740d2 weekly release 4.5.6+ 2025-10-03 20:02:28 +10:00
Mihail Geshoski 32897778cd MDL-75087 mod_assign: Fix failing behat tests in classic 2025-10-03 14:22:34 +07:00
Mihail Geshoski 1e2dcb145f MDL-75087 mod_assign: Fix failing unit test 2025-10-02 18:09:35 +07:00
Rajneel Totaram df9109fa9e MDL-75087 mod_assign: Respect assignment timer
This patch has better handling of timed assignments.
Users cannot directly access the submission page without
confirming to begin a timed assignment.
2025-10-02 11:02:50 +08:00
raortegar ecc53ae6a1 MDL-86253 mod_assign: Respect enrollment status in notifications 2025-10-02 08:15:28 +07:00
raortegar e37eadb84b MDL-86253 mod_quiz: Respect enrollment status in notifications 2025-10-02 08:15:28 +07:00
David Woloszyn d893d639d0 MDL-86494 router: Display error details only when debugging is on 2025-10-01 01:30:20 +00:00
yusufwib01 8d365edffe MDL-86353 mod_assign: Sync exception handling in PdfType and PdfReader 2025-10-01 01:30:20 +00:00
Paul Holden 7a5a3f261e MDL-85421 cohort: limit requests for all cohorts to system context. 2025-10-01 01:30:20 +00:00
David Woloszyn a8ecc443fe MDL-86327 auth_webservice: Stricter check on login 2025-10-01 01:30:20 +00:00
yusufwib01 0b32ae0b46 MDL-86261 calendar: Improve event form to show accessible groups 2025-10-01 01:30:20 +00:00
raortegar 519a09da8b MDL-86334 tool_mfa: Improve multi-factor authentication validation. 2025-10-01 01:30:20 +00:00
Huong Nguyen beb129d286 Merge branch 'MDL-85546-405' of https://github.com/raortegar/moodle into MOODLE_405_STABLE 2025-10-01 08:21:56 +07:00
raortegar 1849cca694 MDL-85546 tool_mfa: Improve handling of require MFA conditions 2025-09-30 11:13:25 +02:00
Huong Nguyen 628bb639ae Merge branch 'MDL-86667-405' of https://github.com/stevandoMoodle/moodle into MOODLE_405_STABLE 2025-09-29 13:36:53 +07:00
Stevani Andolo 8e409341bd MDL-86667 registration: Unset fields after site being unregistered 2025-09-29 10:35:04 +08:00
cescobedo f856c7ba24 weekly release 4.5.6+ 2025-09-26 13:44:24 +02:00
cescobedo 7a281ba10c NOBUG: Add upgrade notes 2025-09-26 13:44:24 +02:00
cescobedo d0a9dba00d Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-09-26 13:44:11 +02:00
Sara Arjona 37d10d8350 MDL-86488 core: Fix to remove whitespaces 2025-09-26 13:20:05 +02:00
Huong Nguyen 9c3ea97d9b Merge branch 'MDL-86511-405' of https://github.com/andimendunia/moodle into MOODLE_405_STABLE 2025-09-26 11:15:54 +07:00
Andi Permana 62ae269490 MDL-86511 tiny_link: TinyMCE fails to insert a link using enter key 2025-09-26 11:13:33 +07:00
Jun Pataleta 9f97900524 Merge branch 'MDL-85684-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-26 11:30:58 +08:00
Huong Nguyen e778f6fbbd Merge branch 'mdl-84491_redis_connection_405' of https://github.com/ngandrass/moodle into MOODLE_405_STABLE 2025-09-26 10:21:01 +07:00
Mihail Geshoski ee439d8fa8 Merge branch 'MDL-85803-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-26 11:10:14 +08:00
Jun Pataleta de0150412b Merge branch 'MDL-86363-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-26 11:07:51 +08:00
Jun Pataleta d1a9ba5386 Merge branch 'MDL-86383-405-enfix' of https://github.com/mudrd8mz/moodle into MOODLE_405_STABLE 2025-09-26 09:48:14 +08:00
Huong Nguyen d1a3d40f65 Merge branch 'MDL-85419-405' of https://github.com/sharpchi/moodle into MOODLE_405_STABLE 2025-09-26 08:47:01 +07:00
Huong Nguyen 7b0eac6677 Merge branch 'MDL-80447-405-4' of https://github.com/raortegar/moodle into MOODLE_405_STABLE 2025-09-26 08:25:06 +07:00
Paul Holden 1044306ce9 MDL-85803 mod_scorm: re-define language string for deleting attempts.
The previous version was re-purposed in 22cc9757 so lost meaning and
context elsewhere. Define new string for course reset elements.
2025-09-25 16:03:32 +01:00
Huong Nguyen 8648dc6046 MDL-80447 repository_googledocs: Add missing fields 2025-09-25 15:37:04 +02:00
Huong Nguyen f7f1a4562d MDL-80447 repository_googledocs: Switch to use core/pubsub 2025-09-25 15:37:04 +02:00
Tony Butler 30b02b20ef MDL-58001 mod_assign: Return early if no grade items are available 2025-09-25 12:04:04 +02:00
Amaia Anabitarte 94d260a4f8 MDL-58001 core_grades: New is_gradable() function in gradelib
Backported from main, introduced in MDL-85837.
2025-09-25 12:03:55 +02:00
Mihail Geshoski 63c2d91bc5 Merge branch 'MDL-86432-405' of https://github.com/timhunt/moodle into MOODLE_405_STABLE 2025-09-25 14:12:08 +08:00
Jun Pataleta 4fdc0b6768 Merge branch 'MDL-86599_405' of https://github.com/timhunt/moodle into MOODLE_405_STABLE 2025-09-25 12:08:36 +08:00
Mihail Geshoski 9883338e8c Merge branch 'MDL-85675-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2025-09-25 11:53:45 +08:00
Mihail Geshoski 08ccef4aec Merge branch 'MDL-85166_405_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_405_STABLE 2025-09-25 10:51:42 +08:00
Mihail Geshoski cc69e589a4 Merge branch 'MDL-84085-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-25 09:57:24 +08:00
AMOS bot 29857c9cde Automatically generated installer lang files 2025-09-25 00:07:52 +00:00
Andrew Nicols 2f6ec1d60a MDL-86432 qtype_ordering: Fix broken data provider 2025-09-24 17:53:38 +01:00
Amaia Anabitarte 34ce92a046 Merge branch 'MDL-86738-405' of https://github.com/sarjona/moodle into MOODLE_405_STABLE 2025-09-24 17:29:17 +02:00
Amaia Anabitarte e96ec9501a Merge branch 'MDL-86488-405-2' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-24 17:16:54 +02:00
Sara Arjona 76997fbf33 MDL-86738 badges: Relocate upgrade notes from MDL-86174
The upgrade notes were incorrectly placed outside the .upgradenotes/ directory
in MDL-86174. This commit moves them to the correct location.
2025-09-24 13:17:51 +02:00
raortegar 8135ded9e3 MDL-80447 repository_googledocs: Update Google Drive OAuth2 scope tests 2025-09-24 11:42:24 +02:00
raortegar c8f0320b24 MDL-80447 repository_googledocs: Update Google Drive OAuth2 scope
Updated the Google Drive integration to use the drive.file scope instead
of the broader drive scope. This change removes support for shared drives,
which rely on full access. As a result, unit tests were updated to reflect
this behavior by bypassing shared drive checks and expectations.
2025-09-24 11:42:23 +02:00
raortegar ab4c436f10 MDL-80447 core_repository: Add upload button to filepicker repository 2025-09-24 11:42:05 +02:00
Mihail Geshoski 9cf08382ae Merge branch 'MDL-83462-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-24 16:16:04 +08:00
Mihail Geshoski 85d0a666a3 Merge branch 'MDL-85682-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-24 10:20:31 +08:00
David Mudrák 0ea18ecce6 MDL-86383 lang: Use the fixed strings in tests, too 2025-09-23 20:47:24 +02:00
Helen Foster 38a820017a MDL-86383 lang: Import fixed English strings (en_fix) 2025-09-23 20:46:43 +02:00
Sara Arjona 4f6526ddb1 weekly release 4.5.6+ 2025-09-23 17:21:13 +02:00
Sara Arjona bf9b563340 NOBUG: Add upgrade notes 2025-09-23 17:21:13 +02:00
Huong Nguyen 02c4d8c217 Merge branch 'MDL-84504-maintenance-log-MOODLE_405_STABLE' of https://github.com/bwalkerl/moodle into MOODLE_405_STABLE 2025-09-23 18:38:17 +08:00
Huong Nguyen 321a86a858 Merge branch 'MDL-86586-405-choice-dropdown' of https://github.com/sharpchi/moodle into MOODLE_405_STABLE 2025-09-23 08:43:34 +07:00
Sara Arjona 2d2fd03b2c Merge branch 'MDL-86596-integration-405' of https://github.com/mihailges/moodle into MOODLE_405_STABLE 2025-09-22 10:13:02 +02:00
Mihail Geshoski 9ce91a9588 Merge branch 'MDL-86512-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-22 15:07:24 +08:00
Mihail Geshoski b2293b37f3 MDL-86596 tool_profiling: Revert string removal without deprecation 2025-09-22 13:09:56 +08:00
Huong Nguyen afab4c9d25 Merge branch 'MDL-84870-405' of https://github.com/andimendunia/moodle into MOODLE_405_STABLE 2025-09-22 08:28:44 +07:00
Huong Nguyen f1e8b839f8 Merge branch 'MDL-83066-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-22 08:24:26 +07:00
Brendan Heywood b7a3d429e2 MDL-84504 cli: Add config logging for cli maintenance mode 2025-09-22 10:13:52 +10:00
Brendan Heywood 10b28ebb08 MDL-84504 core: Allow setting config and logging with one call 2025-09-22 10:13:52 +10:00
Mihail Geshoski dfaa45838e Merge branch 'MDL-83080-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-21 22:47:29 +08:00
Mihail Geshoski af0713a6b7 Merge branch 'MDL-86596-405' of https://github.com/yerairogo/moodle into MOODLE_405_STABLE 2025-09-21 22:29:30 +08:00
Amaia Anabitarte 5ee8e97b61 Merge branch 'MDL-86174-405' of https://github.com/sarjona/moodle into MOODLE_405_STABLE 2025-09-19 16:11:27 +02:00
Amaia Anabitarte 91c8edfbef Merge branch 'MDL-84815-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-19 16:08:36 +02:00
Mark Sharp ebdd862f3c MDL-86586 core_form: Fixed dropdown JS error if no element found 2025-09-19 14:46:51 +01:00
Sara Arjona eb664d6d41 MDL-86174 badges: Add Canvas Credentials paid plan alert 2025-09-19 15:38:12 +02:00
Yerai Rodríguez c4f1af803c MDL-86596 Profiling: add breadcrumb node to profiling detail page
Boost theme removes the last item action.
This adds an extra node so the profile run details page breadcrumb is clickable in both Classic and Boost themes.
2025-09-19 10:54:14 +02:00
Huong Nguyen 0418c85bb4 weekly release 4.5.6+ 2025-09-19 15:33:57 +07:00
Huong Nguyen d5bad6b64d MDL-80053 core_grade: Fix illegal whitespaces 2025-09-19 15:15:55 +07:00
Huong Nguyen c922b363c4 Merge branch 'MDL-83991_405' of https://github.com/panchalrohit/moodle into MOODLE_405_STABLE 2025-09-19 08:57:11 +07:00
Mihail Geshoski 38be896581 Merge branch 'mdl-83857_405' of https://github.com/james-cnz/moodle into MOODLE_405_STABLE 2025-09-19 09:52:55 +08:00
James C 6efd33e7b3 MDL-83857 course: Use set_sectionid not num 2025-09-19 11:35:15 +12:00
Paul Holden 0a87d4507b Merge branch 'MDL-85616-405_glossary_undefined_variable' of https://github.com/davidherney/moodle into MOODLE_405_STABLE 2025-09-18 19:23:23 +01:00
Jun Pataleta 907a3442e9 MDL-86488 componentlibrary: Improved documentation for the action menu 2025-09-18 17:25:22 +08:00
Jun Pataleta 5e20e1d8b8 MDL-86488 mod_subsection: Click on the menu button itself, not the icon 2025-09-18 17:25:22 +08:00
Jun Pataleta 7c6d7a9e1f MDL-86488 behat: Click on the menu button itself, not the icon 2025-09-18 17:25:21 +08:00
Jun Pataleta c131932352 MDL-86488 qbank_columnsortorder: Use action menu Behat steps 2025-09-18 17:25:21 +08:00
Jun Pataleta c7d4f457d7 MDL-86488 behat: Update xpath for action menu trigger's label 2025-09-18 17:25:21 +08:00
Jun Pataleta 4e09d70272 MDL-86488 mod_data: Fix Behat tests for checking action menu items 2025-09-18 17:25:21 +08:00
Jun Pataleta 05d0c9b002 MDL-86488 grade: Move menu trigger label outside the pix icon
* The pix icons must be decorative, and the label be put within
the menu trigger itself as a visually hidden text.
2025-09-18 17:25:21 +08:00
Jun Pataleta e309724b41 MDL-86488 course: Set action menu icon for category actions menu 2025-09-18 17:25:20 +08:00
Jun Pataleta de7c1fdbe1 MDL-86488 mod_lti: Move menu trigger label outside the pix icon 2025-09-18 17:25:20 +08:00
Jun Pataleta 8c5c5eaee4 MDL-86512 mod_assign: Make the select column header name more accessible 2025-09-18 13:55:46 +08:00
Mihail Geshoski e19a406db8 Merge branch 'MDL-80053-405' of https://github.com/Jayce0808/moodle into MOODLE_405_STABLE 2025-09-17 23:33:09 +08:00
Paul Holden 39d9131ad7 Merge branch 'MDL-86454-405' of https://github.com/sh-csg/moodle into MOODLE_405_STABLE 2025-09-17 16:04:36 +01:00
Paul Holden d9a7733e16 Merge branch 'MDL-86541-405' of https://github.com/dfelipemonroy/moodle into MOODLE_405_STABLE 2025-09-17 11:52:57 +01:00
Amaia Anabitarte a11ccf13b6 Merge branch 'MDL-86490-405-2' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-17 11:52:22 +02:00
Jun Pataleta dc3598bcb4 MDL-86488 output: Make the icon in action menu triggers decorative
* Set the action label properly.
* Set the kebab icon as a decorative icon.
* Move the title attribute to the menu button itself instead of the
kebab icon.
2025-09-17 15:27:00 +08:00
Jun Pataleta 095ce67f60 MDL-86490 mod_feedback: a11y improvements for delete template buttons
* Assign a button role to the action_icon.
* Increase target size by adding btn classes.
2025-09-16 22:43:05 +08:00
Jun Pataleta 81a0fbee7f MDL-86490 tool: Click on the action link itself and not on its icon 2025-09-16 22:43:05 +08:00
Jun Pataleta ce4b7b94e0 MDL-86490 output: Mark icon in action_icon as decorative 2025-09-16 22:43:04 +08:00
Jun Pataleta 5d8db4b548 MDL-86512 core: Ensure bulk action selection count announcement
When the bulk actions are first rendered, the item count in the
aria-live region will not be announced by screen readers as aria-live
regions need to be present in the DOM and changed before screen readers
can announce their content.

This commit ensures that the item selection count will be announced
when the bulk actions get added to the DOM by calling
`updateBulkItemSelection()` after rendering the bulk actions HTML. This
removes the non-breaking space in the template, effectively causing a
change in DOM without affecting the visuals, and allowing screen readers
to announce the contents of the item count.
2025-09-16 22:38:18 +08:00
Jun Pataleta 4b9ad6b9fd MDL-86512 core: Assign status role for the number of selected items
Add a status role for the container containing the number of selected
items for bulk actions so that screen readers will announce this
number when an item gets selected for bulk action.
2025-09-16 22:38:18 +08:00
Huong Nguyen b10dacbc75 weekly release 4.5.6+ 2025-09-16 20:58:58 +07:00
Jayce 9d9d52a404 MDL-80053 core_grade: fixed exception message appearing
- Fixing the exceptions shows an incorrectly formatted table
- The filler cell is only used to take space on the left rows but when the right rows are empty, it is not needed.
2025-09-16 13:43:06 +09:30
Jun Pataleta ee4dd60780 Merge branch 'MDL-86495-405-patch' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2025-09-16 10:16:29 +08:00
Mihail Geshoski a923f3724b Merge branch 'MDL-86558-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-09-16 09:55:16 +08:00
Huong Nguyen 641528e9d3 MDL-86495 core: Improve PHPUnit for IPLookup 2025-09-16 08:48:23 +07:00
Amaia Anabitarte 7d24bb77b3 Merge branch 'MDL-85541-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-15 14:57:06 +02:00
Amaia Anabitarte d5521c1871 Merge branch 'MDL-86157-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-15 14:50:46 +02:00
Paul Holden 6bbceaa405 MDL-86157 mod_glossary: correctly format entry page title. 2025-09-15 12:00:48 +01:00
Paul Holden f6c66d2be9 MDL-83080 message: handle core\url instance data as contexturl. 2025-09-15 10:40:28 +01:00
Andi Permana bb18341e72 MDL-84870 core: Update security.txt expiry 2025-09-15 14:31:04 +07:00
Huong Nguyen c93be20aa5 Merge branch 'MDL-79362-405' of https://github.com/abias/moodle into MOODLE_405_STABLE 2025-09-15 11:41:00 +07:00
Jun Pataleta 0214ea3e19 Merge branch 'MDL-86495-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2025-09-15 10:25:28 +08:00
Tim Hunt 3b847d4860 MDL-86599 assignment: add hidden captions to the table in the assign UI
This helps screen-reader users trying to navigate quickly to the right table.
2025-09-12 15:31:25 +01:00
Paul Holden 44dbf84bcb MDL-84085 mod_h5pactivity: account for missing ra table join alias. 2025-09-12 11:38:24 +01:00
Shamim Rezaie a3b82724be weekly release 4.5.6+ 2025-09-12 18:35:04 +10:00
Jun Pataleta 4ded9db847 Merge branch 'MDL-86294-M405_document-db-options' of https://github.com/ziegenberg/moodle into MOODLE_405_STABLE 2025-09-11 17:12:27 +08:00
Paul Holden ebeb8b6af2 Merge branch 'MDL-86330-405' of https://github.com/yerairogo/moodle into MOODLE_405_STABLE 2025-09-10 16:26:45 +01:00
Paul Holden bbda6fabe0 Merge branch 'MDL-86501-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-10 16:10:28 +01:00
Paul Holden 850b45a996 Merge branch 'MDL-86447_MOODLE_405_STABLE' of https://github.com/dcmexy/moodle into MOODLE_405_STABLE 2025-09-10 15:49:58 +01:00
Mihail Geshoski edbd895f60 Merge branch 'MDL-86479-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-10 22:17:22 +08:00
Paul Holden 01834f4bc1 Merge branch 'MDL-86489-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-10 15:04:08 +01:00
Diego Monroy c9ee1c582d MDL-86541 blocks: validate allowing block title to be '0' (zero). 2025-09-10 12:29:41 +02:00
Mihail Geshoski 00970d6e59 Merge branch 'MDL-86498-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-10 17:43:44 +08:00
Jun Pataleta 170a6056de MDL-86501 form: Fix unit tests
The change in the calendar button from an `<a>` tag to a native
`<button>` tag means the `\HTML_QuickForm_group::getValue()` will have
a different result and the `test_onquickformevent()` tests must be
adjusted accordingly.
2025-09-10 11:44:20 +08:00
Huong Nguyen ffc6111f4e Merge branch 'MDL-84076-MOODLE_405_STABLE' of https://github.com/PhMemmel/moodle into MOODLE_405_STABLE 2025-09-10 09:10:16 +07:00
Huong Nguyen a8e32ae7f3 Merge branch 'MDL-85270-m45' of https://github.com/micaherne/moodle into MOODLE_405_STABLE 2025-09-10 09:08:26 +07:00
Huong Nguyen ea7b951d3c Merge branch 'MDL-85266-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-10 09:05:49 +07:00
Paul Holden c0cd20aa61 Merge branch 'MDL-86499-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-09 14:15:36 +01:00
Paul Holden a0ffa26132 Merge branch 'MDL-86501-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-09 14:01:03 +01:00
Jun Pataleta f27faa1305 MDL-86498 badges: Empty string for empty links 2025-09-09 18:16:46 +08:00
Jun Pataleta ebc1084cdc MDL-86498 badges: Add label for badge navigation url_select 2025-09-09 18:16:46 +08:00
Jun Pataleta 0357a43862 MDL-86498 badges: Accessibility coverage for badge details and overview 2025-09-09 18:16:45 +08:00
Jake Dallimore 85a77b7a6c weekly release 4.5.6+ 2025-09-09 15:06:28 +08:00
Jake Dallimore e42e6b6408 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-09-09 15:06:22 +08:00
Huong Nguyen 8e52bbe341 Merge branch 'MDL-83003-M405_INFO-command-not-compatible-with-envoy-proxy-redis' of https://github.com/ziegenberg/moodle into MOODLE_405_STABLE 2025-09-09 09:29:23 +07:00
Huong Nguyen 3204204ad9 MDL-86495 core: Better errors handling for IP Lookup
- Since `Geoplugin` is not free anymore, a new admin setting called `GeoPlugin API key` has been added for user to input
  their API key if they still want to use `Geoplugin` service.
- The `iplookup_find_location()` method has been improved to handle error responses more effectively.
2025-09-09 08:44:59 +07:00
Rajneel Totaram cc335884de MDL-86558 mod_quiz: Fix styles for sticky columns 2025-09-09 11:32:17 +12:00
Michael Aherne 30c73fa641 MDL-85270 output: Export url_select for template. 2025-09-08 10:11:44 +01:00
Mark Sharp 44f9fb5150 MDL-85419 core_courseformat: Move event trigger to after cm cache purge 2025-09-08 10:06:23 +01:00
Huong Nguyen b9af7918c8 Merge branch 'MDL-85335-405' of https://github.com/gbarat87/moodle into MOODLE_405_STABLE 2025-09-08 08:30:32 +07:00
Mihail Geshoski b9a6b71e3c Merge branch 'MDL-86374-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-08 09:22:34 +08:00
AMOS bot 9f330b6b40 Automatically generated installer lang files 2025-09-06 00:07:55 +00:00
Huong Nguyen dcc7e81ac2 weekly release 4.5.6+ 2025-09-05 23:54:09 +07:00
Huong Nguyen 54e319351a Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-09-05 23:53:58 +07:00
Philipp Memmel bfeda89d8c MDL-84076 core_templates: Avoid manipulation of template context object 2025-09-05 09:59:10 +02:00
Yerai Rodríguez 84306938ac MDL-86330 enrol: remove time rounding to avoid missing active enrolments
Rounding time could lead to newest enrolments being excluded, resulting in
these courses not appearing under the "My courses" tab in the Dashboard until the rounded time aligned with the actual time.

enrol_get_all_users_courses() now uses the PSR-20 clock to provide stable
and testable time values.
2025-09-05 08:22:09 +02:00
Huong Nguyen a46f6637cc Merge branch 'MDL-83628-m405' of https://github.com/sammarshallou/moodle into MOODLE_405_STABLE 2025-09-05 09:22:00 +07:00
Simey Lameze e756ec3228 MDL-85675 behat: add timefinish to quiz attempt generator
Adds support for the timefinish attribute to the quiz attempt
generator allowing Behat scenarios to specify the exact completion
time of an attempt.

This is necessary to simulate a scenario where the student finished
the attempt few minutes ago for example.

This commit also adds two scenarios that automates MDLQA-307.
2025-09-05 10:07:37 +08:00
Mihail Geshoski 1c8e7f1c7b Merge branch 'MDL-74046-405' of https://github.com/muhammadarnaldo/moodle into MOODLE_405_STABLE 2025-09-04 23:45:02 +08:00
ferran e6c202c420 Merge branch 'MDL-85416_405' of https://github.com/timhunt/moodle into MOODLE_405_STABLE 2025-09-04 16:31:26 +02:00
ferran f666a0d059 Merge branch 'MDL-72956-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-04 16:10:41 +02:00
Huong Nguyen dfa3176e03 Merge branch 'MDL-86230-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-04 15:44:28 +07:00
Huong Nguyen 8d5cec0164 Merge branch 'MDL-72511-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-09-04 15:42:14 +07:00
ferran 7636570179 Merge branch 'MDL-85641-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-09-04 10:11:25 +02:00
Muhammad Arnaldo ae21155775 MDL-74046 tool_lp: new-tab for competency frameworks repo link 2025-09-04 14:41:54 +07:00
Rajneel Totaram b637072b76 MDL-85641 mod_feedback: Use full width for reponses page 2025-09-04 10:05:01 +12:00
Paul Holden f10d3c78e0 Merge branch 'MDL-85619-405' of https://github.com/mihailges/moodle into MOODLE_405_STABLE 2025-09-03 12:38:32 +02:00
Paul Holden aab8839ab6 Merge branch 'MDL-84982-405' of https://github.com/NashTechOpenUniversity/moodle into MOODLE_405_STABLE 2025-09-03 12:29:55 +02:00
Paul Holden 7b6463fbba Merge branch 'MDL-86187-MOODLE_405_STABLE' of https://github.com/phmemmel/moodle into MOODLE_405_STABLE 2025-09-03 12:16:20 +02:00
Mihail Geshoski 42a7c6df26 Merge branch 'MDL-82244-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-03 12:21:01 +08:00
Mihail Geshoski a3968e0b73 Merge branch 'MDL-73671-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-03 12:15:04 +08:00
Mihail Geshoski aec7468f67 Merge branch 'MDL-83474-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-03 12:07:26 +08:00
AMOS bot ee36d500c9 Automatically generated installer lang files 2025-09-03 00:07:52 +00:00
ferran 6cf9ed0f40 Merge branch 'MDL-71786-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-09-02 17:03:50 +02:00
Tim Hunt d0be2a57ce MDL-85416 assignment: fix date format in messages
For no very obvious reason, the date in the 'You have just
submitted your assignemnt' message was formatted differently
from the default used on-screen.

This commit makes it consistent.
2025-09-02 15:34:23 +02:00
Michael Hawkins 2653626ca0 weekly release 4.5.6+ 2025-09-02 20:32:36 +08:00
Michael Hawkins ace600fe84 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-09-02 20:32:15 +08:00
Mihail Geshoski 28fe42c4ec Merge branch 'mdl-84975_405' of https://github.com/james-cnz/moodle into MOODLE_405_STABLE 2025-09-02 09:43:59 +08:00
Mihail Geshoski c27fde3701 Merge branch 'MDL-86366-405' of https://github.com/lucaboesch/moodle into MOODLE_405_STABLE 2025-09-01 23:18:58 +08:00
Paul Holden a9cf380584 MDL-71786 mod_assign: shift activity grade reminder dates during reset.
Co-authored-by: aneno-m-e <43393997+aneno-m-e@users.noreply.github.com>
2025-09-01 17:07:05 +02:00
Paul Holden 21cb028390 MDL-73671 mod_forum: shift activity due/cutoff dates during reset. 2025-09-01 16:55:14 +02:00
Mihail Geshoski d8cbfc285a Merge branch 'MDL-85018_405_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_405_STABLE 2025-09-01 22:28:08 +08:00
Mihail Geshoski bc3f269ac8 Merge branch 'MDL-73417-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-09-01 22:17:58 +08:00
Jun Pataleta 7a6e74a9fe MDL-86501 form: Fix date selector calendar button mark up
* Make the calendar icon decorative.
* Mark up the calendar button accordingly:
  - Render it as a `<button type="button">`
  - Add label using `aria-label`.
  - Use a more suitable label of "Date picker" rather than "Calendar"
  - Add a `title` attribute to show a tooltip for sighted users.
  - Disable/enable it accordingly by setting/removing the `disabled`
    attribute.
2025-09-01 16:08:54 +08:00
AMOS bot c489b7ac0a Automatically generated installer lang files 2025-09-01 00:07:49 +00:00
Luca Bösch 69c42304f0 MDL-86366 quiz: Description shouldn't show each secure page, but title. 2025-08-31 21:27:04 +02:00
Paul Holden dea5950384 MDL-85266 tool_uploaduser: correctly key bulk users session array. 2025-08-29 15:50:14 +01:00
Paul Holden 6aec3fe946 MDL-84815 user: fix selector element search checkbox alignment.
The removal in d0d62fd0 of the `.form-inline` class caused the
mis-alignment of checkbox inputs in the "Search options" container.

Restore behaviour provided by the aforementioned class in descendent
elements by applying corresponding styles, that were lost with that
change.
2025-08-29 15:48:35 +01:00
Jun Pataleta 72fe24b91a MDL-86479 course: Add accessibility coverage for course index options 2025-08-29 21:05:22 +08:00
cescobedo a7c0d3605a weekly release 4.5.6+ 2025-08-29 15:01:14 +02:00
cescobedo d53466d1c6 NOBUG: Add upgrade notes 2025-08-29 15:01:14 +02:00
Jun Pataleta cc10e8e245 MDL-86489 mod_feedback: Add accessibility coverage for Questions tab
* Also fix the heading level issue found by the test.
2025-08-29 20:49:10 +08:00
Jun Pataleta 65bddce33c MDL-86499 core: Make table sort icons decorative 2025-08-29 12:23:59 +08:00
Huong Nguyen 4bb0555b62 Merge branch 'MDL-86343-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-29 11:04:02 +07:00
Huong Nguyen 63448b6f98 Merge branch 'MDL-85743-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-29 11:00:49 +07:00
Mihail Geshoski bc472d3ea1 Merge branch 'MDL-83256-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-29 11:31:45 +08:00
Huong Nguyen ed9673fe1f Merge branch 'MDL-86452-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2025-08-29 08:46:20 +07:00
Huong Nguyen 004cc09738 Merge branch 'MDL-86301-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2025-08-29 08:41:28 +07:00
Paul Holden a41dc037c9 MDL-82244 course: use site default course visibility in external call. 2025-08-28 17:40:09 +01:00
Amaia Anabitarte 4158ea095b Merge branch 'MDL-86280-405' of https://github.com/lucaboesch/moodle into MOODLE_405_STABLE 2025-08-28 11:17:27 +02:00
Jun Pataleta 337346e3d1 MDL-86489 mod_feedback: Mark required icon as decorative
* Set `alt` param as empty, but add `title` attribute for the sake of
sighted users.
* Add a visually hidden string that indicates the element is required.
2025-08-28 16:51:59 +08:00
Jun Pataleta b04e0bd518 MDL-86479 theme_boost: Fix a11y issues on the course index options
* Add aria-label for the options menu button.
* Add proper menu and menuitem roles for the options menu.
2025-08-28 13:42:33 +08:00
Andrew Nicols 028728b561 MDL-86452 core: Reset navigation data between tests 2025-08-28 13:13:05 +08:00
Stefan Hanauska b33729ab1d MDL-86454 badges: Hide revoke button if not available 2025-08-27 10:08:21 +02:00
Dean Chimezie f69c22ecda MDL-86447 tool_mfa: Restore setup lang string 2025-08-26 13:40:31 -04:00
cescobedo ca2c397895 weekly release 4.5.6+ 2025-08-26 15:54:09 +02:00
cescobedo 7dfc4481d4 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-08-26 15:53:56 +02:00
Mihail Geshoski dc74787166 Merge branch 'MDL-85677-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2025-08-26 11:57:04 +08:00
Mihail Geshoski a6146d1c58 Merge branch 'MDL-85229-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-26 11:48:45 +08:00
Huong Nguyen ca050231fb Merge branch 'MDL-86315-405' of https://github.com/davewoloszyn/moodle into MOODLE_405_STABLE 2025-08-26 09:23:30 +07:00
Simey Lameze e4e7d72571 MDL-85677 behat: test to verify SEB download link visibility 2025-08-26 09:17:47 +08:00
Mihail Geshoski 938dbde96c Merge branch 'MDL-85676-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2025-08-26 00:43:21 +08:00
Andrew Nicols 15f30a7314 MDL-86301 core: Create autoloadable externallib_testcase 2025-08-25 15:14:03 +08:00
Simey Lameze 3bb18b29e0 MDL-85676 behat: validate SEB attempt message 2025-08-25 14:08:03 +08:00
Luca Bösch c69a91921e MDL-86280 mod: Do not filter help text in activity chooser.
Co-authored-by: Ferran Recio <ferran@moodle.com>
2025-08-24 22:42:25 +02:00
AMOS bot bb56dbdc74 Automatically generated installer lang files 2025-08-24 00:09:58 +00:00
Mihail Geshoski dc3c3ca98a weekly release 4.5.6+ 2025-08-22 20:40:21 +08:00
Mihail Geshoski 15bc48978d NOBUG: Add upgrade notes 2025-08-22 20:40:21 +08:00
Huong Nguyen a93254f43c MDL-73845 enrol_database: Fix MySQL issue 2025-08-22 12:58:48 +08:00
Huong Nguyen 77beaab116 Merge branch 'MDL-86231-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2025-08-22 08:46:55 +07:00
Jun Pataleta ce3a869c07 MDL-81455 behat: Add accessibility coverage to default start page tests 2025-08-21 17:50:44 +08:00
Jun Pataleta e76ec78b37 Merge branch 'MDL-81455-405' of https://github.com/rajutm25/moodle into MOODLE_405_STABLE 2025-08-21 17:48:08 +08:00
Jun Pataleta fe0f5baa2d Merge branch 'MDL-86202-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-08-21 15:20:51 +08:00
Rajneel Totaram caf61abd94 MDL-86202 mod_lesson: Use correct heading level 2025-08-21 18:39:33 +12:00
Mihail Geshoski 179848ea1f Merge branch 'MDL-84850-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-21 11:44:26 +08:00
Huong Nguyen b4c7d832b7 Merge branch 'MDL-80611-405' of https://github.com/marinaglancy/moodle into MOODLE_405_STABLE 2025-08-21 08:54:57 +07:00
Mihail Geshoski a061fd95bd Merge branch 'MDL-84739-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-20 17:22:51 +08:00
Paul Holden c9661b9a52 MDL-85684 mod_assign: format course/assignment notification content. 2025-08-20 09:14:51 +01:00
Paul Holden af96e46e56 MDL-83474 webservice: fix empty/zero $itemid handling in text format. 2025-08-20 09:08:32 +01:00
Huong Nguyen c1bdb76d18 Merge branch 'MDL-73845-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2025-08-20 08:41:43 +07:00
Paul Holden a97e536341 MDL-86374 report_outline: use formatted activity name in report. 2025-08-20 00:41:04 +01:00
Paul Holden 0e1de8d591 MDL-85743 user: correct debugging for missing user picture fields. 2025-08-19 17:12:40 +01:00
Paul Holden 51587e9909 MDL-84850 tag: ensure course tags enabled before adding to navigation. 2025-08-19 17:11:00 +01:00
Shamim Rezaie 43767635b7 weekly release 4.5.6+ 2025-08-20 00:45:53 +10:00
Shamim Rezaie 68776525be Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-08-20 00:45:43 +10:00
rajutm25 41728c3d87 MDL-81455 core: Fix welcome message heading in dashboard
The welcome message on login was incorrectly replacing the dashboard
page header.The change ensures the welcome message appears as a
paragraph while maintaining the dashboard page heading.
2025-08-19 19:47:39 +05:30
Paul Holden fcdcd9d7e6 MDL-86363 completion: ensure module viewed completion data is cleaned.
The privacy provider should ensure it removes said data consistently.
2025-08-19 11:17:48 +01:00
Mi Jia Looi 7e3e2077f0 MDL-73845 enrol_database: Add lock to prevent race condition 2025-08-19 15:36:19 +07:00
Jun Pataleta 2395392c99 MDL-72511 calendar: Better calendar navigation labelling
* Make the accessible labels of the previous and next month links more
informative by indicating the name of the previous/next months.
* Make titles on the previous and next month links reflect the actual
month and show titles only when the calendar is rendered within a block.
This is because the title attribute is only used for the sake for
sighted users who may need to know the meaning of the previous/next
month links, especially when the calendar is displayed in compact form
where the previous/next month name is not shown.
2025-08-19 11:26:18 +08:00
Jun Pataleta aef8d9e6f1 MDL-72511 calendar: Fix month_mini navigation
* Hide arrow icons from assistive technologies.
* Add a more sensible aria-label for the previous/next
month links.
2025-08-19 11:26:18 +08:00
Huong Nguyen e74c4acec4 Merge branch 'MDL-86109-405' of https://github.com/stevandoMoodle/moodle into MOODLE_405_STABLE 2025-08-19 09:53:44 +07:00
Mihail Geshoski 74c9f95c4c Merge branch 'MDL-83772-405' of https://github.com/srobotta/moodle into MOODLE_405_STABLE 2025-08-19 10:27:09 +08:00
Paul Holden 5ce61f4be4 MDL-86343 reportbuilder: fix course groupmode column type value.
Per a3c386af, this column does not produce numeric output. Also cleanup
redundancy in the default branch of corresponding switch statement.
2025-08-18 17:06:35 +01:00
Paul Holden c9a677dc7c MDL-84739 gradingform_rubric: consistently style rubric table icons. 2025-08-18 08:57:58 +01:00
Paul Holden e9b11f36d5 MDL-83256 user: account for course context instances of teacher fields. 2025-08-18 08:57:06 +01:00
Paul Holden e6bae7d01f MDL-85229 mod_url: observe description appearance setting on display.
Correctly determine whether said description is in fact "empty".
2025-08-18 08:55:55 +01:00
Paul Holden 9359d08bd6 MDL-83066 blocks: restore deletion warning for "sticky" blocks.
It was lost when switching to modal delete configuration in 529c5c2e.
2025-08-18 08:49:36 +01:00
Daniel Ziegenberg 7b1af357ff MDL-86294 core: Document missing dboptions in config-dist.php
For the full list of database config settings, the config-dist.php file
referred to https://docs.moodle.org/en/Database_settings, which in turn
pointed back to
https://github.com/moodle/moodle/blob/master/config-dist.php. We should
not play hide and seek with our configuration options, so let's document
them.

Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
2025-08-17 17:07:52 +02:00
AMOS bot ff4c249e39 Automatically generated installer lang files 2025-08-16 00:07:53 +00:00
Philipp Memmel 106fae466d MDL-86187 core_ddl: Add reserved keywords for MySQL 8.4 2025-08-15 11:17:40 +00:00
Paul Holden 2be96308d3 MDL-85682 mod_assign: account for absence of workflow select filter. 2025-08-15 10:48:22 +01:00
David Woloszyn 2bf704c153 MDL-86315 registration: Unset emails in hub when dependant value not set 2025-08-15 12:33:15 +10:00
Huong Nguyen e07cf88f4d weekly release 4.5.6+ 2025-08-15 09:27:00 +07:00
Stevani Andolo 31ce71608d MDL-86109 tiny_media: Fix selected subtitles and captions language
Ensures the selected subtitle or caption language is correctly retained
and displayed.
2025-08-15 10:13:33 +08:00
Paul Holden 84858b01cf MDL-72956 mod_feedback: remove session access for started activities. 2025-08-14 11:27:39 +01:00
Rajneel Totaram 969b1a7f98 MDL-73417 mod_assign: Remove duplicate array key 2025-08-14 14:34:15 +12:00
AMOS bot 33035402a4 Automatically generated installer lang files 2025-08-14 00:08:00 +00:00
AMOS bot 943801d2d9 Automatically generated installer lang files 2025-08-13 00:08:01 +00:00
guillaumebarat 59aef0a010 MDL-85335 cli: Allow uninstall of sub-plugin when parent missing 2025-08-12 15:07:29 +10:00
James C 20e708cf73 MDL-84975 course: Handle tags in section and activity move dialogues 2025-08-12 13:22:29 +12:00
Mark Johnson 47fb08fc6d MDL-85166 question: Update move_question_set_references
This backports the applicable part of MDL-85210 to 4.5.

move_question_set_references expected the set reference to use the old
filter format.

This change converts the filter to the new format if required, before
updating the category and context ids.
2025-08-11 10:55:50 +01:00
Mark Johnson a576b9db5d MDL-85018 questionlib: Update broken question test
A multianswer question with a missing database record no longer throws
an exception, so doesn't work for the "broken question" test, so switch
to ddwtos.
2025-08-11 10:37:05 +01:00
Mark Johnson 9cd0fe59a3 MDL-85018 qtype_multianswer: Prevent errors on backup/restore
If a multianswer question has its question_multianswer record missing
for some reason, attempting to backup and restore a bank containing the
question will result a dml_missing_record exception.

The qtype_multianswer::get_question_options() method already copes with
the sequence from this record being empty, so we can just remove the
MUST_EXIST check and let it fall back to that.
2025-08-11 10:37:05 +01:00
Paul Holden 66db7639e9 MDL-85541 gradereport_history: show user country value correctly. 2025-08-11 09:17:23 +01:00
Stephan Robotta 168d27f0b3 MDL-83772 mod_data: Fixed autosave bug in text areas with same name 2025-08-11 08:50:33 +02:00
Andrew Nicols cc7a7d2b2c MDL-86231 behat: Support array callable syntax in execute method 2025-08-08 11:42:30 +08:00
Mihail Geshoski fb02f4fa9f Moodle release 4.5.6 2025-08-08 10:48:30 +08:00
Mihail Geshoski 85a6851a8e NOBUG: Add upgrade notes 2025-08-08 10:48:29 +08:00
Sara Arjona 2db7c80f80 weekly release 4.5.5+ 2025-08-07 16:45:16 +02:00
Sara Arjona ec3ab5528a NOBUG: Add upgrade notes 2025-08-07 16:45:16 +02:00
Sara Arjona 256080fb2c NOBUG: Fixed file access permissions 2025-08-07 16:45:10 +02:00
Sara Arjona 8c2110c80c Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-08-07 16:45:07 +02:00
Huong Nguyen 9a45e5c064 Merge branch 'MDL-86004-405' of https://github.com/davewoloszyn/moodle into MOODLE_405_STABLE 2025-08-07 14:10:00 +07:00
David Woloszyn db896863cb MDL-86004 core: Modify upgrade step for Microsoft endpoints
The upgrade step for Microsoft OAuth2 issuers has been modified
to address an oversight in MDL-84432. The upgrade now only updates
service base urls for multi-tenant issuers.

Co-authored-by: Raju Tummoji <raju.tummoji@moodle.com>
2025-08-07 17:01:14 +10:00
Huong Nguyen 5f0d1b806c Merge branch 'MDL-85718-405' of https://github.com/yusufwib01/moodle into MOODLE_405_STABLE 2025-08-07 12:23:54 +07:00
Huong Nguyen 789f112c86 Merge branch 'MDL-86205-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-08-07 12:15:07 +07:00
Huong Nguyen f35a5c1047 Merge branch 'MDL-85794-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-08-07 11:37:33 +07:00
Jun Pataleta 43aa89c255 MDL-86205 message: Close drawer when activating elements outside of it 2025-08-07 12:31:05 +08:00
Huong Nguyen 99dc7feb31 Merge branch 'MDL-85795-405-2' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-08-07 11:26:26 +07:00
Huong Nguyen eb0dd85d88 MDL-84217 editor_tiny: Remove incorrect map file 2025-08-07 11:13:28 +07:00
Huong Nguyen 13d7e5dc6b Merge branch 'MDL-84217-405' of https://github.com/stevandoMoodle/moodle into MOODLE_405_STABLE 2025-08-07 10:56:34 +07:00
Ilya Tregubov 1646ce747e MDL-63395 user: Fix profile visibility check.
It needs to be done in user_get_user_details as well.
2025-08-07 10:30:04 +07:00
Jun Pataleta 0082009561 Merge branch 'MDL-85674-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2025-08-07 10:44:07 +08:00
Jun Pataleta ec3f9ed5e4 Merge branch 'MDL-78233-405' of https://github.com/Tasiobg/moodle into MOODLE_405_STABLE 2025-08-07 10:35:06 +08:00
Jun Pataleta ca7501babb Merge branch 'MDL-85802-405' of https://github.com/mihailges/moodle into MOODLE_405_STABLE 2025-08-07 10:33:37 +08:00
Mihail Geshoski 6035d07677 Merge branch 'MDL-79540_405_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_405_STABLE 2025-08-07 10:19:19 +08:00
Huong Nguyen f71bd594d1 Merge branch 'MDL-84010-m405' of https://github.com/sammarshallou/moodle into MOODLE_405_STABLE 2025-08-07 09:17:44 +07:00
AMOS bot 19e31c4169 Automatically generated installer lang files 2025-08-07 00:07:56 +00:00
ferran 17c8757314 Merge branch 'MDL-86176-405' of https://github.com/sarjona/moodle into MOODLE_405_STABLE 2025-08-06 16:22:00 +02:00
Sara Arjona 0427e1bf7b MDL-86176 badges: Adjust badges due to Canvas Credentials changes
This change addresses necessary adjustments to how OBV2.0 badges are
sent to backpacks, specifically to ensure compatibility and correct
functionality following recent updates to Canvas Credentials' conditions.

It ensures our badge integration continues to operate smoothly despite
these external changes.
2025-08-06 15:05:55 +02:00
Jun Pataleta 8c84d14195 MDL-85795 behat: Update button text used in search steps 2025-08-06 19:58:09 +08:00
Simey Lameze e375efca79 MDL-85674 behat: replace multiple steps by a single table step 2025-08-06 18:20:54 +08:00
andelacruz d870292683 MDL-85674 mod_grade: Behat test for multiple questions manual grading 2025-08-06 18:20:54 +08:00
Jun Pataleta 2e9c76095c Merge branch 'MDL-86095-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-08-06 15:41:11 +08:00
yusufwib01 0a3e64de5d MDL-85718 tiny_link: Improve image link behavior in TinyMCE 2025-08-06 14:31:16 +07:00
Rajneel Totaram 5008146b77 MDL-86095 mod_book: Let screenreader know print book opens in new window 2025-08-06 18:33:24 +12:00
Huong Nguyen 03430a4060 Merge branch 'MDL-85132_405_STABLE' of https://github.com/vladaskidanovas/moodle into MOODLE_405_STABLE 2025-08-06 10:54:18 +07:00
Laurent David 7616aa1eef MDL-84464 Report: Hide report for teachers without groups 2025-08-06 10:22:13 +07:00
Laurent David 6978147a03 MDL-84463 mod_feedback: Fix typo in behat test description 2025-08-06 03:05:21 +00:00
Laurent David a0f12b787b MDL-84463 mod_feedback: Fix users in no group and email
* Non editing teachers in no group should not receive feedback emails
from students in no group
2025-08-06 03:05:21 +00:00
Laurent David 890d43e1c3 MDL-84463 mod_feedback: Fix show entries and nonrespondents
* Non editing teachers not in a groups should not see entries
from other users
2025-08-06 03:05:21 +00:00
raortegar 4068e8ee2b MDL-85704 mod_bigbluebuttonbn: Update sesskey handling for compatibility 2025-08-06 03:05:21 +00:00
Huong Nguyen 4d12c8fac3 Merge branch 'MDL-85510-405' of https://github.com/NashTechOpenUniversity/moodle into MOODLE_405_STABLE 2025-08-06 08:30:16 +07:00
Paul Holden 98659fad08 MDL-86230 admin: fix site registration link to Moodle app.
Broken by 5d316201.
2025-08-06 00:36:09 +01:00
Khoa Nguyen Dang f56e272ff7 MDL-85510 Questions: clearfix from MDL-84708 affects spacing
Co-authored-by: Mikel Martín Corrales <mikel@moodle.com>
2025-08-05 15:53:22 +07:00
Stevani Andolo 98f8fdee6a MDL-84217 editor_tiny: Added 'img-fluid' class to dropped image files 2025-08-05 15:00:45 +08:00
sam marshall a08193f094 MDL-84010 core_files: sync_external_file can cause infinite recursion 2025-08-05 07:49:02 +01:00
Mihail Geshoski 1159f0763b Merge branch 'MDL-86001-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-05 11:42:33 +08:00
Mihail Geshoski 33b7ccdd88 Merge branch 'MDL-85565_405' of https://github.com/PhilippImhof/moodle into MOODLE_405_STABLE 2025-08-05 11:35:54 +08:00
Mihail Geshoski eb97c10d89 Merge branch 'MDL-85492_405' of https://github.com/krisbax/moodle into MOODLE_405_STABLE 2025-08-05 11:20:41 +08:00
Huong Nguyen 49e3a29b00 Merge branch 'MDL-85338-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-08-05 09:11:07 +07:00
Huong Nguyen b784f20565 Merge branch 'MDL-85231-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-08-05 08:50:32 +07:00
Huong Nguyen 375e56bb9b Merge branch 'MDL-86043-405' of https://github.com/tasiobg/moodle into MOODLE_405_STABLE 2025-08-05 08:32:14 +07:00
Jun Pataleta 76f0a88d89 MDL-85795 message: Enclose the search widget in a group 2025-08-04 18:46:50 +08:00
Jun Pataleta c4b864606f MDL-85795 search: Enclose the search widget in a group 2025-08-04 18:46:49 +08:00
Shamim Rezaie 86d14f5932 Merge branch 'MDL-85787-405-2' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-08-04 09:53:52 +10:00
Paul Holden 9f56af660a MDL-83462 mod_page: observe page description display in popup. 2025-08-01 17:04:26 +01:00
Jun Pataleta d1575b2739 MDL-85794 message: Set notification preferences controls within a group 2025-08-01 18:51:22 +08:00
Mihail Geshoski 9a419d4378 MDL-85802 mod_quiz: Fix double-escaping in notification content
Avoids double-escaping when filtering notification content. Adds
a unit test to validate the content filtering logic.
2025-08-01 18:50:12 +08:00
Iñigo Zendegi dc869f0c05 MDL-85802 mod_quiz: format course/quiz notification content. 2025-08-01 18:50:12 +08:00
Jun Pataleta 9e7c8cd9c0 MDL-85231 message: Style the notification buttons consistently 2025-08-01 13:09:02 +08:00
Jun Pataleta 43b5b809e6 MDL-85231 message_popup: Add a close button on the notification popover
This follows the recommendation on the ARIA Authoring Practices Guide
(APG) for modal dialogues:
> It is strongly recommended that the tab sequence of all dialogs
> include a visible element with role button that closes the dialog,
such as a close icon or cancel button.

https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
2025-08-01 13:09:02 +08:00
Laurent David b56614fce6 MDL-85231 message_popup: Add focustrap in message popup 2025-08-01 13:09:02 +08:00
David Mudrák 8143ad5cb5 MDL-85945 lang: Use fixed strings in the tests, too 2025-07-31 17:06:06 +02:00
Helen Foster 580a46bf12 MDL-85945 lang: Import fixed English strings (en_fix) 2025-07-31 17:05:30 +02:00
Sara Arjona b479c7a056 weekly release 4.5.5+ 2025-07-31 16:08:40 +02:00
Sara Arjona 353f4ed7cc NOBUG: Add upgrade notes 2025-07-31 16:08:39 +02:00
Sara Arjona 599b74c1a2 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-07-31 16:08:33 +02:00
Michael Hawkins 6498193e6d Merge branch 'MDL-85362-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2025-07-31 14:37:06 +08:00
Huong Nguyen eea759e64c Merge branch 'MDL-86062-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2025-07-31 10:21:03 +07:00
Huong Nguyen b9bea60fa8 Merge branch 'MDL-84485-Select-webservice-in-wstoken-behat-breadcrumb-405' of https://github.com/farhan6318/moodle into MOODLE_405_STABLE 2025-07-31 10:17:52 +07:00
Huong Nguyen afd0e41b69 Merge branch 'MDL-85897-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2025-07-31 08:58:32 +07:00
AMOS bot 5bf57f83c9 Automatically generated installer lang files 2025-07-31 00:08:02 +00:00
Shamim Rezaie b00188cd82 Merge branch 'MDL-85295-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-07-31 02:44:01 +10:00
Shamim Rezaie 5ea0f0f35e Merge branch 'MDL-72133-filter-deleted-logs-MOODLE_405_STABLE' of https://github.com/bwalkerl/moodle into MOODLE_405_STABLE 2025-07-31 02:27:18 +10:00
Mihail Geshoski 3e43057840 Merge branch 'MDL-86113-405' of https://github.com/snake/moodle into MOODLE_405_STABLE 2025-07-30 23:29:17 +08:00
Mihail Geshoski 8730b522e6 Merge branch 'MDL-85470-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-07-30 23:10:44 +08:00
Tasio 3bcbaf2ec8 MDL-86043 core: test for the task sending new users an email 2025-07-30 16:23:42 +02:00
Tasio d205f1bcb4 MDL-86043 core: fix sql to work in oracle
I’ve updated the query to use sql_isnotempty, which generates the correct SQL for the selected database.
I have aso added an "order by" because after some testing I noticed that Oracle and MySQL retun the results
in different order which feels inconsistent and makes it more difficult to test.
2025-07-30 16:23:34 +02:00
Paul Holden 116dc12ed1 MDL-85338 message: make messaging drawer responsive to sticky footer.
Co-authored-by: David Woloszyn <david.woloszyn@moodle.com>
2025-07-30 13:59:01 +01:00
Jun Pataleta de57e4a1cf Merge branch 'MDL-85995-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-07-30 17:10:09 +08:00
Andrew Nicols 86479d5871 MDL-86062 mod_choice: Stop calling get_string from lib.php 2025-07-30 13:58:40 +08:00
Andrew Nicols 63a21837db MDL-85897 core: Move all deprecations to emit_deprecation 2025-07-30 13:53:27 +08:00
Andrew Nicols 2181327bf2 MDL-85897 core: Add emit_deprecation() method
This changeset adds a new `\core\deprecation::emit_deprecation` method
with the same signature as the `emit_deprecation_if_found()` method.

It is intended to be used in places where something is guaranteed to
have been deprecated, and will throw appropriate debugging if the
deprecated attribute is not found.

The `emit_deprecation_if_found()` method should still be used where the
item being checked is not known to be deprecated or not, for example in
calling code.
2025-07-30 13:53:21 +08:00
Huong Nguyen f12170bb56 Merge branch 'MDL-85590_fix_modal_backdrop_attachment_point_405' of https://github.com/MariaMozgunova/moodle into MOODLE_405_STABLE 2025-07-30 08:37:46 +07:00
Farhan Karmali 4985662a5b MDL-84485 webservice: Add Behat step for mobile web service selection
This commit introduces a mandatory service selection step in our Behat tests
to ensure consistent behavior in environments with multiple third-party services.
2025-07-29 16:00:51 +05:30
Huong Nguyen 763bff0289 Merge branch 'MDL-85855-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-29 16:49:51 +07:00
Jun Pataleta af455ccfe2 MDL-85470 badges: Add a11y Behat test for the manage badges page 2025-07-29 15:17:24 +08:00
Jun Pataleta f83d35c631 MDL-85470 badges: Use badge:namewithimagelink column in badges table 2025-07-29 15:17:24 +08:00
Jun Pataleta bc100f6efa MDL-85470 reportbuilder: New column containing badge image, name, link 2025-07-29 15:17:24 +08:00
Jun Pataleta fde36bb49c MDL-85787 course: Limit course index drag and drop to certain pages only
Only allow the drag and drop functionality in the course index on pages
that provide alternatives to move course items such as the course
homepage and the course section view pages.

This is to meet the WCAG 2.2 Level AA Success Criterion 2.5.7 for
dragging movements.
See: https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements
2025-07-29 11:46:02 +08:00
Jayce Birrell 38fe5cc384 MDL-85698 core: combo box informs screen reader users 2025-07-29 10:52:55 +09:30
AMOS bot 67249bea47 Automatically generated installer lang files 2025-07-28 13:16:59 +00:00
Maria Mozgunova 20dfe6475b MDL-85590 core: Fix modal backdrop attachment point for full screen 2025-07-28 10:46:30 +01:00
Benjamin Walker b08eaaa028 MDL-72133 report_log: Add fallback context display 2025-07-28 09:06:40 +10:00
Benjamin Walker b3e2e81b0d MDL-72133 report_loglive: Display logs filtered by deleted course ids 2025-07-28 09:06:40 +10:00
Benjamin Walker eb0634d489 MDL-72133 report_log: Display logs filtered by deleted course ids 2025-07-28 09:06:40 +10:00
Philipp Imhof 59808c6870 MDL-85565 phpunit: check for <select> before <option>
Make sure that assert_select_options() will fail when the output does
not contain a <select> and therefore does not have the given options.
2025-07-25 13:13:30 +02:00
Jake Dallimore 4a02a9975d MDL-86113 mod_lti: skip slow network calls during tests
This check exists to determine whether a given tool URL is a cartridge,
specifically when the URL does not end in .xml. It hits the URL to
determine this. In tests, we have many mock tool URLs, which won't
resolve, and running this code causes large hangs and random failures.
Any tests covering cartridge support still work fine, provided they
continue to use URLs ending in .xml; this change only skips the check
during tests for other non-xml URLs.
2025-07-25 16:49:07 +08:00
Michael Hawkins 51662cb8ef weekly release 4.5.5+ 2025-07-25 12:14:36 +08:00
Michael Hawkins c3e13a4ca6 Merge branch 'MDL-83111-405' of https://github.com/sh-csg/moodle into MOODLE_405_STABLE 2025-07-24 12:01:17 +08:00
Huong Nguyen 35eec5b9aa Merge branch 'MDL-86049-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2025-07-24 09:47:51 +07:00
Huong Nguyen 3b68cecb15 Merge branch 'MDL-85504-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-07-24 09:44:11 +07:00
Rajneel Totaram 4c3be75ef2 MDL-85995 mod_book: Make previous/next buttons accessible 2025-07-24 10:08:39 +12:00
Jun Pataleta dc91c2144c MDL-85504 mod_feedback: Enhance the question edit form via JS
The drag handle and actions menu should not be within the accessible
labels of the question items. In order to enhance the question edit
form with these, they need to be rendered via JS outside of container
of the questions' accessible name.
2025-07-23 16:15:52 +08:00
Amaia Anabitarte 319113f872 Merge branch 'MDL-85989-405' of https://github.com/sarjona/moodle into MOODLE_405_STABLE 2025-07-23 10:14:00 +02:00
Jun Pataleta e894f95f6c MDL-85504 form: Legend's label template data should not be escaped
The label context data in the fieldset's legend tag should not be
escaped and allow raw HTML given that legend tags can contain
phrasing content and headings.

See developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/legend.
2025-07-23 16:10:53 +08:00
Huong Nguyen 977eacdfc0 Merge branch 'MDL-60799_405' of https://github.com/jonof/moodle into MOODLE_405_STABLE 2025-07-23 09:03:42 +07:00
Huong Nguyen d43a89d86f Merge branch 'MDL-83996-readonly-false-positive-MOODLE_405_STABLE' of https://github.com/bwalkerl/moodle into MOODLE_405_STABLE 2025-07-23 09:00:13 +07:00
Huong Nguyen 39c57e2f02 Merge branch 'MDL-84787-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-23 08:51:37 +07:00
Huong Nguyen c68104c12f Merge branch 'MDL-85573-tool-brickfield-uninstall-405' of https://github.com/szymonk1101/moodle into MOODLE_405_STABLE 2025-07-23 08:33:49 +07:00
Rohit Panchal 63cc9efdbc MDL-83991 tag: Optimized tag sql query 2025-07-23 01:00:33 +05:30
Mihail Geshoski b177a0cee6 Merge branch 'MDL-85997-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-22 23:35:25 +08:00
Mihail Geshoski 3d41378956 Merge branch 'MDL-85038-M405_dml_missing_record_exception-when-running-mod_assign-task-send_assignment_due_soon_notification_to_user' of https://github.com/ziegenberg/moodle into MOODLE_405_STABLE 2025-07-22 23:28:41 +08:00
Mihail Geshoski 09f9052355 Merge branch 'MDL-83558-405' of https://github.com/andrewgos/moodle into MOODLE_405_STABLE 2025-07-22 13:54:02 +08:00
Huong Nguyen 1ef0196b0d MDL-85362 core: Define environment requirements for Moodle 5.1 2025-07-22 10:54:17 +07:00
Rajneel Totaram a5e3d3f018 MDL-85295 qtype_ordering: render feedback properly
'answertext' is already passed through format_text(), so no need to escape it in the template.
2025-07-22 11:40:38 +12:00
Andrew Nicols 7c4210b6ea MDL-86049 core: Do not use the component cache before purge
When trying to resolve a corrupt cache where it is not possible to
complete the Moodle setup/bootstrap, it is necessary to ignore the
current cache to load the cache purging infrastructure.
2025-07-21 12:25:26 +08:00
Benjamin Walker 5aec7f1992 MDL-83996 sessions: Fix false positive read only sessions errors 2025-07-21 10:26:27 +10:00
Mihail Geshoski c97987e9ed weekly release 4.5.5+ 2025-07-18 14:52:50 +08:00
Mihail Geshoski 266d5f8857 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-07-18 14:52:37 +08:00
Paul Holden 6a1d35dfa0 MDL-85944 block_myoverview: fix previously badly nested SCSS. 2025-07-17 15:25:05 +07:00
Mihail Geshoski d7d824e872 Merge branch 'MDL-85055-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2025-07-17 12:14:56 +08:00
Paul Holden 396f77e552 MDL-85055 task: correct use of DI provided frozen Clock API.
Fixes some of the usage introduced in tests as part of a35335a9.
2025-07-17 11:56:55 +08:00
Huong Nguyen a736250f33 Merge branch 'MDL-85050-405' of https://github.com/NashTechOpenUniversity/moodle into MOODLE_405_STABLE 2025-07-17 10:24:34 +07:00
Huong Nguyen b0c912d6f0 Merge branch 'MDL-85944-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-17 08:53:01 +07:00
Huong Nguyen 72640fbc5a Merge branch 'MDL-81384-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-17 08:44:04 +07:00
Shamim Rezaie 29fab26069 Merge branch 'MDL-73003-405' of https://github.com/Jayce0808/moodle into MOODLE_405_STABLE 2025-07-17 07:29:47 +10:00
Mihail Geshoski 114917dc65 Merge branch 'MDL-77237-405' of https://github.com/meirzamoodle/moodle into MOODLE_405_STABLE 2025-07-16 23:53:15 +08:00
Huong Nguyen c9c1d71c32 Merge branch 'MDL-85609-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-16 09:52:48 +07:00
Andrew Gosali a61e0d92ed MDL-83558 mod_forum: fix incorrect message on edge case scenario
(when student tries to see all participants on a forum with visible groups mode)
2025-07-15 22:46:02 +07:00
Daniel Ziegenberg 4e822cccfd MDL-85038 assignment: fix exception on sending assignment notification
When the assignment gets deleted between the creation of the ad-hoc task
mod_assign\task\send_assignment_due_soon_notification_to_user or
mod_assign\task\send_assignment_overdue_notification_to_user and its
execution, the ad-hoc task fails with a dml_missing_record_exception and
the task gets stuck.

In this case we do not want the task to fail and just return with an
appropriate mtrace.

Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
2025-07-15 10:46:35 +02:00
meirzamoodle 07af6ccc73 MDL-77237 core: Suppress debugging during capabilities cleanup
To prevent the debugging messages from appearing during the upgrade,
I set a flag to hide them.

For the test file, I adjusted the parameters in
the unassign_capability() function, which seemed to be using
the parameter intended for assign_capability().
2025-07-15 14:36:26 +07:00
Paul Holden cada977938 MDL-85944 block_myoverview: specific styling for "My courses" page. 2025-07-15 08:02:17 +01:00
Mihail Geshoski cd409009e4 Merge branch 'MDL-85246-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-07-15 11:11:26 +08:00
Huong Nguyen ef0ba8a988 Merge branch 'MDL-72582-MOODLE_405_STABLE' of https://github.com/tsmilan/moodle into MOODLE_405_STABLE 2025-07-15 10:07:55 +07:00
Huong Nguyen 4de8318ca9 Merge branch 'MDL-86016-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2025-07-15 09:56:41 +07:00
Huong Nguyen 9bd33ef7e6 Merge branch 'MDL-85040-405-new' of https://github.com/meirzamoodle/moodle into MOODLE_405_STABLE 2025-07-15 09:42:29 +07:00
Mihail Geshoski 4baac8c6ce Merge branch 'MDL-84385-requireonce-405' of https://github.com/leonstr/moodle into MOODLE_405_STABLE 2025-07-15 09:41:18 +08:00
Andrew Nicols b4092640b8 MDL-86016 core: Remove xmldb test in favour of unit test 2025-07-15 09:33:05 +08:00
Andrew Nicols 75ac9fc3ae MDL-86016 tests: Make install.xml test less restrictive 2025-07-15 09:33:04 +08:00
Amaia Anabitarte b7d3d4f191 MDL-81384 report_participation: Remove innecessary JS parameters
The parameter are usable in course participants page because user
could add bulk notes linked to users. But that script is not used
in report/participants, so we don't need those arguments on the call.

Backported from MDL-75669.
2025-07-14 21:32:30 +01:00
Paul Holden ccb3a73f98 MDL-81384 theme_boost: centre help icon margin/padding. 2025-07-14 21:32:30 +01:00
meirzamoodle d78eccdb13 MDL-85040 forms: Make required form icons decorative
The required icon in mforms is not necessary. The div containing it
already has a title attribute that enables mouse users to hover on the
icon and see its meaning.

Screen reader users will know whether a field is required or not via
the `aria-required` attribute.

Labelling the required icon via `aria-label` does not pass the
a11y audit.

Given these, it only makes sense to make the required icon decorative
for mforms.

Co-authored-by: Jun Pataleta <jun@moodle.com>
2025-07-14 14:25:23 +07:00
AMOS bot 12e3626a6d Automatically generated installer lang files 2025-07-12 00:07:54 +00:00
Paul Holden 86fe6f4ae7 MDL-86001 qbank_columnsortorder: populate preview data question text. 2025-07-11 15:23:46 +01:00
Paul Holden 1708db93e6 MDL-85997 output: require CLI library from corresponding renderer. 2025-07-11 11:59:41 +01:00
Mark Johnson 9f953080d1 MDL-79540 qbank_managecategories: Don't allow own child as parent
When we build the options for a question category selector, the
"nochildrenof" option isn't correctly compared to the category ID. This
means we can select a category's own children as its new parent.
2025-07-11 10:07:49 +01:00
Sara Arjona 0a01a0d119 MDL-85989 output: Fix choicelist when selected 0 is not first
Previously, when the selected value in a choicelist was 0 but not in
the first position, the behavior was incorrect.
This commit fixes that issue, ensuring the choicelist behaves as expected
regardless of the position of the 0 value.
2025-07-11 09:15:02 +02:00
Michael Hawkins ce34e8ff08 weekly release 4.5.5+ 2025-07-11 12:49:25 +08:00
Leon Stringer 34a0d939dd MDL-84385 mod_assign: Remove unneeded require_once 2025-07-10 09:26:28 +01:00
Huong Nguyen 14d442c52c Merge branch 'MDL-85863-405' of https://github.com/yusufwib01/moodle into MOODLE_405_STABLE 2025-07-10 10:46:48 +07:00
Mihail Geshoski c4c11a559f Merge branch 'MDL-85689-405' of https://github.com/andelacruz/moodle into MOODLE_405_STABLE 2025-07-10 11:38:36 +08:00
Mark Johnson 3e8f439d17 MDL-85721 qtype_multianswer: Update find_duplicated_subquestions for Oracle compatibility 2025-07-10 10:29:08 +07:00
Huong Nguyen ab271c4c24 Merge branch 'MDL-85262-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-10 08:33:44 +07:00
Huong Nguyen ed44561319 Merge branch 'MDL-84901-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-10 08:25:44 +07:00
Mihail Geshoski b85edb5f5a Merge branch 'MDL-85939-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-09 23:25:10 +08:00
Mihail Geshoski 0a7c65c43e Merge branch 'MDL-84255-405' of https://github.com/lucaboesch/moodle into MOODLE_405_STABLE 2025-07-09 23:17:22 +08:00
Rajneel Totaram ba71a95d34 MDL-85246 mod_quiz: Fix quiz grade feedback
Using $CFG->recovergradesdefault and/or $gradeitem->refresh_grades()
are problematic when viewing a quiz. When reenrolling a user with
previous attempts, a teacher can choose not to recover the user's grades,
even when recovergradesdefault=true. This must be respected when
the user views the quiz. For site with recovergradesdefault=false,
the users always see a grade of 0, which is wrong.

This change shows the correct grade score and feedback to users and
avoids unintentionally recovering the grades.
2025-07-09 20:33:14 +12:00
Jun Pataleta 09bbcdaf32 Merge branch 'MDL-85596-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-09 14:14:48 +08:00
andelacruz 0de18a98cf MDL-85689 mod_quiz: Behat for adding and deleting page from quiz 2025-07-09 13:26:39 +08:00
Mihail Geshoski 0f5f7c8dbe Merge branch 'MDL-85721_405_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_405_STABLE 2025-07-09 10:20:09 +08:00
Huong Nguyen 4c406b2d81 Merge branch 'MDL-85697-405' of https://github.com/andelacruz/moodle into MOODLE_405_STABLE 2025-07-09 09:10:53 +07:00
Paul Holden 19edba7020 MDL-85939 qbank_columnsortorder: improve preview page table alignment. 2025-07-08 17:58:38 +01:00
Kristiina.Bax aee5428591 MDL-85492 report_questioninstances: sort qtypes in alphabetical order 2025-07-08 17:14:15 +01:00
Paul Holden 2d4b15f317 MDL-85262 tool_task: switch next run time comparison in renderer.
If a task is "due", then next run time should be considered "ASAP".
2025-07-08 13:49:47 +01:00
Paul Holden 6462abc6df MDL-84901 auth: specific session start error language string. 2025-07-08 12:33:07 +01:00
Huong Nguyen 422a6f94a9 Merge branch 'MDL-84976-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2025-07-08 18:13:44 +07:00
Huong Nguyen 6f961cacc5 Merge branch 'MDL-85037-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-08 18:09:00 +07:00
Huong Nguyen eed6310a08 Merge branch 'MDL-85947-M405_bump-GHA-ubuntu-24-04' of https://github.com/ziegenberg/moodle into MOODLE_405_STABLE 2025-07-08 17:35:12 +07:00
andelacruz ddcaa5f8a6 MDL-85697 mod_lesson: Behat for lesson group and user overrides 2025-07-08 17:03:21 +08:00
andelacruz 6b9cbd5a19 MDL-85697 mod_assign: Behat for assignment group and user overrides 2025-07-08 17:02:06 +08:00
andelacruz 0b86e7f574 MDL-85697 mod_quiz: Behat for quiz group and user overrides 2025-07-08 16:59:49 +08:00
Luca Bösch 547cd0de94 MDL-84255 quiz: Show quiz title in heading in accessrule_seb layout. 2025-07-08 07:51:51 +02:00
Daniel Ziegenberg 323d7ba996 MDL-85947 ci: Bump GHA to Ubuntu 24.04
Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
2025-07-07 16:12:45 +02:00
yusufwib01 22c2f2455e MDL-85863 user: Defer new-password email until after transaction commit 2025-07-07 21:03:32 +07:00
Trisha Milan 4e9c6b3f37 MDL-72582 core_files: Add unit test for readfile_accel debugging output
Adds a unit test for the debugging message triggered when readfile_accel() detects
a non-empty output buffer. The readfile_accel() function directly manipulates the
output buffers, so calling it from within PHPUnit triggers an error like "Test code
or tested code closed output buffers other than its own".

As a workaround, the test runs a CLI script in a separate process, allowing buffer
behaviour to be isolated and debugging output to be captured for assertions.
2025-07-07 18:59:25 +10:00
Matt Rice a442938279 MDL-72582 core_files: Update readfile_accel error handling
Correct the error handling in readfile_accel to properly log errors when
working on a stored_file, rather than hiding the true error behind the
incorrect "Object of class stored_file could not be converted to
string."

Co-authored-by: Simey Lameze <simey@moodle.com>
Co-authored-by: Trisha Milan <trishamilan@catalyst-au.net>
2025-07-07 18:59:19 +10:00
Laurent David 067138aab4 MDL-81702 core_message: Add focustrap and backdrop
* Add focustrap and backdrop to message drawer.
2025-07-07 07:09:16 +02:00
Laurent David e41473d9ff MDL-81702 theme_boost: Add focus lock to drawers
* Add focus lock to drawers for small screens
2025-07-07 07:09:16 +02:00
Laurent David 97aa3dd1f9 MDL-81702 theme_boost: Remove site home link from drawers
* The site-home-link was added to all menu drawers and should
only be added to the main menu drawer (mobile version)
2025-07-07 07:09:16 +02:00
Mihail Geshoski 2fce831627 weekly release 4.5.5+ 2025-07-06 09:26:27 +08:00
Mihail Geshoski 6fe2fb1f0f NOBUG: Add upgrade notes 2025-07-06 09:26:27 +08:00
Mihail Geshoski 396a02396e Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-07-06 09:26:13 +08:00
Mihail Geshoski cb9bebdc8e Merge branch 'MDL-85350_405' of https://github.com/PhilippImhof/moodle into MOODLE_405_STABLE 2025-07-05 19:57:51 +08:00
Mihail Geshoski 9a28c68655 Merge branch 'MDL-85563-m405' of https://github.com/NeillM/moodle into MOODLE_405_STABLE 2025-07-05 19:57:51 +08:00
Paul Holden 98329e1d71 MDL-85037 block_html: correct access checks for plugin file serving. 2025-07-04 08:55:58 +08:00
Huong Nguyen 046a9c4905 Merge branch 'MDL-76342-405' of https://github.com/yusufwib01/moodle into MOODLE_405_STABLE 2025-07-03 09:07:59 +07:00
Huong Nguyen b37969c4f9 Merge branch 'MDL-85769-405' of https://github.com/meirzamoodle/moodle into MOODLE_405_STABLE 2025-07-03 09:05:24 +07:00
Mihail Geshoski cc2d6b276d Merge branch 'patch/MDL-85666-405' of https://github.com/skodak/moodle into MOODLE_405_STABLE 2025-07-03 09:54:33 +08:00
Petr Skoda 6a4b730dfd MDL-85666 phpunit: add general plugin tests
This defines new group which can be used
to limit execution of these tests to one plugin only.

For example to execute all tests for Label module use:

phpunit --testsuite=mod_label_testsuite
phpunit --group=plugin_checks --filter=mod_label

or

phpunit --filter=mod_label
2025-07-02 07:12:13 +02:00
AMOS bot 9fa2846df4 Automatically generated installer lang files 2025-07-02 00:09:53 +00:00
Mark Johnson e721309453 MDL-85721 qtype_numerical: Prevent duplication on restore
If question_numerical record for a question has been deleted, when its
options are loaded its answers will not have a tolerance property. This
can lead to duplication.

Also, older questions may not have a question_numerical_options record.

To avoid this, we use 0 as a default tolerance for all answers if there
is no matching record, and do the same when reading the question
structure from backups. We also generate default values for the rest of
the options.
2025-07-01 11:58:59 +01:00
Mark Johnson 3101c07a97 MDL-85721 qtype: Cope with missing options records
If we restore a question (or any other) which has had its
qtype_xxx_options record deleted, we get a notification output when we
try to build the options.

This may be called from an AJAX request (such as when we duplicate a
quiz), and outputting the notification breaks the AJAX response.
Returning false also means we don't get the answers attached to the
questiondata options, so the structure doesn't match the restored data,
and we get duplication.

This emits the errors via debugging instead, which allows it to be
supressed or logged, and allows get_question_options() to continue
running.
2025-07-01 11:58:58 +01:00
Mark Johnson 8c9d16e605 MDL-85721 qtype_multianswer: Clean up duplicate subquestions
This adds an ad-hoc task to clean up duplicate subquestions created by
the bug.

When the upgrade is run, it will run a query to check if any
subquestions have been duplicated (if there are multiple questions with
the same parent, text and stamp), and queue the cleanup task if there
are.

For each stamp identified, the task will find instances of the question
that are not referred to in their parent's sequence field, confirm that
they have 0 usages, and delete them.
2025-07-01 11:58:58 +01:00
Mark Johnson d3d2d67c8e MDL-85721 qtype_multichoice: Generate default options on restore
qtype_multichoice::get_question_options() will create a default options
object if no qtype_multichoice_options record exists. This means if we
restore a backup containing a multichoice question without any options
(see previous commit), it will always create a duplicate.

This change generates a default set of options for the backupdata if
none exists, so the identity hash will match if the same question exists
in the database without options, and we dont get duplicates.
2025-07-01 11:54:50 +01:00
Mark Johnson 7c0600ae16 MDL-85721 qtype_multianswer: Don't delete qtype data for subquestions
Historically, multianswer would re-use question IDs from subquestions,
so it was necessary to delete qtype-specific data if the qtype changed.
Since versioning was introduced, editing creates new versions of the
qtype and subquestions. Deleting the data means the original versions of
the subquestions cannot function correctly, so we shouldn't do that
anymore.
2025-07-01 11:54:49 +01:00
Andrew Nicols b6f3972fad Merge branch 'MOODLE_405_STABLE' of git.in.moodle.com:moodle/integration; branch 'MDL-84531-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-07-01 17:36:47 +08:00
Neill Magill 76275dadf5 MDL-85563 phpunit: Clock API should always use Moodle system time
When using `clock::now()` the frozen cloxk and incrementing clock will now
always return a `DateTimeImmutable` that uses the configured Moodle system
time. In unit tests this will be Australia/Perth.

Before this change, it would sometimes be UCT.
2025-06-30 11:43:46 +01:00
Daniel Ziegenberg 202fe57da2 MDL-83003 core: Add error check for envoy proxy
Checking the redis server version is also simplified; once ought to be
enough :)

Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
2025-06-30 09:32:01 +02:00
Huong Nguyen c36274a15a weekly release 4.5.5+ 2025-06-29 16:18:49 +07:00
meirzamoodle c080b4bf58 MDL-85769 AI: Trap focus within AI drawer on smaller screens
Co-authored-by: Laurent David <laurent.david@moodle.com>
2025-06-28 10:59:41 +07:00
Paul Holden be23488e54 MDL-85855 calendar: format course shortname attribute correctly. 2025-06-26 21:47:12 +08:00
Mihail Geshoski c8116a0dbe Merge branch 'MDL-85665-405' of https://github.com/andelacruz/moodle into MOODLE_405_STABLE 2025-06-26 10:44:07 +08:00
Huong Nguyen 2ed1dd557c Merge branch 'MDL-83752-M405' of https://github.com/ziegenberg/moodle into MOODLE_405_STABLE 2025-06-26 09:19:43 +07:00
Huong Nguyen fc34abe738 Merge branch 'MDL-76414-405' of https://github.com/yusufwib01/moodle into MOODLE_405_STABLE 2025-06-26 08:29:47 +07:00
Huong Nguyen c477dcd640 Merge branch 'MDL-85734-405' of https://github.com/lucaboesch/moodle into MOODLE_405_STABLE 2025-06-26 08:25:57 +07:00
Huong Nguyen c4e40860e4 Merge branch 'MDL-85466-405' of https://github.com/lucaboesch/moodle into MOODLE_405_STABLE 2025-06-26 08:22:29 +07:00
Huong Nguyen 91530e92ee Merge branch 'MDL-82109-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-06-26 08:19:58 +07:00
Jonathon Fowler f7b694d15f MDL-60799 enrol: update_instance() needs separate objects passed
Passing the same object means enrol_cohort does not recognise a change
in role when an edit or batch course upload operation is done.

 * enrol/editinstance.php was modifying $instance before passing it to
   enrol_plugin::update_instance() so the latter never sees a difference
   versus $data. This is redundant since update_instance() is doing the
   same thing a second time.
 * admin/tool/uploadcourse/classes/course.php is passing the same object
   as both arguments to enrol_plugin::update_instance().

There is an expectation that update_enrol_plugin_data() is to mutate its
$instance argument and then return it based on how derived classes in
enrol_guest and enrol_self behave, so cloning $instance within
update_enrol_plugin_data() before mutating it isn't an option.
2025-06-25 09:43:10 +10:00
Daniel Ziegenberg 471a73e98c MDL-83752 workshop: Handle empty feedbackreviewer
Causes PHP warnings when null on PHP 8.1.

Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
2025-06-24 12:41:17 +02:00
andelacruz 651af33600 MDL-85665 mod_quiz: Behat to build quiz in single activity format course 2025-06-24 17:58:32 +08:00
Huong Nguyen 002398450f Merge branch 'MDL-85683-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-06-24 09:21:22 +07:00
Huong Nguyen 7f708151eb Merge branch 'MDL-85407-45' of https://github.com/durenadev/moodle into MOODLE_405_STABLE 2025-06-24 09:17:03 +07:00
Paul Holden 1263027e0f MDL-85596 output: update "More" sub-menus ARIA expanded attribute. 2025-06-20 21:10:29 +01:00
Luca Bösch 90ce0974bd MDL-85734 calendar: support multilang group names. 2025-06-20 16:47:45 +02:00
Luca Bösch 23dc4c7ca4 MDL-85466 course: space between number and course icon. 2025-06-20 16:47:16 +02:00
Huong Nguyen 97ed6b7083 weekly release 4.5.5+ 2025-06-20 15:14:38 +07:00
Huong Nguyen 499f6f3688 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-06-20 15:14:26 +07:00
Sara Arjona 5a22bfc175 Merge branch 'MDL-75947-integration-405-fix' of https://github.com/mihailges/moodle into MOODLE_405_STABLE 2025-06-19 17:22:08 +02:00
Mihail Geshoski 66bf9156b3 MDL-75947 ltiservice_gradebookservices: Fix unit test
Fixes the test_sequential_score_posts() unit test in lineitem_test
by removing dynamic class property assignments, which were triggering
deprecation notices.
2025-06-19 16:15:36 +08:00
yusufwib01 a14b77ec1c MDL-76414 navigation: Show 'Calendar' link in primary nav to guests 2025-06-19 13:13:58 +07:00
Huong Nguyen 8d6e21135c Merge branch 'MDL-85223-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-06-19 11:26:20 +07:00
Mihail Geshoski 9d661d516e Merge branch 'MDL-75947-405' of https://github.com/Jayce0808/moodle into MOODLE_405_STABLE 2025-06-19 12:18:15 +08:00
Huong Nguyen 8369f19a64 Merge branch 'MDL-85678-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-06-19 11:07:48 +07:00
Mihail Geshoski 1338ddaa3f Merge branch 'MDL-83487-405' of https://github.com/muhammadarnaldo/moodle into MOODLE_405_STABLE 2025-06-19 11:31:24 +08:00
Huong Nguyen 67827bf0fa Merge branch 'MDL-85159-405' of https://github.com/stevandoMoodle/moodle into MOODLE_405_STABLE 2025-06-19 10:03:51 +07:00
yusufwib01 7479f88d22 MDL-76342 core_output: Wrap buttons below heading on small screens 2025-06-19 10:01:33 +07:00
Mihail Geshoski 0e70433873 Merge branch 'MDL-85514-405' of https://github.com/snake/moodle into MOODLE_405_STABLE 2025-06-19 10:58:38 +08:00
Jake Dallimore d54a04197a MDL-85514 core_form: support 'in' rule for multiselects via subset of
The 'in' rule currently behaves the same way that 'eq' does when used
with multi-selects, which is not correct. It should be modelled by
checking whether the selected values are a subset of the rule values
(i.e. selected values are 'in' the rule values). This patch implements
that for core form rules as well as for the admin settings show/hide.
Several behat features are updated as well, because these were verifying
the incorrect behaviour for multi-select in rules.
2025-06-19 09:58:30 +08:00
Mihail Geshoski f6497cbe1f Merge branch 'MDL-42263-405' of https://github.com/Jayce0808/moodle into MOODLE_405_STABLE 2025-06-18 16:17:06 +08:00
Jayce Birrell 693d89cbdb MDL-73003 core_grades: Don't use 'scale' to determine grading method.
When a forum activity used a rubrics with a scale for grading, it was not treated as an advanced grading method, causing
the code to mishandle the grading object and not return any grades.
2025-06-18 16:51:20 +09:30
Huong Nguyen ccc6b2ee89 Merge branch 'MDL-84832-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-06-18 09:50:58 +07:00
Paul Holden 5d7c70c21e Merge branch 'MDL-85773-405' of https://github.com/sebastsg/moodle into MOODLE_405_STABLE 2025-06-17 11:08:46 +01:00
Sebastian Gundersen d2e1d0e76e MDL-85773 forms: Read-only selects must have static labels
This is to prevent labels with an invalid for attribute, referencing non-existing elements.
2025-06-17 11:44:35 +02:00
Tasio fc21f64863 MDL-78233 accesslib: remove unreliable test
Co-authored-by: Marina Glancy <marina.glancy@gmail.com>
2025-06-17 10:37:57 +02:00
Mihail Geshoski b5fd491b73 Merge branch 'MDL-84978-405' of https://github.com/olivabigyo/moodle into MOODLE_405_STABLE 2025-06-17 15:15:30 +08:00
Mihail Geshoski e8ac6e2fec Merge branch 'MDL-82797_405' of https://github.com/cwarwicker/moodle into MOODLE_405_STABLE 2025-06-17 14:59:52 +08:00
Marina Glancy e3411249d6 MDL-80611 general: do not translate url in help links 2025-06-16 18:10:24 +01:00
Paul Holden 10aece9d65 MDL-85609 tool_mfa: fix user counting for factors in use table. 2025-06-16 13:03:43 +01:00
Jun Pataleta b419fd08fc MDL-85678 mod_feedback: a11y Behat coverage for Manage templates page 2025-06-16 13:50:25 +08:00
Jun Pataleta 5c3e474160 MDL-85678 mod_feedback: Fix a11y issues in the Manage templates page
- Fix the heading hierarchy.
- Add a visually-hidden column header for the actions column.
2025-06-16 13:50:25 +08:00
Jake Dallimore 1bf62c2e46 MDL-75947 ltiservice_gradebookservices: score->timestamp as dategraded
If this is not passed in to grade_update(), then it's not passed on to
update_raw_grade(), resulting in time() being used instead, which isn't
correct. The grade timemodified value should be set to the timestamp
provided by the tool in the score post JSON, not the time of the score
post itself.

Co-authored-by: Jayce Birrell <jayce.birrell@moodle.com>
2025-06-16 14:43:46 +09:30
Jake Dallimore 036f61da49 MDL-75947 ltiservice_gradebookservices: test sequential score posting
This tests highlights a problem with the score handling logic and
will currently fail. On score post, the current time is used for
grade->timemodified instead of the score->timestamp provided by the
tool in the POST JSON.
2025-06-16 14:43:46 +09:30
aya-saad1 e6894fcf11 MDL-42263 assign: Force language of email sent to graders
Co-authored-by: Jayce Birrell <jayce.birrell@moodle.com>
2025-06-16 11:01:18 +09:30
AMOS bot 4fceaed909 Automatically generated installer lang files 2025-06-15 00:09:52 +00:00
Mihail Geshoski 95518023c6 weekly release 4.5.5+ 2025-06-13 14:43:42 +08:00
Mihail Geshoski fe0d7c9b0a NOBUG: Add upgrade notes 2025-06-13 14:43:42 +08:00
Paul Holden 56801cdf3b MDL-84531 core: default deprecated attribute $replacement to null. 2025-06-12 14:50:56 +01:00
Huong Nguyen 9691e4d4c6 MDL-85316 dml: Revert DB vendor changes 2025-06-12 17:11:26 +07:00
Jun Pataleta d2711e792f Merge branch 'MDL-84752-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-06-12 17:15:43 +08:00
Paul Holden 102621f53d MDL-84752 qtype_essay: create accessible min/max words form elements.
Because of historical requirements involving fieldsets in question
type forms (see 958e7671), we should use ARIA label attribute on
grouped elements rather than normal labels in order to make them
accessible.

Co-authored-by: Jun Pataleta <jun@moodle.com>
2025-06-12 10:11:48 +01:00
Jun Pataleta 44fc7bd927 Merge branch 'MDL-85700-405' of https://github.com/roland04/moodle into MOODLE_405_STABLE 2025-06-12 17:06:40 +08:00
Huong Nguyen 18b20273fd MDL-72123 message: Add upgrade note 2025-06-12 17:06:40 +08:00
Huong Nguyen 49729e14a4 Merge branch 'MDL-72123-405' of https://github.com/yusufwib01/moodle into MOODLE_405_STABLE 2025-06-12 17:06:39 +08:00
Huong Nguyen 97ccd815f9 Merge branch 'MDL-84611-405' of https://github.com/sh-csg/moodle into MOODLE_405_STABLE 2025-06-12 08:37:34 +07:00
Mikel Martín abdf765a28 MDL-85700 theme_boost: Fix singleselect spacing
- Fix single_select template form layout.
2025-06-11 13:53:03 +02:00
Justin Hunt bd54d58d8a MDL-82797 question engine: gift import fix 2025-06-11 11:58:23 +01:00
Stefan Hanauska 172277eae4 MDL-84611 blocks: Respect instance_allow_multiple() 2025-06-11 11:22:05 +02:00
Stefan Hanauska 110e4f7aed MDL-84611 block_html: Change tests to respect capabilities 2025-06-11 11:22:05 +02:00
Huong Nguyen 8061a3cdc8 Merge branch 'MDL-77964-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2025-06-11 14:08:50 +07:00
Huong Nguyen 60c73f7ff7 Merge branch 'MDL-85316-405-mysql-version' of https://github.com/meirzamoodle/moodle into MOODLE_405_STABLE 2025-06-11 13:33:49 +07:00
Huong Nguyen d83b08f92c Merge branch 'MDL-85274-405' of https://github.com/davewoloszyn/moodle into MOODLE_405_STABLE 2025-06-11 13:30:18 +07:00
David Woloszyn 5dd77e0022 MDL-85274 calendar: Duration limits added to forms 2025-06-11 14:30:11 +10:00
meirzamoodle cc7efbf977 MDL-85316 admin: Add environment check for AWS Aurora database 2025-06-11 10:44:12 +07:00
Philipp Imhof cd11b9533f MDL-85350 questions: skip restore test for certain 3rd party plugins
The test_restore_course_with_same_stamp_questions() test assumes
that all question types using answers will also use the table
"question_answers" to store said answers. However, some 3rd party
qtypes might use custom tables and for those, the test would fail.
2025-06-06 15:22:19 +02:00
Rajneel Totaram 436c8070e2 MDL-84976 tool_policy: noclean param was not set 2025-06-06 15:14:40 +12:00
Paul Holden 4e1feb2c4e MDL-85683 enrol: use formatted course name for notification context. 2025-06-06 00:22:13 +01:00
yusufwib01 8c96b52f08 MDL-72123 message: Hide add to contacts button if user not allowed 2025-06-06 01:09:29 +07:00
Szymon Karolczuk dd639a9516 MDL-85573 tool_brickfield: fix uninstall code 2025-06-05 19:16:20 +02:00
Niels Gandraß 8cce7a8a82 MDL-84491 core_session: Fix redis connection in CLI / scheduled task 2025-06-05 10:26:55 +02:00
Mihail Geshoski 4390e42ea2 MDL-85619 mod_assign: Preserve default modal behaviour on save
Removes the preventDefault() call that previously prevented the
save/cancel modal from automatically closing when the 'Save' action
is applied for a specific bulk action on the assignment submissions
page.
2025-06-04 17:34:54 +08:00
vlad.kidanov a9b8b52a16 MDL-85132 mod_glossary: Add missing end div for entrybox element. 2025-05-30 10:30:06 +01:00
Jun Pataleta 3f16708b55 MDL-84832 tiny_media: Announce when alt text maxlength is reached
Co-authored-by: Meirza <meirza.arson@moodle.com>
2025-05-30 17:08:18 +08:00
Paul Holden 586ead512c MDL-85223 reportbuilder: initialize user entity filters just once. 2025-05-29 15:32:30 +01:00
Paul Holden 1e5ad34897 MDL-77964 availability: avoid JS console errors when closing modal. 2025-05-29 10:15:31 +01:00
David Herney 57e7f3ae94 MDL-85616 mod_glossary: The existence of the variable is controlled 2025-05-29 03:55:14 -05:00
Katalin Lukacs Toth 9a23fc37fb MDL-84978 mod_forum: Fix latest/earliest post id lookup in a discussion
This properly fixes the problems that commit 0f5a96f in MDL-80848 tried
to address.
2025-05-21 11:04:19 +02:00
Paul Holden bf3c210d82 MDL-82109 notes: include link to add site note if user has capability. 2025-05-20 13:18:37 +01:00
Muhammad Arnaldo 14c33bf2a8 MDL-83487 mod_forum: retain upcoming posts in user digests 2025-05-16 15:35:14 +07:00
toanlamt cbda104d14 MDL-85050 Javascript: core/dropzone Fix link & box click event conflict 2025-05-15 15:11:54 +07:00
Stevani Andolo 0b719c3951 MDL-85159 editor_tiny: Fix language list and track srclang a11y issue 2025-05-14 09:22:05 +08:00
Daniel Ureña 5d640c4b5c MDL-85407 mod_data: Fix unclosed HTML in list view 2025-05-08 11:15:49 +02:00
Alexander Bias ac30d9e451 MDL-79362 auth_ldap: Close transaction when user fails to create 2025-04-28 10:53:34 +02:00
sam marshall e3ccdbee8a MDL-83628 message: message_provider_uninstall slow
This function was slow because using case-insensitive LIKE on the large
user_preferences table means that Postgres does not use its index and
has to sequential-scan the table.
2025-04-24 13:05:10 +01:00
Stefan Hanauska b9144ae56a MDL-83111 tool_recyclebin: Avoid null value 2025-04-18 07:15:10 +02:00
toanlamt 14d365dcf0 MDL-84982 tiny_media: Fix excessive data passed to the usedfiles JS file 2025-03-26 13:56:08 +07:00
Paul Holden 1c4b98c2d2 MDL-84787 calendar: restore course-specific management page parameter. 2025-03-11 13:32:52 +00:00
1196 changed files with 12071 additions and 4398 deletions
+3 -3
View File
@@ -19,7 +19,7 @@ env:
jobs:
Grunt:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: Checking out code
@@ -61,12 +61,12 @@ jobs:
matrix:
include:
# MySQL builds always run with the lowest PHP supported version.
- os: ubuntu-22.04
- os: ubuntu-24.04
php: 8.1
extensions:
db: mysqli
# PostgreSQL builds always run with the highest PHP supported version.
- os: ubuntu-22.04
- os: ubuntu-24.04
php: 8.3
db: pgsql
+79
View File
@@ -6,6 +6,85 @@ More detailed information on key changes can be found in the [Developer update n
The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com).
## 4.5.7
### core
#### Added
- The Behat `::execute()` method now accepts an array-style callable in addition to the string `classname::method` format.
The following formats are now accepted:
```php
// String format:
$this->execute('behat_general::i_click_on', [...]);
// Array format:
$this->execute([behat_general::class,' i_click_on'], [...]);
```
For more information see [MDL-86231](https://tracker.moodle.org/browse/MDL-86231)
- The `\externallib_advanced_testcase` has been replaced by `\core_external\tests\externallib_testcase` and is now autoloadable.
For more information see [MDL-86283](https://tracker.moodle.org/browse/MDL-86283)
### core_badges
#### Added
- A number of new static methods have been added to `core_badges\backpack_api` to support the new Canvas Credentials backpack provider. These methods allow you to retrieve lists of providers and regions, check if Canvas Credentials fields should be displayed, and get a region URL or API URL based on a given region ID. The new methods include `get_providers`, `get_regions`, `display_canvas_credentials_fields`, `get_region_url`, `get_region_api_url`, `get_regionid_from_url`, and `is_canvas_credentials_region`.
For more information see [MDL-86174](https://tracker.moodle.org/browse/MDL-86174)
### core_grades
#### Added
- New 'is_gradable()' function has been created to return whether the item has any gradeitem that is GRADE_TYPE_VALUE or GRADE_TYPE_SCALE.
For more information see [MDL-85837](https://tracker.moodle.org/browse/MDL-85837)
### core_message
#### Added
- The `contexturl` property to `\core\message\message` instances can now contain `\core\url` values in addition to plain strings
For more information see [MDL-83080](https://tracker.moodle.org/browse/MDL-83080)
## 4.5.6
### core
#### Added
- Add a new method has_valid_group in \core\report_helper that will return true or false depending if the user has a valid group. This is mainly false in case the user is not in any group in SEPARATEGROUPS. Used in report_log and report_loglive
For more information see [MDL-84464](https://tracker.moodle.org/browse/MDL-84464)
#### Changed
- The `\core\attribute\deprecated` attribute constructor `$replacement` parameter now defaults to null, and can be omitted
For more information see [MDL-84531](https://tracker.moodle.org/browse/MDL-84531)
- Added a new `\core\deprecation::emit_deprecation()` method which should be used in places where a deprecation is known to occur. This method will throw debugging if no deprecation notice was found, for example:
```php
public function deprecated_method(): void {
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
}
```
For more information see [MDL-85897](https://tracker.moodle.org/browse/MDL-85897)
### core_message
#### Added
- The web service `core_message_get_member_info` additionally returns `cancreatecontact` which is a boolean value for a user's permission to add a contact.
For more information see [MDL-72123](https://tracker.moodle.org/browse/MDL-72123)
## 4.5.5
### core
+18 -9
View File
@@ -69,7 +69,7 @@ if ($options['enablelater']) {
}
$time = time() + ($options['enablelater']*60);
set_config('maintenance_later', $time);
set_config('maintenance_later', $time, null, true);
echo get_string('clistatusenabledlater', 'admin', userdate($time))."\n";
return 0;
@@ -77,23 +77,32 @@ if ($options['enablelater']) {
} else if ($options['enable']) {
if (file_exists("$CFG->dataroot/climaintenance.html")) {
// The maintenance is already enabled, nothing to do.
} else {
enable_cli_maintenance_mode();
exit(0);
}
enable_cli_maintenance_mode();
set_config('maintenance_enabled', 'cli mode', null, true);
if (isset($CFG->maintenance_later)) {
unset_config('maintenance_later', null, true);
}
set_config('maintenance_enabled', 0);
unset_config('maintenance_later');
echo get_string('sitemaintenanceoncli', 'admin')."\n";
exit(0);
} else if ($options['enableold']) {
set_config('maintenance_enabled', 1);
unset_config('maintenance_later');
set_config('maintenance_enabled', 1, null, true);
if (isset($CFG->maintenance_later)) {
unset_config('maintenance_later', null, true);
}
echo get_string('sitemaintenanceon', 'admin')."\n";
exit(0);
} else if ($options['disable']) {
set_config('maintenance_enabled', 0);
unset_config('maintenance_later');
if ($CFG->maintenance_enabled !== '0') {
set_config('maintenance_enabled', 0, null, true);
}
if (isset($CFG->maintenance_later)) {
unset_config('maintenance_later', null, true);
}
if (file_exists("$CFG->dataroot/climaintenance.html")) {
unlink("$CFG->dataroot/climaintenance.html");
}
+1
View File
@@ -24,6 +24,7 @@
*/
define('CLI_SCRIPT', true);
define('IGNORE_COMPONENT_CACHE', true);
require(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/clilib.php');
+3 -3
View File
@@ -158,13 +158,13 @@ if ($options['plugins']) {
if (is_null($plugin)) {
cli_writeln('Unknown plugin: ' . $component);
} else {
$pluginstring = $plugin->component . "\t" . $plugin->displayname;
$pluginstring = $component . "\t" . $plugin->displayname;
if ($pluginman->can_uninstall_plugin($plugin->component)) {
if ($pluginman->can_uninstall_plugin($component)) {
if ($options['run']) {
cli_writeln('Uninstalling: ' . $pluginstring);
$progress = new progress_trace_buffer(new text_progress_trace(), true);
$pluginman->uninstall_plugin($plugin->component, $progress);
$pluginman->uninstall_plugin($component, $progress);
$progress->finished();
cli_write($progress->get_buffer());
} else {
+7 -1
View File
@@ -4716,6 +4716,7 @@
<VENDOR name="mysql" version="8.4" />
<VENDOR name="postgres" version="14" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="auroramysql" version="8.0" />
</DATABASE>
<PHP version="8.2.0" level="required">
</PHP>
@@ -4895,6 +4896,8 @@
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_aurora_version" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="5.1" requires="4.2.3">
@@ -4906,8 +4909,9 @@
<DATABASE level="required">
<VENDOR name="mariadb" version="10.11.0" />
<VENDOR name="mysql" version="8.4" />
<VENDOR name="postgres" version="14" />
<VENDOR name="postgres" version="15" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="auroramysql" version="8.0" />
</DATABASE>
<PHP version="8.2.0" level="required">
</PHP>
@@ -5092,6 +5096,8 @@
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_aurora_version" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+2
View File
@@ -23,6 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('IGNORE_COMPONENT_CACHE', true);
require_once('../config.php');
require_once($CFG->libdir.'/adminlib.php');
+1 -1
View File
@@ -2218,7 +2218,7 @@ class core_admin_renderer extends plugin_renderer_base {
public function moodleorg_registration_message() {
$a = new stdClass();
$a->moreinformation = '#id_sitestats'; // More information anchor.
$a->moodleapp = HUB_MOODLEORGHUBURL . '/solutions/moodle-app/';
$a->moodleapp = MOODLE_PRODUCTURL . '/solutions/moodle-app/';
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub', $a), FORMAT_MARKDOWN);
$out .= html_writer::link(
+15
View File
@@ -65,6 +65,21 @@ if (($hassiteconfig || has_any_capability(array(
new lang_string('allowexternalbackpack', 'badges'),
new lang_string('allowexternalbackpack_desc', 'badges'), 1));
$defaultcanvasregions = [
'Australia|https://au.badgr.io|https://api.au.badgr.io/v2',
'Canada|https://ca.badgr.io|https://api.ca.badgr.io/v2',
'Europe|https://eu.badgr.io|https://api.eu.badgr.io/v2',
'Singapore|https://sg.badgr.io|https://api.sg.badgr.io/v2',
'United States|https://badgr.io|https://api.badgr.io/v2',
];
$globalsettings->add(new admin_setting_configtextarea(
'badges_canvasregions',
new lang_string('canvasregions', 'badges'),
new lang_string('canvasregions_desc', 'badges'),
implode("\n", $defaultcanvasregions),
PARAM_RAW,
));
$ADMIN->add('badges', $globalsettings);
$ADMIN->add('badges',
+8
View File
@@ -70,6 +70,14 @@ if ($hassiteconfig) {
$temp->add(new admin_setting_configtext('googlemapkey3', new lang_string('googlemapkey3', 'core_admin'),
new lang_string('googlemapkey3_help', 'core_admin'), '', PARAM_RAW, 60));
$temp->add(new admin_setting_configtext(
'geopluginapikey',
new lang_string('geopluginapikey', 'core_admin'),
new lang_string('geopluginapikey_desc', 'core_admin'),
'',
PARAM_TEXT,
));
}
$ADMIN->add('location', $temp);
+1 -1
View File
@@ -79,7 +79,7 @@ Feature: An administrator can browse user accounts
Scenario: Delete a user account
Given I navigate to "Users > Accounts > Browse list of users" in site administration
And I press "Delete" action in the "User One" report row
And I should see "Are you absolutely sure you want to completely delete the user User One" in the "Delete user" "dialogue"
And I should see "Are you sure you want to delete user User One" in the "Delete user" "dialogue"
And I click on "Delete" "button" in the "Delete user" "dialogue"
Then I should see "Deleted user User One"
And I should not see "User One" in the "reportbuilder-table" "table"
@@ -20,9 +20,9 @@ Feature: I can apply presets
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should exist in the "Social" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
And "Enabled" "icon" should exist in the "Immediate feedback with CBM" "table_row"
And "Enabled" "link" should exist in the "Immediate feedback with CBM" "table_row"
And I navigate to "Plugins > Question types > Manage question types" in site administration
And "Enabled" "icon" should exist in the "Calculated multichoice" "table_row"
And "Enabled" "link" should exist in the "Calculated multichoice" "table_row"
When I navigate to "Site admin presets" in site administration
And I press "Review settings and apply" action in the "Starter" report row
And I should see "Setting changes"
@@ -113,9 +113,9 @@ Feature: I can apply presets
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should not exist in the "Social" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
And "Enabled" "icon" should not exist in the "Immediate feedback with CBM" "table_row"
And "Enabled" "link" should not exist in the "Immediate feedback with CBM" "table_row"
And I navigate to "Plugins > Question types > Manage question types" in site administration
And "Enabled" "icon" should not exist in the "Calculated multichoice" "table_row"
And "Enabled" "link" should not exist in the "Calculated multichoice" "table_row"
Scenario: Re-applying Starter Moodle preset does not display setting changes
# Apply Starter preset.
@@ -27,9 +27,9 @@ Feature: I can revert changes after a load
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should not exist in the "Social" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
And "Enabled" "icon" should not exist in the "Immediate feedback with CBM" "table_row"
And "Enabled" "link" should not exist in the "Immediate feedback with CBM" "table_row"
And I navigate to "Plugins > Question types > Manage question types" in site administration
And "Enabled" "icon" should not exist in the "Calculated multichoice" "table_row"
And "Enabled" "link" should not exist in the "Calculated multichoice" "table_row"
And I navigate to "Site admin presets" in site administration
And I press "Show version history" action in the "Starter" report row
When I click on "Restore this version" "link"
@@ -45,6 +45,6 @@ Feature: I can revert changes after a load
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
And "Disable" "icon" should exist in the "Social" "table_row"
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
And "Enabled" "icon" should exist in the "Immediate feedback with CBM" "table_row"
And "Enabled" "link" should exist in the "Immediate feedback with CBM" "table_row"
And I navigate to "Plugins > Question types > Manage question types" in site administration
And "Enabled" "icon" should exist in the "Calculated multichoice" "table_row"
And "Enabled" "link" should exist in the "Calculated multichoice" "table_row"
+11 -2
View File
@@ -24,5 +24,14 @@
defined('MOODLE_INTERNAL') || die;
// Remove the 'enableaccessibilitytools' configuration setting in case of reinstall.
unset_config('enableaccessibilitytools');
/**
* Plugin uninstall code.
*
* @return true
*/
function xmldb_tool_brickfield_uninstall(): bool {
// Remove the 'enableaccessibilitytools' configuration setting in case of reinstall.
unset_config('enableaccessibilitytools');
return true;
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace tool_capability;
// phpcs:disable moodle.PHPUnit.TestCaseProvider.dataProviderSyntaxMethodNotFound
/**
* Detect common problems in capability definitions of plugins.
*
* @group plugin_checks
* @package tool_capability
* @copyright 2025 Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class plugin_checks_test extends \core\tests\plugin_checks_testcase {
/**
* Verify contents of plugin db/access.php file.
*
* @dataProvider all_plugins_provider
* @coversNothing
*
* @param string $component
* @param string $plugintype
* @param string $pluginname
* @param string $dir
*/
public function test_db_access_file(string $component, string $plugintype, string $pluginname, string $dir): void {
global $CFG;
$stringmanager = get_string_manager();
$corerolefile = "$CFG->dirroot/lang/en/role.php";
$langfile = "$dir/lang/en/$component.php";
$file = "$dir/db/access.php";
$capabilities = $this->fetch_array_from_file($file, 'capabilities');
if (!$capabilities) {
$this->expectNotToPerformAssertions();
return;
}
foreach ($capabilities as $capname => $capability) {
if ($plugintype === 'qbank' && str_starts_with($capname, 'moodle/question:')) {
// Question bank capabilities are irregular.
$strname = explode('/', $capname, 2)[1];
$this->assertTrue($stringmanager->string_exists($strname, 'core_role'),
"Missing capability name string '$strname' in $corerolefile");
continue;
}
$this->assertMatchesRegularExpression("|^$plugintype/$pluginname:[a-z0-9_]+$|", $capname);
$strname = substr($capname, strlen($plugintype) + 1);
$this->assertTrue($stringmanager->string_exists($strname, $component),
"Missing capability name string '$strname' in $langfile");
$this->assertSame($capname, clean_param($capname, PARAM_CAPABILITY));
}
}
}
@@ -39,7 +39,7 @@ The component output classes can render an action menu entirely in PHP. The step
The following code is a basic example of an action menu:
{{< php >}}
```php
/** @var core_renderer $output*/
$output = $PAGE->get_renderer('core');
@@ -54,11 +54,11 @@ $menu->add(new action_menu_link(
));
echo $output->render($menu);
{{< / php >}}
```
And this is the same example but passing the items in the creation:
{{< php >}}
```php
/** @var core_renderer $output*/
$output = $PAGE->get_renderer('core');
@@ -72,32 +72,64 @@ $menu = new action_menu([
]);
echo $output->render($menu);
{{< / php >}}
```
### Setup the menu trigger
By default, the action menu trigger is a cog icon. However, the class has methods to convert it to a kebab menu or even display any arbitrary content.
By default, the action menu trigger is a button that uses the `t/edit_menu` icon and is displayed with a caret. However, the class has methods to convert it to a kebab menu or even display any arbitrary content.
Example of a kebab menu:
#### Example of a kebab menu
{{< php >}}
```php
/** @var core_renderer $output*/
$output = $PAGE->get_renderer('core');
$menu = new action_menu();
$menu->set_kebab_trigger(get_string('edit'), $output);
$menu->set_additional_classes('fields-actions');
{{< / php >}}
```
Example of a custom trigger:
#### Customising the menu trigger
{{< php >}}
/** @var core_renderer $output*/
$output = $PAGE->get_renderer('core');
##### Trigger with a text label
```php
// This example displays an "Edit" label for the trigger.
$menu = new action_menu();
$menu->set_menu_trigger(get_string('edit'));
{{< / php >}}
```
##### Trigger with an icon
This example displays an icon for the trigger. When rendering the menu trigger button as an icon button, ensure that the icon is rendered as a decorative image. If you are using a `pix_icon`, pass an empty `$alt` parameter to make the icon decorative.
The accessible name of the icon button should be set within the button element itself. You can set an accessible name for the icon trigger button either by:
* Using the `::set_action_label()` method.
```php
$menu = new action_menu();
// Make sure the pix icon is rendered as a decorative image by passing an empty alt parameter.
$icon = $output->pix_icon('t/edit', '');
$menu->set_menu_trigger($icon);
$menu->set_action_label(get_string('edit'));
```
* Or alternatively, by adding a visually hidden text alongside the icon.
```php
$menu = new action_menu();
// Make sure the pix icon is rendered as a decorative image by passing an empty alt parameter.
$icon = $output->pix_icon('t/edit', '');
// Add a visually hidden text label for the trigger button.
$icon .= html_writer::span(get_string('edit'), 'sr-only');
$menu->set_menu_trigger($icon);
```
##### Removing the caret symbol
You may also remove the caret symbol by adding a `no-caret` class to the `triggerextraclasses` property.
```php
$menu->triggerextraclasses = 'no-caret';
```
### Add items
@@ -108,7 +140,7 @@ Secondary items: are displayed inside the action menu dropdown.
The item location must be configured before adding the element. The following example shows different ways to add primary and secondary menu items.
{{< php >}}
```php
// Primary items examples.
$menu->add(new action_menu_link(
new moodle_url($PAGE->url),
@@ -134,7 +166,7 @@ $menu->add(new action_menu_link_secondary(
new pix_icon('t/user', ''),
'Action link example',
));
{{< / php >}}
```
## Types of items
@@ -178,7 +210,7 @@ Construct params:
The following example creates a subpanel using a renderable choicelist instance:
{{< php >}}
```php
/** @var core_renderer $output*/
$output = $PAGE->get_renderer('core');
@@ -205,7 +237,7 @@ $menu->add(new core\output\local\action_menu\subpanel(
));
echo $output->render($menu);
{{< / php >}}
```
### HTML string
@@ -77,7 +77,7 @@ echo '<p><strong>Important note:</strong> actions menus are not prepared
to be displayed inside iframes. You may need to scroll to see the
action menu options.</p>';
echo $output->heading("Action menu default example", 4);
echo $output->heading("Action menu default example", 3);
$menu = new action_menu();
@@ -87,11 +87,11 @@ $menu->add($subpanel);
$menu->add($basicactionlink);
echo '<div class="border m-3 p-3 d-flex flex-row">';
echo '<div class="flex-fill">Example of default an action menu</div><div>';
echo '<div class="flex-fill">An action menu rendered without customisation</div><div>';
echo $OUTPUT->render($menu);
echo '</div></div>';
echo $output->heading("Kebab menu example", 4);
echo $output->heading("Kebab menu example", 3);
$menu = new action_menu();
$menu->set_kebab_trigger(get_string('edit'), $output);
@@ -106,11 +106,11 @@ $menu->add(new core\output\local\action_menu\subpanel(
$menu->add($basicactionlink);
echo '<div class="border m-3 p-3 d-flex flex-row">';
echo '<div class="flex-fill">Example of kebab menu</div><div>';
echo '<div class="flex-fill">An action menu with a kebab menu trigger button</div><div>';
echo $OUTPUT->render($menu);
echo '</div></div>';
echo $output->heading("Custom trigger menu example", 4);
echo $output->heading("Custom trigger menu examples", 3);
$menu = new action_menu();
$menu->set_menu_trigger(get_string('edit'));
@@ -124,11 +124,53 @@ $menu->add(new core\output\local\action_menu\subpanel(
$menu->add($basicactionlink);
echo '<div class="border m-3 p-3 d-flex flex-row">';
echo '<div class="flex-fill">Example of kebab menu</div><div>';
echo '<div class="flex-fill">An action menu with a menu trigger button with a custom text label</div><div>';
echo $OUTPUT->render($menu);
echo '</div></div>';
echo $output->heading("Primary actions menu example", 4);
$links = [
$basicactionlink,
$basicactionlink,
];
$moreicon = $OUTPUT->pix_icon('i/moremenu', '');
$editicon = $OUTPUT->pix_icon('t/edit', '');
$menu = new action_menu($links);
$menu->set_menu_trigger($moreicon);
$menu->set_action_label(get_string('moremenu'));
$menu->triggerattributes = [
'title' => get_string('moremenu'),
];
echo '<div class="border m-3 p-3 d-flex flex-row">';
echo '<div class="flex-fill">An action menu with only an icon for its custom menu trigger button</div>';
echo html_writer::div($OUTPUT->render($menu));
echo '</div>';
echo '<div class="border m-3 p-3 d-flex flex-row">';
echo '<div class="flex-fill">An action menu with only an icon for its custom menu trigger button with the caret removed</div>';
$menu->triggerextraclasses = 'no-caret';
echo html_writer::div($OUTPUT->render($menu));
echo '</div>';
$menu = new action_menu($links);
$menu->set_menu_trigger($editicon . ' ' . get_string('edit'));
echo '<div class="border m-3 p-3 d-flex flex-row">';
echo '<div class="flex-fill">An action menu with an icon and visible text for its custom menu trigger button</div>';
echo html_writer::div($OUTPUT->render($menu));
echo '</div>';
$menu = new action_menu($links);
$menu->set_menu_trigger($editicon . ' ' . html_writer::span(get_string('edit'), 'sr-only'));
echo '<div class="border m-3 p-3 d-flex flex-row">';
echo '<div class="flex-fill">An action menu with an icon and visually hidden text for its custom menu trigger button</div>';
echo html_writer::div($OUTPUT->render($menu));
echo '</div>';
echo $output->heading("Primary actions menu example", 3);
$menu = new action_menu();
$menu->set_menu_trigger(get_string('edit'));
@@ -28,6 +28,7 @@ use renderable;
use templatable;
use renderer_base;
use single_button;
use action_link;
use stdClass;
use moodle_url;
use context;
@@ -65,6 +66,8 @@ class manage_competency_frameworks_page implements renderable, templatable {
* @param context $pagecontext The page context
*/
public function __construct(context $pagecontext) {
global $OUTPUT;
$this->pagecontext = $pagecontext;
if (competency_framework::can_manage_context($this->pagecontext)) {
@@ -74,10 +77,13 @@ class manage_competency_frameworks_page implements renderable, templatable {
'get'
);
$this->navigation[] = $addpage;
$competenciesrepository = new single_button(
$icon = $OUTPUT->pix_icon('i/externallink', get_string('opensinnewwindow'), 'moodle', ['class' => 'ms-1']);
$competenciesrepository = new action_link(
new moodle_url('https://moodle.net/search', ['q' => 'competency frameworks']),
get_string('competencyframeworksrepository', 'tool_lp'),
'get'
get_string('competencyframeworksrepository', 'tool_lp') . $icon,
null,
['target' => '_blank', 'class' => 'btn btn-secondary ms-sm-2'],
);
$this->navigation[] = $competenciesrepository;
}
+17 -26
View File
@@ -50,7 +50,7 @@ class manager {
],
'tool_mfa' => [
'guidance',
]
],
];
/**
@@ -441,6 +441,21 @@ class manager {
$url = new \moodle_url($url);
}
// Admin not setup.
if (!empty($CFG->adminsetuppending)) {
return self::NO_REDIRECT;
}
// Honor prevent_redirect.
if ($preventredirect) {
return self::NO_REDIRECT;
}
// Login as.
if (\core\session\manager::is_loggedinas()) {
return self::NO_REDIRECT;
}
// Check for pluginfile.php urls.
$pluginfileurl = new \moodle_url('/pluginfile.php');
if ($url->compare($pluginfileurl)) {
@@ -485,22 +500,6 @@ class manager {
}
}
// Admin not setup.
if (!empty($CFG->adminsetuppending)) {
return self::NO_REDIRECT;
}
// Initial installation.
// We get this for free from get_plugins_with_function.
// Upgrade check.
// We get this for free from get_plugins_with_function.
// Honor prevent_redirect.
if ($preventredirect) {
return self::NO_REDIRECT;
}
// User not properly setup.
if (user_not_fully_set_up($USER)) {
return self::NO_REDIRECT;
@@ -516,11 +515,6 @@ class manager {
return self::NO_REDIRECT;
}
// Login as.
if (\core\session\manager::is_loggedinas()) {
return self::NO_REDIRECT;
}
// Site policy.
if (isset($USER->policyagreed) && !$USER->policyagreed) {
$manager = new \core_privacy\local\sitepolicy\manager();
@@ -538,11 +532,8 @@ class manager {
}
// WS/AJAX check.
// Prevents any potential bypassing of multi-factor authentication.
if (WS_SERVER || AJAX_SCRIPT) {
if (isset($SESSION->mfa_pending) && !empty($SESSION->mfa_pending)) {
// Allow AJAX and WS, but never from auth.php.
return self::NO_REDIRECT;
}
return self::REDIRECT_EXCEPTION;
}
+10 -10
View File
@@ -379,7 +379,7 @@ class renderer extends \plugin_renderer_base {
WHERE deleted = 0
AND suspended = 0
GROUP BY auth";
$allusersinfo = $DB->get_records_sql($alluserssql, []);
$allusersinfo = $DB->get_records_sql_menu($alluserssql);
$noncompletesql = "SELECT u.auth, COUNT(u.id)
FROM {user} u
@@ -388,7 +388,7 @@ class renderer extends \plugin_renderer_base {
AND (mfaa.lastverified < ?
OR mfaa.lastverified IS NULL)
GROUP BY u.auth";
$noncompleteinfo = $DB->get_records_sql($noncompletesql, [$lookback, $lookback]);
$noncompleteinfo = $DB->get_records_sql_menu($noncompletesql, [$lookback, $lookback]);
$nologinsql = "SELECT auth, COUNT(id)
FROM {user}
@@ -396,7 +396,7 @@ class renderer extends \plugin_renderer_base {
AND suspended = 0
AND lastlogin < ?
GROUP BY auth";
$nologininfo = $DB->get_records_sql($nologinsql, [$lookback]);
$nologininfo = $DB->get_records_sql_menu($nologinsql, [$lookback]);
$mfauserssql = "SELECT auth,
COUNT(DISTINCT tm.userid)
@@ -406,7 +406,7 @@ class renderer extends \plugin_renderer_base {
AND u.deleted = 0
AND u.suspended = 0
GROUP BY u.auth";
$mfausersinfo = $DB->get_records_sql($mfauserssql, [$lookback]);
$mfausersinfo = $DB->get_records_sql_menu($mfauserssql, [$lookback]);
$factorsusedsql = "SELECT CONCAT(u.auth, '_', tm.factor) as id,
COUNT(*)
@@ -417,7 +417,7 @@ class renderer extends \plugin_renderer_base {
AND u.suspended = 0
AND (tm.revoked = 0 OR (tm.revoked = 1 AND tm.timemodified > ?))
GROUP BY CONCAT(u.auth, '_', tm.factor)";
$factorsusedinfo = $DB->get_records_sql($factorsusedsql, [$lookback, $lookback]);
$factorsusedinfo = $DB->get_records_sql_menu($factorsusedsql, [$lookback, $lookback]);
// Auth rows.
$authtypes = get_enabled_auth_plugins(true);
@@ -426,10 +426,10 @@ class renderer extends \plugin_renderer_base {
$row[] = \html_writer::tag('b', $authtype);
// Setup the overall totals columns.
$row[] = $allusersinfo[$authtype]->count ?? '-';
$row[] = $mfausersinfo[$authtype]->count ?? '-';
$row[] = $noncompleteinfo[$authtype]->count ?? '-';
$row[] = $nologininfo[$authtype]->count ?? '-';
$row[] = $allusersinfo[$authtype] ?? '-';
$row[] = $mfausersinfo[$authtype] ?? '-';
$row[] = $noncompleteinfo[$authtype] ?? '-';
$row[] = $nologininfo[$authtype] ?? '-';
// Create a running counter for the total.
$authtotal = 0;
@@ -439,7 +439,7 @@ class renderer extends \plugin_renderer_base {
if (!empty($column)) {
// Get the information from the data key.
$key = $authtype . '_' . $column;
$count = $factorsusedinfo[$key]->count ?? 0;
$count = $factorsusedinfo[$key] ?? 0;
$authtotal += $count;
$row[] = $count ? format_float($count, 0) : '-';
@@ -71,7 +71,7 @@ $string['setupfactor:instructionsverification'] = '3. Enter the verification cod
$string['setupfactor:intro'] = 'To set up this method, you need to have a device with an authenticator app. If you don\'t have an app, you can download one. For example, <a href="https://2fas.com/" target="_blank">2FAS Auth</a>, <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a>, Google Authenticator, Microsoft Authenticator or Twilio Authy.';
$string['setupfactor:key'] = 'Secret key: ';
$string['setupfactor:link'] = 'Or enter details manually.';
$string['setupfactor:link_help'] = 'If you are on a mobile device and already have an authenticator app installed this link may work. Note that using TOTP on the same device as you login on can weaken the benefits of MFA.';
$string['setupfactor:link_help'] = 'If you are on a mobile device and already have an authenticator app installed this link may work. Note that using TOTP on the same device as you log in on can weaken the benefits of MFA.';
$string['setupfactor:linklabel'] = 'Open app already installed on this device';
$string['setupfactor:mode'] = 'Mode:';
$string['setupfactor:mode:timebased'] = 'Time-based';
-1
View File
@@ -2,4 +2,3 @@ created,tool_mfa
createdfromip,tool_mfa
lastverified,tool_mfa
revoke,tool_mfa
setup,tool_mfa
+1 -1
View File
@@ -150,6 +150,7 @@ $string['settings:redir_exclusions'] = 'URLS which should not redirect the MFA c
$string['settings:redir_exclusions_help'] = 'Each new line is a relative URL from the siteroot for which the MFA check will not redirect from';
$string['settings:weight'] = 'Factor weight';
$string['settings:weight_help'] = 'The weight of this factor if passed. A user needs at least 100 points to log in.';
$string['setup'] = 'Setup';
$string['setupfactor'] = 'Set up factor';
$string['setupfactorbuttonadditional'] = 'Add additional factor';
$string['setuprequired'] = 'User setup';
@@ -180,4 +181,3 @@ $string['created'] = 'Created';
$string['createdfromip'] = 'Created from IP';
$string['lastverified'] = 'Last verified';
$string['revoke'] = 'Revoke';
$string['setup'] = 'Setup';
+2
View File
@@ -590,6 +590,7 @@ class api {
'subdirs' => false,
'maxfiles' => -1,
'context' => context_system::instance(),
'noclean' => true,
];
}
@@ -606,6 +607,7 @@ class api {
'subdirs' => false,
'maxfiles' => -1,
'context' => context_system::instance(),
'noclean' => true,
];
}
+3 -3
View File
@@ -52,9 +52,9 @@ if ($listurl) {
}
// Add a new nav item to make $listurl clickable for the Boost theme.
if (isset($script)) {
$lastrunnav = get_string('lastrun', 'tool_profiling');
$PAGE->navbar->add($lastrunnav);
if (isset($script) || isset($runid)) {
$detailnav = get_string('detailview', 'tool_profiling');
$PAGE->navbar->add($detailnav);
}
// Header
@@ -31,6 +31,7 @@ $string['cannotfindanyrunforurl'] = 'Sorry, cannot find any profiling run for th
$string['cannotfindanyrunforrunid'] = 'Sorry, cannot find the \'{$a}\' profiling run';
$string['comment'] = 'Comment';
$string['cputime'] = 'CPU time';
$string['detailview'] = 'Summary of run';
$string['differencesbetween2runsof'] = 'Differences between 2 runs of {$a}';
$string['executiontime'] = 'Execution time';
$string['export'] = 'Export';
+1 -1
View File
@@ -254,7 +254,7 @@ class course_bin extends base_bin {
// This hack will be removed once recycle bin switches to use its own backup mode, with
// own preferences and 100% separate from MOODLE_AUTOMATED.
// TODO: Remove this as part of MDL-65228.
$forcedrestoresettings = $CFG->forced_plugin_settings['restore'] ?? null;
$forcedrestoresettings = $CFG->forced_plugin_settings['restore'] ?? [];
$CFG->forced_plugin_settings['restore']['restore_general_users'] = 1;
$CFG->forced_plugin_settings['restore']['restore_general_groups'] = 1;
+1 -1
View File
@@ -274,7 +274,7 @@ class tool_task_renderer extends plugin_renderer_base {
$nextruntime = $task->get_next_run_time();
$due = $nextruntime < $now;
if ($task->get_attempts_available() > 0) {
$nextrun = $due ? userdate($nextruntime) : get_string('asap', 'tool_task');
$nextrun = $due ? get_string('asap', 'tool_task') : userdate($nextruntime);
} else {
$nextrun = get_string('never', 'admin');
}
@@ -0,0 +1,23 @@
@tool @tool_task @javascript
Feature: Manage adhoc task
In order to manage adhoc tasks
As an admin
I need to be able to view adhoc tasks
Scenario Outline: View adhoc tasks next run time
Given the following "tool_task > adhoc tasks" exist:
| classname | seconds | hostname | pid | nextruntime |
| \core\task\asynchronous_backup_task | 0 | c69335460f7f | 1915 | <nextruntime> |
When I log in as "admin"
And I navigate to "Server > Tasks > Ad hoc tasks" in site administration
Then the following should exist in the "Ad hoc tasks" table:
| Component / Class name | Next run |
| asynchronous_backup_task | <nextruntimestr> |
And I click on "asynchronous_backup_task" "link" in the "Ad hoc tasks" "table"
And the following should exist in the "\core\task\asynchronous_backup_task Ad hoc tasks" table:
| Next run |
| <nextruntimestr> |
Examples:
| nextruntime | nextruntimestr |
| ##yesterday## | ASAP |
| ##tomorrow noon## | ##tomorrow noon##%A, %d %B %Y, %I:%M## |
+3 -3
View File
@@ -43,7 +43,7 @@ class tool_task_generator extends testing_module_generator {
global $DB;
$conditions = ['classname' => $data['classname']];
$record = $DB->get_record('task_scheduled', $conditions, '*', MUST_EXIST);
$record->timestarted = time() - $data['seconds'];
$record->timestarted = $data['seconds'] > 0 ? time() - $data['seconds'] : 0;
$record->hostname = $data['hostname'];
$record->pid = $data['pid'];
$DB->update_record('task_scheduled', $record);
@@ -59,8 +59,8 @@ class tool_task_generator extends testing_module_generator {
global $DB;
$adhoctask = (object)[
'classname' => $data['classname'],
'nextruntime' => 0,
'timestarted' => time() - $data['seconds'],
'nextruntime' => $data['nextruntime'] ?? 0,
'timestarted' => $data['seconds'] > 0 ? time() - $data['seconds'] : 0,
'hostname' => $data['hostname'],
'pid' => $data['pid'],
];
+1 -1
View File
@@ -1146,7 +1146,7 @@ class tool_uploadcourse_course {
// Now update values.
// Sort out plugin specific fields.
$modifiedinstance = $plugin->update_enrol_plugin_data($course->id, $method, $instance);
$modifiedinstance = $plugin->update_enrol_plugin_data($course->id, $method, clone $instance);
$plugin->update_instance($instance, $modifiedinstance);
} else {
foreach ($errors as $key => $message) {
@@ -126,3 +126,19 @@ Feature: An admin can create courses with cohort enrolments using a CSV file
And I press "Cancel"
And I click on "Edit" "link" in the "Cohort 4" "table_row"
And the field "Assign role" matches value "Non-editing teacher"
@javascript
Scenario: Uploading a cohort enrolment method with a non-default role applies only the specified role
Given the following "users" exist:
| username | firstname | lastname | email |
| t1 | Teacher | 1 | s1@example.com |
And the following "cohort members" exist:
| user | cohort |
| t1 | CV4 |
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_cohort_multiple.csv" file to "File" filemanager
And I click on "Preview" "button"
And I click on "Upload courses" "button"
When I am on the "Course 1" "course" page
And I navigate to course participants
Then I should see "Non-editing teacher" in the "Teacher 1" "table_row"
And I should not see "Student" in the "Teacher 1" "table_row"
@@ -25,7 +25,7 @@ Feature: An admin can update courses enrolments using a CSV file
When I click on "Upload courses" "button"
Then I should see "Course updated"
And I am on the "Course 1" "enrolment methods" page
And "Disable" "icon" should exist in the "Guest access" "table_row"
And "Disable" "link" should exist in the "Guest access" "table_row"
@javascript
Scenario: Creating enrolment method by disabling it
@@ -42,7 +42,7 @@ Feature: An admin can update courses enrolments using a CSV file
When I click on "Upload courses" "button"
Then I should see "Course updated"
And I am on the "Course 1" "enrolment methods" page
And "Enable" "icon" should exist in the "Guest access" "table_row"
And "Enable" "link" should exist in the "Guest access" "table_row"
@javascript
Scenario: Enabling enrolment method
@@ -55,13 +55,13 @@ Feature: An admin can update courses enrolments using a CSV file
When I click on "Upload courses" "button"
Then I should see "Course updated"
And I am on the "Course 1" "enrolment methods" page
And "Disable" "icon" should exist in the "Guest access" "table_row"
And "Disable" "link" should exist in the "Guest access" "table_row"
@javascript
Scenario: Disable an enrolment method
Given I am on the "Course 1" "enrolment methods" page
And I click on "Enable" "link" in the "Guest access" "table_row"
And "Disable" "icon" should exist in the "Guest access" "table_row"
And "Disable" "link" should exist in the "Guest access" "table_row"
And I navigate to "Courses > Upload courses" in site administration
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_disable.csv" file to "File" filemanager
And I set the field "Upload mode" to "Only update existing courses"
@@ -71,7 +71,7 @@ Feature: An admin can update courses enrolments using a CSV file
When I click on "Upload courses" "button"
Then I should see "Course updated"
And I am on the "Course 1" "enrolment methods" page
And "Enable" "icon" should exist in the "Guest access" "table_row"
And "Enable" "link" should exist in the "Guest access" "table_row"
@javascript
Scenario: Delete an enrolment method
+6 -6
View File
@@ -910,8 +910,8 @@ class process {
}
if ($this->get_bulk() == UU_BULK_UPDATED or $this->get_bulk() == UU_BULK_ALL) {
if (!in_array($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[] = $user->id;
if (!array_key_exists($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[$user->id] = $user->id;
}
}
@@ -924,8 +924,8 @@ class process {
$this->usersuptodate++;
if ($this->get_bulk() == UU_BULK_ALL) {
if (!in_array($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[] = $user->id;
if (!array_key_exists($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[$user->id] = $user->id;
}
}
}
@@ -1059,8 +1059,8 @@ class process {
\context_user::instance($user->id);
if ($this->get_bulk() == UU_BULK_NEW or $this->get_bulk() == UU_BULK_ALL) {
if (!in_array($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[] = $user->id;
if (!array_key_exists($user->id, $SESSION->bulk_users)) {
$SESSION->bulk_users[$user->id] = $user->id;
}
}
}
@@ -1,12 +0,0 @@
@tool @tool_xmldb
Feature: The Reconcile XMLDB files report works and all the files are ok
In order to ensure that all the XMLDB Editor xml files are generated properly
As a developer
I need to be able to run the Reconcile XMLDB files report and verify that everything is ok
Scenario: The Reconcile XMLDB files reports that all files are ok
Given I log in as "admin"
And I navigate to "Development > XMLDB editor" in site administration
When I follow "[Reconcile XMLDB files]"
Then I should see "Look for XMLDB files needing reconciling"
And I should see "All files are OK. No reconciling is needed."
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -31,6 +31,8 @@ import AIHelper from 'core_ai/helper';
import DrawerEvents from 'core/drawer_events';
import {subscribe} from 'core/pubsub';
import * as MessageDrawerHelper from 'core_message/message_drawer_helper';
import * as FocusLock from 'core/local/aria/focuslock';
import {isSmall} from "core/pagehelpers";
const AICourseAssist = class {
@@ -60,6 +62,7 @@ const AICourseAssist = class {
this.jumpToElement = document.querySelector(Selectors.ELEMENTS.JUMPTO);
this.summaryActionElement = document.querySelector(Selectors.ACTIONS.SUMMARY);
this.aiDrawerCloseElement = this.aiDrawerElement.querySelector(Selectors.ELEMENTS.AIDRAWER_CLOSE);
this.isDrawerFocusLocked = false;
this.registerEventListeners();
}
@@ -85,6 +88,12 @@ const AICourseAssist = class {
}
});
document.addEventListener('keydown', e => {
if (this.isAIDrawerOpen() && e.key === 'Escape') {
this.closeAIDrawer();
}
});
// Close AI drawer if message drawer is shown.
subscribe(DrawerEvents.DRAWER_SHOWN, () => {
if (this.isAIDrawerOpen()) {
@@ -198,12 +207,27 @@ const AICourseAssist = class {
}
this.jumpToElement.setAttribute('tabindex', 0);
this.jumpToElement.focus();
// If the AI drawer is opened on a small screen, we need to trap the focus tab within the AI drawer.
if (isSmall()) {
FocusLock.trapFocus(this.aiDrawerElement);
this.aiDrawerElement.setAttribute('aria-modal', 'true');
this.aiDrawerElement.setAttribute('role', 'dialog');
this.isDrawerFocusLocked = true;
}
}
/**
* Close the AI drawer.
*/
closeAIDrawer() {
// Untrap focus if it was locked.
if (this.isDrawerFocusLocked) {
FocusLock.untrapFocus();
this.aiDrawerElement.removeAttribute('aria-modal');
this.aiDrawerElement.setAttribute('role', 'region');
}
this.aiDrawerElement.classList.remove('show');
this.aiDrawerElement.setAttribute('tabindex', '-1');
this.aiDrawerBodyElement.removeAttribute('aria-live');
@@ -22,6 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['aidrawerlabel'] = 'AI-generated content drawer';
$string['aisummary'] = 'AI summary';
$string['courseassist:summarise_text'] = 'Summarise text';
$string['copy'] = 'Copy';
@@ -31,7 +31,7 @@
"content": "<p>Content to display</p>"
}
}}
<div class="ai-drawer" id="ai-drawer" tabindex="-1">
<div class="ai-drawer" id="ai-drawer" aria-label={{#quote}}{{#str}} aidrawerlabel, aiplacement_courseassist {{/str}}{{/quote}} tabindex="-1" role="region">
<div class="ai-drawer-header">
<button id="ai-drawer-close" class="btn ai-drawer-button" type="button" data-action="course-summarise">
{{#pix}} e/cancel, core {{/pix}}
+1
View File
@@ -938,6 +938,7 @@ class auth_plugin_ldap extends auth_plugin_base {
} catch (Exception $e) {
print_string('invaliduserexception', 'auth_ldap', print_r($user, true) . $e->getMessage());
$errors++;
$transaction->allow_commit();
continue;
}
echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n";
+1 -1
View File
@@ -87,7 +87,7 @@ Please log in as before, using the link on the login page.
$string['emailpasswordchangeinfosubject'] = '{$a}: Change password information';
$string['info'] = 'External account';
$string['issuer'] = 'OAuth 2 service';
$string['issuernologin'] = 'This issuer can not be used to login';
$string['issuernologin'] = 'This issuer can not be used to log in.';
$string['key'] = 'Key';
$string['linkedlogins'] = 'Linked logins';
$string['linkedloginshelp'] = 'Help with linked logins';
+1 -1
View File
@@ -31,7 +31,7 @@ $string['auth_shib_contact_administrator'] = 'In case you are not associated wit
$string['auth_shibbolethdescription'] = 'Using this method users are created and authenticated using Shibboleth. For set-up details, see the <a href="{$a}">Shibboleth README</a>.';
$string['auth_shibboleth_errormsg'] = 'Please select the organization you are member of!';
$string['auth_shibboleth_login'] = 'Shibboleth login';
$string['auth_shibboleth_login_long'] = 'Login to Moodle via Shibboleth';
$string['auth_shibboleth_login_long'] = 'Log in via Shibboleth';
$string['auth_shibboleth_manual_login'] = 'Manual login';
$string['auth_shibboleth_select_member'] = 'I\'m a member of ...';
$string['auth_shibboleth_select_organization'] = 'For authentication via Shibboleth, please select your organisation from the drop-down menu:';
+9 -1
View File
@@ -73,7 +73,15 @@ class auth_plugin_webservice extends auth_plugin_base {
function user_login_webservice($username, $password) {
global $CFG, $DB;
// special web service login
if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
$user = $DB->get_record('user', [
'username' => $username,
'auth' => 'webservice',
'deleted' => '0',
'suspended' => '0',
'mnethostid' => $CFG->mnet_localhost_id,
]);
if ($user) {
return validate_internal_user_password($user, $password);
}
return false;
+1 -1
View File
@@ -31,6 +31,6 @@ class core_availability_renderer extends plugin_renderer_base {
*/
#[\core\attribute\deprecated(availability_info::class, since: '4.0', mdl: 'MDL-71691', final: true)]
public function render_core_availability_multiple_messages(): void {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
}
}
@@ -764,7 +764,7 @@ M.core_availability.List.prototype.clickAdd = function() {
dialogRef.dialog = new M.core.dialogue(config);
dialogRef.dialog.show();
cancel.on('click', function() {
dialogRef.dialog.destroy();
dialogRef.dialog.hide();
// Focus the button they clicked originally.
this.inner.one('> .availability-button').one('button').focus();
}, this);
@@ -801,7 +801,7 @@ M.core_availability.List.prototype.getAddHandler = function(type, dialogRef) {
M.core_availability.form.rootList.renumber();
this.updateHtml();
// Hide dialog.
dialogRef.dialog.destroy();
dialogRef.dialog.hide();
newItem.focusAfterAdd();
};
};
File diff suppressed because one or more lines are too long
@@ -764,7 +764,7 @@ M.core_availability.List.prototype.clickAdd = function() {
dialogRef.dialog = new M.core.dialogue(config);
dialogRef.dialog.show();
cancel.on('click', function() {
dialogRef.dialog.destroy();
dialogRef.dialog.hide();
// Focus the button they clicked originally.
this.inner.one('> .availability-button').one('button').focus();
}, this);
@@ -801,7 +801,7 @@ M.core_availability.List.prototype.getAddHandler = function(type, dialogRef) {
M.core_availability.form.rootList.renumber();
this.updateHtml();
// Hide dialog.
dialogRef.dialog.destroy();
dialogRef.dialog.hide();
newItem.focusAfterAdd();
};
};
+2 -2
View File
@@ -762,7 +762,7 @@ M.core_availability.List.prototype.clickAdd = function() {
dialogRef.dialog = new M.core.dialogue(config);
dialogRef.dialog.show();
cancel.on('click', function() {
dialogRef.dialog.destroy();
dialogRef.dialog.hide();
// Focus the button they clicked originally.
this.inner.one('> .availability-button').one('button').focus();
}, this);
@@ -799,7 +799,7 @@ M.core_availability.List.prototype.getAddHandler = function(type, dialogRef) {
M.core_availability.form.rootList.renumber();
this.updateHtml();
// Hide dialog.
dialogRef.dialog.destroy();
dialogRef.dialog.hide();
newItem.focusAfterAdd();
};
};
+2 -2
View File
@@ -111,7 +111,7 @@ abstract class base_controller extends backup implements loggable {
*/
#[\core\attribute\deprecated(since: '4.1', mdl: 'MDL-74548', final: true)]
public function set_copy(): void {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
}
/**
@@ -119,6 +119,6 @@ abstract class base_controller extends backup implements loggable {
*/
#[\core\attribute\deprecated('restore_controller::get_copy()', since: '4.1', mdl: 'MDL-74548', final: true)]
public function get_copy() {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
}
}
+8
View File
@@ -1,5 +1,13 @@
# core_badges (subsystem) Upgrade notes
## 4.5.7
### Added
- A number of new static methods have been added to `core_badges\backpack_api` to support the new Canvas Credentials backpack provider. These methods allow you to retrieve lists of providers and regions, check if Canvas Credentials fields should be displayed, and get a region URL or API URL based on a given region ID. The new methods include `get_providers`, `get_regions`, `display_canvas_credentials_fields`, `get_region_url`, `get_region_api_url`, `get_regionid_from_url`, and `is_canvas_credentials_region`.
For more information see [MDL-86174](https://tracker.moodle.org/browse/MDL-86174)
## 4.5
### Added
+113 -59
View File
@@ -60,6 +60,7 @@ if (!empty($issuedbadge->recipient->id)) {
$assertionid = $assertion->get_assertion_hash();
$assertionentityid = $assertiondata['id'];
$badgeadded = false;
$issuerexists = false;
if (badges_open_badges_backpack_api() == OPEN_BADGES_V2) {
$sitebackpack = badges_get_site_primary_backpack();
$api = new \core_badges\backpack_api($sitebackpack);
@@ -71,62 +72,75 @@ if (!empty($issuedbadge->recipient->id)) {
$issuer = $assertion->get_issuer();
if (!($issuerentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email']))) {
$response = $api->put_issuer($issuer);
if (!$response) {
throw new moodle_exception('invalidrequest', 'error');
if ($response) {
$issuerexists = true;
$issuerentityid = $response->id;
badges_external_create_mapping(
$sitebackpack->id,
OPEN_BADGES_V2_TYPE_ISSUER,
$issuer['email'],
$issuerentityid,
);
}
$issuerentityid = $response->id;
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'],
$issuerentityid);
}
// Create badge.
$badge = $assertion->get_badge_class(false);
$badgeid = $assertion->get_badge_id();
if (!($badgeentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid))) {
$response = $api->put_badgeclass($issuerentityid, $badge);
if (!$response) {
throw new moodle_exception('invalidrequest', 'error');
if ($issuerexists) {
// Create badge.
$badge = $assertion->get_badge_class(false);
$badgeid = $assertion->get_badge_id();
if (!($badgeentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid))) {
$response = $api->put_badgeclass($issuerentityid, $badge);
if ($response) {
$badgeentityid = $response->id;
badges_external_create_mapping(
$sitebackpack->id,
OPEN_BADGES_V2_TYPE_BADGE,
$badgeid,
$badgeentityid,
);
}
}
$badgeentityid = $response->id;
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid,
$badgeentityid);
}
// Create assertion (Award the badge!).
$assertionentityid = badges_external_get_mapping(
$sitebackpack->id,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionid
);
if ($assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) {
$assertionentityid = badges_generate_badgr_open_url(
$sitebackpack,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionentityid
);
}
// Create an assertion for the recipient in the issuer's account.
if (!$assertionentityid) {
$response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata);
if (!$response) {
throw new moodle_exception('invalidrequest', 'error');
}
$assertionentityid = badges_generate_badgr_open_url($sitebackpack, OPEN_BADGES_V2_TYPE_ASSERTION, $response->id);
$badgeadded = true;
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
$response->id);
} else {
// An assertion already exists. Make sure it's up to date.
$internalid = badges_external_get_mapping(
// Create assertion (Award the badge!).
$assertionentityid = badges_external_get_mapping(
$sitebackpack->id,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionid,
'externalid'
$assertionid
);
$response = $api->update_assertion($internalid, $assertiondata);
if (!$response) {
throw new moodle_exception('invalidrequest', 'error');
if ($assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) {
$assertionentityid = badges_generate_badgr_open_url(
$sitebackpack,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionentityid
);
}
// Create an assertion for the recipient in the issuer's account.
if (!$assertionentityid) {
$response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata);
if ($response) {
$assertionentityid = badges_generate_badgr_open_url(
$sitebackpack,
OPEN_BADGES_V2_TYPE_ASSERTION,
$response->id,
);
$badgeadded = true;
badges_external_create_mapping(
$sitebackpack->id,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionid,
$response->id,
);
}
} else {
// An assertion already exists. Make sure it's up to date.
$internalid = badges_external_get_mapping(
$sitebackpack->id,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionid,
'externalid'
);
$response = $api->update_assertion($internalid, $assertiondata);
}
}
}
@@ -136,21 +150,61 @@ if (!empty($issuedbadge->recipient->id)) {
// - If a user and site backpack have the same provider we can skip this as Badgr automatically maps recipients
// based on email address.
// - This is only needed when the backpacks are from different regions.
if ($assertionentityid && !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid)) {
if (
$assertionentityid
&& (!$issuerexists || !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid))
) {
$userapi = new \core_badges\backpack_api($userbackpack, $backpack);
$userapi->authenticate();
$response = $userapi->import_badge_assertion($assertionentityid);
if (!$response) {
throw new moodle_exception('invalidrequest', 'error');
if ($response) {
$assertionentityid = $response->id;
$badgeadded = true;
badges_external_create_mapping(
$userbackpack->id,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionid,
$assertionentityid,
);
}
$assertionentityid = $response->id;
$badgeadded = true;
badges_external_create_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
$assertionentityid);
}
$response = $badgeadded ? ['success' => 'addedtobackpack'] : ['warning' => 'existsinbackpack'];
redirect(new moodle_url('/badges/mybadges.php', $response));
if ($badgeadded) {
$message = get_string('addedtobackpack', 'badges');
$messagetype = \core\output\notification::NOTIFY_SUCCESS;
} else {
if (isset($userapi) && !empty($userapi->get_errors())) {
// If the api used to import the badge to the backpack has errors, show them to inform the user.
if (array_filter($userapi->get_errors(), fn($element) => str_contains($element, "DUPLICATE_BADGE"))) {
// Duplicated badges are displayed as a warning.
$message = get_string('existsinbackpack', 'badges');
$messagetype = \core\output\notification::NOTIFY_WARNING;
} else {
// If the userapi has any other errors, we will use those to inform the user.
$message = get_string(
'error:cannotsendtobackpack',
'badges',
implode($userapi->get_errors()),
);
$messagetype = \core\output\notification::NOTIFY_ERROR;
}
} else if (isset($api) && !empty($api->get_errors())) {
// If the api used to create/update the issuer has errors, show them to inform the user.
$errors = $api->get_errors() ?? [get_string('invalidrequest', 'error')];
$message = get_string(
'error:cannotsendtobackpack',
'badges',
implode($errors),
);
$messagetype = \core\output\notification::NOTIFY_ERROR;
}
}
redirect(
url: new \core\url('/badges/mybadges.php'),
message: $message,
messagetype: $messagetype,
);
} else {
redirect(new moodle_url('/badges/mybadges.php'));
redirect(new \core\url('/badges/mybadges.php'));
}
+144
View File
@@ -42,6 +42,17 @@ define('BADGE_EXPIRES_TOKEN', 'expires');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backpack_api {
/** @var int Canvas Credentials backpack provider */
public const PROVIDER_CANVAS_CREDENTIALS = 0;
/** @var int Other backpack provider */
public const PROVIDER_OTHER = 1;
/** @var int Empty provider */
public const PROVIDER_EMPTY = -1;
/** @var int Empty region */
public const REGION_EMPTY = -1;
/** @var string The email address of the issuer or the backpack owner. */
private $email;
@@ -512,6 +523,20 @@ class backpack_api {
return backpack_api_mapping::get_authentication_error();
}
/**
* List all errors occurred during the requests to the backpack.
*
* @return array The list of errors.
*/
public function get_errors(): array {
$errors = [];
foreach ($this->mappings as $mapping) {
$errors = array_merge($errors, $mapping->get_errors());
}
return $errors;
}
/**
* Get the list of badges in a collection.
*
@@ -565,4 +590,123 @@ class backpack_api {
return [];
}
/**
* Get list of backpack providers for OBv2.0.
*
* @return string[] Array with the OBv2.0 backpack providers.
*/
public static function get_providers(): array {
$allproviders = [
self::PROVIDER_CANVAS_CREDENTIALS => 'canvascredentialsprovider',
self::PROVIDER_OTHER => 'otherprovider',
];
foreach ($allproviders as $key => $value) {
if (get_string_manager()->string_exists($value, 'badges')) {
$providers[$key] = get_string($value, 'badges');
} else {
// If the string does not exist, use the key as a fallback.
$providers[$key] = $value;
}
}
return $providers;
}
/**
* Get list of regions for backpack providers.
*
* @return array Regions with the following information: name, url and apiurl.
*/
public static function get_regions() {
global $CFG;
$regions = [];
if (empty(trim($CFG->badges_canvasregions))) {
return $regions;
}
$entries = explode("\n", $CFG->badges_canvasregions);
foreach ($entries as $entry) {
if (empty(trim($entry)) || substr_count($entry, '|') != 2) {
continue;
}
$entry = trim($entry);
$parts = explode('|', $entry);
$regions[] = [
'name' => $parts[0],
'url' => rtrim($parts[1], '/'),
'apiurl' => rtrim($parts[2], '/'),
];
}
return $regions;
}
/**
* Whether the Canvas Credentials fields should be displayed or not in the backpack form.
*
* @return bool True if the fields should be displayed; false otherwise.
*/
public static function display_canvas_credentials_fields(): bool {
return !empty(self::get_providers()) && !empty(self::get_regions());
}
/**
* Get backpack URL for a given regionid.
*
* @param int $regionid The region identifier.
* @return string|null The backpack URL.
*/
public static function get_region_url(int $regionid): ?string {
$regions = self::get_regions();
if (!array_key_exists($regionid, $regions)) {
return null;
}
return $regions[$regionid]['url'];
}
/**
* Get backpack API URL for a given regionid.
*
* @param int $regionid The region identifier.
* @return string|null The backpack API URL.
*/
public static function get_region_api_url(int $regionid): ?string {
$regions = self::get_regions();
if (!array_key_exists($regionid, $regions)) {
return null;
}
return $regions[$regionid]['apiurl'];
}
/**
* Get region identifier from a given backpack URL.
* When the URL is not found, the last region index is returned.
*
* @param string $url The backpack URL.
* @return int The region identifier associated to the given backpack URL or the last region index if not found.
*/
public static function get_regionid_from_url(string $url): int {
$regions = self::get_regions();
if (empty($regions)) {
return self::REGION_EMPTY;
}
// Normalize the URL by removing the trailing slash.
$normalizedurl = rtrim($url, '/');
$regionurl = array_search($normalizedurl, array_column($regions, 'url'));
return $regionurl !== false ? (int)$regionurl : count($regions) - 1;
}
/**
* Check whether the given URL is a Canvas Credentials one.
*
* @param string $url The backpack URL.
* @return bool True is the given URL is a Canvas Credentials region; false otherwise.
*/
public static function is_canvas_credentials_region(string $url): bool {
$regions = self::get_regions();
return in_array($url, array_column($regions, 'url'));
}
}
+40
View File
@@ -81,6 +81,9 @@ class backpack_api_mapping {
/** @var int OpenBadges version. */
protected $backpackapiversion;
/** @var array Errors encountered during the request. */
protected $errors = [];
/**
* Create a mapping.
*
@@ -146,6 +149,26 @@ class backpack_api_mapping {
return self::$authenticationerror;
}
/**
* Get the errors encountered during the request.
*
* @return array The list of errors.
*/
public function get_errors() {
return $this->errors;
}
/**
* Add an error to the list of errors.
*
* @param string $error The error message.
* @return self This instance for method chaining.
*/
public function add_error(string $error): self {
$this->errors[] = $error;
return $this;
}
/**
* Does the action match this mapping?
*
@@ -342,6 +365,23 @@ class backpack_api_mapping {
$response = $curl->put($url, $post, $options);
}
$response = json_decode($response);
if ($response === null) {
$this->add_error(get_string('invalidrequest', 'error'));
return null;
}
if (isset($response->status) && isset($response->status->success) && $response->status->success != true) {
// If the response wasn't successful, store the errors and return null.
if (isset($response->validationErrors)) {
$error = implode(', ', $response->validationErrors);
} else if (isset($response->status->description)) {
$error = $response->status->description;
} else {
$error = get_string('invalidrequest', 'error');
}
$this->add_error($error);
return null;
}
if (isset($response->result)) {
$response = $response->result;
}
+312 -54
View File
@@ -16,6 +16,8 @@
namespace core_badges\form;
use core_badges\backpack_api;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
@@ -37,11 +39,7 @@ class external_backpack extends \moodleform {
global $CFG;
$mform = $this->_form;
$backpack = false;
if (isset($this->_customdata['externalbackpack'])) {
$backpack = $this->_customdata['externalbackpack'];
}
$backpack = $this->_customdata['externalbackpack'] ?? null;
$mform->addElement('hidden', 'action', 'edit');
$mform->setType('action', PARAM_ALPHA);
@@ -52,14 +50,22 @@ class external_backpack extends \moodleform {
$mform->setDefault('apiversion', OPEN_BADGES_V2P1);
$mform->addRule('apiversion', null, 'required', null, 'client');
$this->add_provider_fields();
$mform->addElement('text', 'backpackweburl', get_string('backpackweburl', 'core_badges'));
$mform->setType('backpackweburl', PARAM_URL);
$mform->addRule('backpackweburl', null, 'required', null, 'client');
$mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->hideIf('backpackweburl', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->addElement('text', 'backpackapiurl', get_string('backpackapiurl', 'core_badges'));
$mform->setType('backpackapiurl', PARAM_URL);
$mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->hideIf('backpackapiurl', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->addElement('text', 'backpackweburlv2p1', get_string('backpackweburl', 'core_badges'));
$mform->setType('backpackweburlv2p1', PARAM_URL);
$mform->addRule('backpackweburlv2p1', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->hideIf('backpackweburlv2p1', 'apiversion', 'ne', (string) OPEN_BADGES_V2P1);
$mform->addElement('hidden', 'id', ($backpack->id ?? null));
$mform->setType('id', PARAM_INT);
@@ -70,31 +76,116 @@ class external_backpack extends \moodleform {
$mform->addElement('hidden', 'backpackuid', 0);
$mform->setType('backpackuid', PARAM_INT);
$mform->addElement('advcheckbox', 'includeauthdetails', null, get_string('includeauthdetails', 'core_badges'));
if (!empty($backpack->backpackemail) || !empty($backpack->password)) {
$mform->setDefault('includeauthdetails', 1);
// Add rules for backpack URL fields.
if (backpack_api::display_canvas_credentials_fields()) {
$mform->hideIf('backpackweburl', 'provider', 'ne', backpack_api::PROVIDER_OTHER);
$mform->hideIf('backpackapiurl', 'provider', 'ne', backpack_api::PROVIDER_OTHER);
}
$issuercontact = $CFG->badges_defaultissuercontact;
$this->add_auth_fields($issuercontact);
$issueremail = $CFG->badges_defaultissuercontact;
// Connect to a Canvas Credentials provider.
$this->add_connect_issuer_canvas_fields($issueremail);
// Connect to another provider.
$this->add_connect_issuer_fields($backpack, $issueremail);
if ($backpack) {
$this->set_data($backpack);
}
$mform->hideIf('includeauthdetails', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
$mform->hideIf('backpackemail', 'includeauthdetails');
$mform->hideIf('backpackemail', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
$mform->hideIf('password', 'includeauthdetails');
$mform->hideIf('password', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
$mform->hideIf('backpackapiurl', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
// Disable short forms.
$mform->setDisableShortforms();
$this->add_action_buttons();
}
#[\Override]
public function definition_after_data(): void {
parent::definition_after_data();
$mform = $this->_form;
if ($this->is_submitted()) {
if (!$mform->elementExists('apiversion')) {
return;
}
$apiversion = $mform->getElement('apiversion')->getValue();
$apiversion = $apiversion ? array_pop($apiversion) : null;
$provider = $mform->elementExists('provider') ? $mform->getElement('provider')->getValue() : null;
$provider = $provider ? array_pop($provider) : null;
$region = $mform->elementExists('region') ? $mform->getElement('region')->getValue() : null;
$region = $region ? array_pop($region) : null;
if ($apiversion == OPEN_BADGES_V2) {
if (
$provider == backpack_api::PROVIDER_CANVAS_CREDENTIALS
&& isset($region) && $region != backpack_api::REGION_EMPTY
) {
$mform->getElement('backpackweburl')->setValue(
backpack_api::get_region_url($region),
);
$mform->getElement('backpackapiurl')->setValue(
backpack_api::get_region_api_url($region),
);
if ($mform->getElement('includeauthdetailscanvas')->getValue()) {
$mform->getElement('backpackemail')->setValue(
$mform->getElement('backpackemailcanvas')->getValue(),
);
$mform->getElement('password')->setValue(
$mform->getElement('backpackpasswordcanvas')->getValue(),
);
}
} else if (is_null($provider) || $provider == backpack_api::PROVIDER_OTHER) {
if ($mform->getElement('includeauthdetails')->getValue() == 0) {
// Clear backpack issuer fields when authentication details checkbox is not checked.
$mform->getElement('backpackemail')->setValue('');
$mform->getElement('password')->setValue('');
}
}
} else if ($apiversion == OPEN_BADGES_V2P1) {
if (!empty($mform->getElement('backpackweburlv2p1')->getValue())) {
$mform->getElement('backpackweburl')->setValue(
$mform->getElement('backpackweburlv2p1')->getValue(),
);
}
// Clear backpack issuer fields when OBv2.1 is selected.
$mform->getElement('includeauthdetails')->setValue(0);
$mform->getElement('backpackemail')->setValue('');
$mform->getElement('password')->setValue('');
}
}
}
#[\Override]
public function set_data($backpack) {
if ($backpack->apiversion == OPEN_BADGES_V2) {
if (backpack_api::is_canvas_credentials_region($backpack->backpackweburl)) {
// Calculate provider and region fields based on backpack URLs.
$backpack->provider = backpack_api::PROVIDER_CANVAS_CREDENTIALS;
$backpack->region = backpack_api::get_regionid_from_url($backpack->backpackweburl);
$backpack->backpackweburl = '';
$backpack->backpackapiurl = '';
if (isset($backpack->backpackemail) && !empty($backpack->backpackemail)) {
// Update Canvas Credentials fields.
$backpack->includeauthdetailscanvas = 1;
$backpack->backpackemailcanvas = $backpack->backpackemail;
$backpack->backpackpasswordcanvas = $backpack->password;
// Clear email and password fields for another providers.
$backpack->includeauthdetails = 0;
$backpack->backpackemail = '';
$backpack->password = '';
}
} else {
$backpack->provider = backpack_api::PROVIDER_OTHER;
}
} else if ($backpack->apiversion == OPEN_BADGES_V2P1) {
$backpack->backpackweburlv2p1 = $backpack->backpackweburl;
$backpack->backpackweburl = '';
$backpack->backpackapiurl = '';
}
parent::set_data($backpack);
}
/**
* Validate the data from the form.
*
@@ -106,42 +197,212 @@ class external_backpack extends \moodleform {
$errors = parent::validation($data, $files);
// Ensure backpackapiurl and backpackweburl are valid URLs.
$isobv21 = isset($data['apiversion']) && $data['apiversion'] == OPEN_BADGES_V2P1;
if (!$isobv21) {
if (empty($data['backpackapiurl'])) {
$errors['backpackapiurl'] = get_string('err_required', 'form');
} else if (!preg_match('@^https?://.+@', $data['backpackapiurl'])) {
$errors['backpackapiurl'] = get_string('invalidurl', 'badges');
}
$isobv20 = isset($data['apiversion']) && $data['apiversion'] == OPEN_BADGES_V2;
$isobv2p1 = isset($data['apiversion']) && $data['apiversion'] == OPEN_BADGES_V2P1;
if ($isobv20) {
$errors = array_merge($errors, $this->validate_obv20($data));
} else if ($isobv2p1) {
$errors = array_merge($errors, $this->validate_obv2p1($data));
}
if (!empty($data['backpackweburl']) && !preg_match('@^https?://.+@', $data['backpackweburl'])) {
$errors['backpackweburl'] = get_string('invalidurl', 'badges');
// Check email and password are not empty when including auth details.
if (!empty($data['includeauthdetails']) && empty($data['backpackemail'])) {
$errors['backpackemail'] = get_string('err_required', 'form');
}
if (!empty($data['includeauthdetails']) && empty($data['password'])) {
$errors['password'] = get_string('err_required', 'form');
}
return $errors;
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
* Validate the data for Open Badges v2.0.
*
* @return object|void
* @param array $data Form data.
* @return string[] An array of error messages.
*/
public function get_data() {
$data = parent::get_data();
if ($data ) {
if ((isset($data->includeauthdetails) && !$data->includeauthdetails)
|| (isset($data->apiversion) && $data->apiversion == 2.1)) {
$data->backpackemail = "";
$data->password = "";
}
private function validate_obv20(array $data): array {
$errors = [];
if ((isset($data->apiversion) && $data->apiversion == 1)) {
$data->password = "";
$displaycanvasfields = backpack_api::display_canvas_credentials_fields();
if (
$displaycanvasfields
&& (!array_key_exists('provider', $data) || $data['provider'] == backpack_api::PROVIDER_EMPTY)
) {
// Check provider is set.
$errors['provider'] = get_string('err_required', 'form');
} else if (
$displaycanvasfields
&& ($data['provider'] == backpack_api::PROVIDER_CANVAS_CREDENTIALS)
) {
// Check region is set.
if (!array_key_exists('region', $data) || $data['region'] == backpack_api::REGION_EMPTY) {
$errors['region'] = get_string('err_required', 'form');
}
} else {
if (empty($data['backpackweburl'])) {
$errors['backpackweburl'] = get_string('err_required', 'form');
} else if (!preg_match('@^https?://.+@', $data['backpackweburl'])) {
$errors['backpackweburl'] = get_string('invalidurl', 'badges');
}
if (empty($data['backpackapiurl'])) {
$errors['backpackapiurl'] = get_string('err_required', 'form');
} else if (!preg_match('@^https?://.+@', $data['backpackapiurl'])) {
$errors['backpackapiurl'] = get_string('invalidurl', 'badges');
}
}
return $data;
if ($displaycanvasfields) {
if (!empty($data['includeauthdetailscanvas']) && empty($data['backpackemailcanvas'])) {
$errors['backpackemailcanvas'] = get_string('err_required', 'form');
}
if (!empty($data['includeauthdetailscanvas']) && empty($data['backpackpasswordcanvas'])) {
$errors['backpackpasswordcanvas'] = get_string('err_required', 'form');
}
}
return $errors;
}
/**
* Validate the data for Open Badges v2.1.
*
* @param array $data Form data.
* @return string[] An array of error messages.
*/
private function validate_obv2p1(array $data): array {
$errors = [];
if (empty($data['backpackweburlv2p1'])) {
$errors['backpackweburlv2p1'] = get_string('err_required', 'form');
} else if (!preg_match('@^https?://.+@', $data['backpackweburlv2p1'])) {
$errors['backpackweburlv2p1'] = get_string('invalidurl', 'badges');
}
return $errors;
}
/**
* Add provider fields to the form.
*/
protected function add_provider_fields(): void {
$mform = $this->_form;
if (!backpack_api::display_canvas_credentials_fields()) {
// If canvas credentials fields are not to be displayed, return early.
return;
}
// Add an empty option at the start.
$providers = backpack_api::get_providers();
$providers = [backpack_api::PROVIDER_EMPTY => ''] + $providers;
$mform->addElement('select', 'provider', get_string('provider', 'core_badges'), $providers);
$mform->setType('provider', PARAM_RAW);
$mform->hideIf('provider', 'apiversion', 'ne', OPEN_BADGES_V2);
// Add an empty option at the start.
$regions = backpack_api::get_regions();
$regions = [backpack_api::REGION_EMPTY => ''] + array_column($regions, 'name');
$mform->addElement('select', 'region', get_string('region', 'core_badges'), $regions);
$mform->setType('region', PARAM_RAW);
$mform->hideIf('region', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
$mform->hideIf('region', 'apiversion', 'ne', OPEN_BADGES_V2);
}
/**
* Add Canvas backpack specific issuer auth details.
*
* @param string|null $email The email addressed provided or null if it's new.
*/
protected function add_connect_issuer_canvas_fields(?string $email): void {
$mform = $this->_form;
if (!backpack_api::display_canvas_credentials_fields()) {
// If canvas credentials fields are not to be displayed, return early.
return;
}
$providers = backpack_api::get_providers();
$regions = backpack_api::get_regions();
if (empty($providers) || empty($regions)) {
// If no providers or regions are available, return early.
return;
}
// Checkbox and information to enable/disable issuer account.
$mform->addElement('static', '', null, '');
$mform->addElement(
'advcheckbox',
'includeauthdetailscanvas',
null,
'<strong>' . get_string('includeauthdetailscanvas', 'core_badges') . '</strong> '
. get_string('includeauthdetailscanvas_subtitle', 'core_badges'),
);
if (!empty($backpack->backpackemail) || !empty($backpack->password)) {
$mform->setDefault('includeauthdetailscanvas', 1);
}
$mform->addHelpButton('includeauthdetailscanvas', 'includeauthdetailscanvas', 'core_badges');
$mform->hideIf('includeauthdetailscanvas', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->hideIf('includeauthdetailscanvas', 'region', 'eq', backpack_api::REGION_EMPTY);
$mform->hideIf('includeauthdetailscanvas', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
$mform->addElement(
'static',
'includeauthdetailscanvasdesc',
null,
get_string('includeauthdetailscanvas_desc', 'core_badges'),
);
$mform->hideIf('includeauthdetailscanvasdesc', 'includeauthdetailscanvas');
$mform->hideIf('includeauthdetailscanvasdesc', 'region', 'eq', backpack_api::REGION_EMPTY);
$mform->hideIf('includeauthdetailscanvasdesc', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
$mform->hideIf('includeauthdetailscanvasdesc', 'apiversion', 'ne', OPEN_BADGES_V2);
// Email.
$mform->addElement('text', 'backpackemailcanvas', get_string('issueremail', 'core_badges'));
$mform->setType('backpackemailcanvas', PARAM_EMAIL);
$mform->setDefault('backpackemailcanvas', $email);
$mform->hideIf('backpackemailcanvas', 'includeauthdetailscanvas');
$mform->hideIf('backpackemailcanvas', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->hideIf('backpackemailcanvas', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
// Password.
$mform->addElement('passwordunmask', 'backpackpasswordcanvas', get_string('password'));
$mform->setType('backpackpasswordcanvas', PARAM_RAW);
$mform->hideIf('backpackpasswordcanvas', 'includeauthdetailscanvas');
$mform->hideIf('backpackpasswordcanvas', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->hideIf('backpackpasswordcanvas', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
}
/**
* Add generic backpack issuer auth details.
*
* @param \stdClass|null $backpack The backpack instance.
* @param string|null $email The issuer email or null if it's new.
*/
protected function add_connect_issuer_fields(?\stdClass $backpack, ?string $email): void {
$mform = $this->_form;
// Checkbox and information to enable/disable issuer account.
$mform->addElement(
'advcheckbox',
'includeauthdetails',
null,
'<strong>' . get_string('includeauthdetails', 'core_badges') . '</strong>',
);
if ($backpack && (!empty($backpack->backpackemail) || !empty($backpack->password))) {
$mform->setDefault('includeauthdetails', 1);
}
$mform->addHelpButton('includeauthdetails', 'includeauthdetails', 'core_badges');
$mform->hideIf('includeauthdetails', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
$mform->hideIf('includeauthdetails', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->addElement('static', 'includeauthdetailsdesc', null, get_string('includeauthdetails_desc', 'core_badges'));
$mform->hideIf('includeauthdetailsdesc', 'includeauthdetails');
$mform->hideIf('includeauthdetailsdesc', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
// Email and password fields.
$this->add_auth_fields($email);
}
/**
@@ -153,25 +414,22 @@ class external_backpack extends \moodleform {
*/
protected function add_auth_fields(?string $email, bool $includepassword = true) {
$mform = $this->_form;
$emailstring = get_string('email');
$passwordstring = get_string('password');
$showpasswordhelp = false;
if (!isset($this->_customdata['userbackpack'])) {
$emailstring = get_string('defaultissuercontact', 'core_badges');
$passwordstring = get_string('defaultissuerpassword', 'core_badges');
$showpasswordhelp = true;
}
$mform->addElement('text', 'backpackemail', $emailstring);
// Email.
$mform->addElement('text', 'backpackemail', get_string('issueremail', 'core_badges'));
$mform->setType('backpackemail', PARAM_EMAIL);
$mform->setDefault('backpackemail', $email);
$mform->hideIf('backpackemail', 'includeauthdetails');
$mform->hideIf('backpackemail', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->hideIf('backpackemail', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
// Password.
if ($includepassword) {
$mform->addElement('passwordunmask', 'password', $passwordstring);
$mform->addElement('passwordunmask', 'password', get_string('password'));
$mform->setType('password', PARAM_RAW);
if ($showpasswordhelp) {
$mform->addHelpButton('password', 'defaultissuerpassword', 'badges');
}
}
$mform->hideIf('password', 'includeauthdetails');
$mform->hideIf('password', 'apiversion', 'ne', OPEN_BADGES_V2);
$mform->hideIf('password', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS);
}
}
@@ -69,7 +69,9 @@ class manage_badge_action_bar extends base_action_bar {
$elements['button'] = new single_button(new moodle_url('/badges/index.php', $params), get_string('back'), 'get');
$badgenav = $this->generate_badge_navigation();
if ($badgenav) {
$elements['urlselect'] = new url_select($badgenav, $this->page->url->out(false), null);
$badgenavselect = new url_select($badgenav, $this->page->url->out(false), null);
$badgenavselect->set_label(get_string('badgesnavigation', 'badges'), ['class' => 'sr-only']);
$elements['urlselect'] = $badgenavselect;
}
foreach ($elements as $key => $element) {
$elements[$key] = $element->export_for_template($output);
@@ -130,6 +130,38 @@ class badge extends base {
return html_writer::link($url, $row->name);
});
// Name with image and link.
$columns[] = (new column(
'namewithimagelink',
new lang_string('namewithimagelink', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join("LEFT JOIN {context} {$contextalias}
ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . "
AND {$contextalias}.instanceid = {$badgealias}.courseid")
->add_fields(
"{$badgealias}.name, {$badgealias}.id, {$badgealias}.type, {$badgealias}.courseid"
)
->add_field($DB->sql_cast_to_char("{$badgealias}.imagecaption"), 'imagecaption')
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->set_is_sortable(true)
->add_callback(static function ($value, stdClass $badge): string {
if ($badge->id === null) {
return '';
}
if ($badge->type == BADGE_TYPE_SITE) {
$context = context_system::instance();
} else {
context_helper::preload_from_record($badge);
$context = context_course::instance($badge->courseid);
}
$badgeimage = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', 'f2');
$url = new moodle_url('/badges/overview.php', ['id' => $badge->id]);
return html_writer::img($badgeimage, $badge->imagecaption) . ' ' . html_writer::link($url, $badge->name);
});
// Description (note, this column contains plaintext so requires no post-processing).
$descriptionfieldsql = "{$badgealias}.description";
if ($DB->get_dbfamily() === 'oracle') {
@@ -94,8 +94,6 @@ class badges extends system_report {
$this->add_filters();
$this->add_actions();
// Set initial sorting by name.
$this->set_initial_sort_column('badge:namewithlink', SORT_ASC);
$this->set_default_no_results_notice(new lang_string('nomatchingbadges', 'core_badges'));
// Set if report can be downloaded.
@@ -129,8 +127,7 @@ class badges extends system_report {
*/
public function add_columns(string $badgeissuedalias): void {
$columns = [
'badge:image',
'badge:namewithlink',
'badge:namewithimagelink',
'badge:status',
'badge:criteria',
];
@@ -138,15 +135,12 @@ class badges extends system_report {
$canviewdraftbadges = $this->can_view_draft_badges();
if (!$canviewdraftbadges) {
// Remove status and recipients column.
unset($columns[2]);
unset($columns[1]);
}
$this->add_columns_from_entities($columns);
// Remove title from image column.
$this->get_column('badge:image')->set_title(null);
// Change title from namewithlink column.
$this->get_column('badge:namewithlink')->set_title(new lang_string('name'));
// Change title of the `namewithimagelink` column to 'Name'.
$this->get_column('badge:namewithimagelink')->set_title(new lang_string('name'));
// Recipients column.
if ($canviewdraftbadges) {
@@ -193,7 +187,7 @@ class badges extends system_report {
return $OUTPUT->action_icon($badgeurl, $icon, null, null, true);
});
$this->set_initial_sort_column('badge:namewithlink', SORT_ASC);
$this->set_initial_sort_column('badge:namewithimagelink', SORT_ASC);
}
/**
@@ -51,7 +51,7 @@ class course_badges extends system_report {
*/
#[\core\attribute\deprecated('course_badges::initialise', since: '4.5', mdl: 'MDL-82503')]
protected function initialise(): void {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
global $USER;
// Our main entity, it contains all of the column definitions that we need.
$badgeentity = new badge();
@@ -103,7 +103,7 @@ class course_badges extends system_report {
*/
#[\core\attribute\deprecated('course_badges::can_view', since: '4.5', mdl: 'MDL-82503')]
protected function can_view(): bool {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
return has_capability('moodle/badges:viewbadges', $this->get_context());
}
@@ -118,7 +118,7 @@ class course_badges extends system_report {
*/
#[\core\attribute\deprecated('course_badges::add_columns', since: '4.5', mdl: 'MDL-82503')]
protected function add_columns(): void {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
$badgeissuedalias = $this->get_entity('badge_issued')->get_table_alias('badge_issued');
$this->add_columns_from_entities([
@@ -156,7 +156,7 @@ class course_badges extends system_report {
*/
#[\core\attribute\deprecated('course_badges::add_filters', since: '4.5', mdl: 'MDL-82503')]
protected function add_filters(): void {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
$this->add_filters_from_entities([
'badge:name',
'badge_issued:issued',
+40 -13
View File
@@ -151,12 +151,17 @@ class core_badges_renderer extends plugin_renderer_base {
'value' => $this->output->larrow() . ' ' . get_string('award', 'badges'),
'class' => 'actionbutton btn btn-secondary')
);
$actioncell->text .= html_writer::empty_tag('input', array(
if (has_capability('moodle/badges:revokebadge', $this->page->context)) {
$actioncell->text .= html_writer::empty_tag(
'input',
[
'type' => 'submit',
'name' => 'revoke',
'value' => get_string('revoke', 'badges') . ' ' . $this->output->rarrow(),
'class' => 'actionbutton btn btn-secondary')
);
'class' => 'actionbutton btn btn-secondary',
]
);
}
$actioncell->text .= html_writer::end_tag('div', array());
$actioncell->attributes['class'] = 'actions';
$potentialcell = new html_table_cell();
@@ -187,10 +192,22 @@ class core_badges_renderer extends plugin_renderer_base {
$dl[get_string('createdon', 'search')] = userdate($badge->timecreated);
$dl[get_string('badgeimage', 'badges')] = print_badge_image($badge, $context, 'large');
$dl[get_string('imageauthorname', 'badges')] = $badge->imageauthorname;
$dl[get_string('imageauthoremail', 'badges')] =
html_writer::tag('a', $badge->imageauthoremail, array('href' => 'mailto:' . $badge->imageauthoremail));
$dl[get_string('imageauthorurl', 'badges')] =
html_writer::link($badge->imageauthorurl, $badge->imageauthorurl, array('target' => '_blank'));
$dl[get_string('imageauthoremail', 'badges')] = '';
if (trim($badge->imageauthoremail)) {
$dl[get_string('imageauthoremail', 'badges')] = html_writer::tag(
'a',
$badge->imageauthoremail,
['href' => 'mailto:' . $badge->imageauthoremail],
);
}
$dl[get_string('imageauthorurl', 'badges')] = '';
if (trim($badge->imageauthorurl)) {
$dl[get_string('imageauthorurl', 'badges')] = html_writer::link(
$badge->imageauthorurl,
$badge->imageauthorurl,
['target' => '_blank'],
);
}
$dl[get_string('imagecaption', 'badges')] = $badge->imagecaption;
$tags = \core_tag_tag::get_item_tags('core_badges', 'badge', $badge->id);
$dl[get_string('tags', 'badges')] = $this->output->tag_list($tags, '');
@@ -200,12 +217,22 @@ class core_badges_renderer extends plugin_renderer_base {
$display .= $this->heading(get_string('issuerdetails', 'badges'), 3);
$dl = array();
$dl[get_string('issuername', 'badges')] = $badge->issuername;
$dl[get_string('contact', 'badges')] = html_writer::tag('a', $badge->issuercontact, array('href' => 'mailto:' . $badge->issuercontact));
$dl[get_string('issuerurl', 'badges')] = html_writer::tag(
'a',
$badge->issuerurl,
['href' => $badge->issuerurl, 'target' => '_blank'],
);
$dl[get_string('contact', 'badges')] = '';
if (trim($badge->issuercontact)) {
$dl[get_string('contact', 'badges')] = html_writer::tag(
'a',
$badge->issuercontact,
['href' => 'mailto:' . $badge->issuercontact],
);
}
$dl[get_string('issuerurl', 'badges')] = '';
if (trim($badge->issuerurl)) {
$dl[get_string('issuerurl', 'badges')] = html_writer::tag(
'a',
$badge->issuerurl,
['href' => $badge->issuerurl, 'target' => '_blank'],
);
}
$display .= $this->definition_list($dl);
// Issuance details if any.
+195
View File
@@ -0,0 +1,195 @@
<?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 core_badges;
/**
* Unit tests for backpack_api class.
*
* @package core_badges
* @covers \core_badges\backpack_api
* @copyright 2025 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class backpack_api_test extends \advanced_testcase {
/**
* Test get_providers function.
*/
public function test_get_providers(): void {
global $CFG;
$providers = backpack_api::get_providers();
$this->assertCount(2, $providers);
$this->assertArrayHasKey(backpack_api::PROVIDER_CANVAS_CREDENTIALS, $providers);
$this->assertArrayHasKey(backpack_api::PROVIDER_OTHER, $providers);
}
/**
* Test get_regions function.
*/
public function test_get_regions(): void {
global $CFG;
$this->resetAfterTest();
// Default: 5 regions (Canvas Credentials).
$regions = backpack_api::get_regions();
$this->assertCount(5, $regions);
// No regions.
$CFG->badges_canvasregions = '';
$regions = backpack_api::get_regions();
$this->assertEmpty($regions);
// One region.
$CFG->badges_canvasregions = 'Australia|https://au.badgr.io|https://api.au.badgr.io/v2';
$regions = backpack_api::get_regions();
$this->assertCount(1, $regions);
$this->assertEquals('Australia', $regions[0]['name']);
$this->assertEquals('https://au.badgr.io', $regions[0]['url']);
$this->assertEquals('https://api.au.badgr.io/v2', $regions[0]['apiurl']);
// Two regions + empty lines + invalid line.
$CFG->badges_canvasregions = "\nUnited States|https://badgr.io|https://api.badgr.io/v2\ninvalidline\n" .
'Europe|https://eu.badgr.io|https://api.eu.badgr.io/v2' . "\n";
$regions = backpack_api::get_regions();
$this->assertCount(2, $regions);
$expected = [
[
'name' => 'United States',
'url' => 'https://badgr.io',
'apiurl' => 'https://api.badgr.io/v2',
],
[
'name' => 'Europe',
'url' => 'https://eu.badgr.io',
'apiurl' => 'https://api.eu.badgr.io/v2',
],
];
$this->assertEquals($expected, $regions);
}
/**
* Test display_canvas_credentials_fields function.
*/
public function test_display_canvas_credentials_fields(): void {
global $CFG;
$this->resetAfterTest();
// By default, the fields should be displayed (5 regions).
$this->assertTrue(backpack_api::display_canvas_credentials_fields());
// No regions configured, fields should not be displayed.
$CFG->badges_canvasregions = '';
$this->assertFalse(backpack_api::display_canvas_credentials_fields());
// One region configured, fields should be displayed.
$CFG->badges_canvasregions = 'Australia|https://au.badgr.io|https://api.au.badgr.io/v2';
$this->assertTrue(backpack_api::display_canvas_credentials_fields());
}
/**
* Test get_region_url and get_region_api_url functions.
*/
public function test_get_region_urls(): void {
global $CFG;
$this->resetAfterTest();
// Default: 5 regions (Canvas Credentials).
$regions = backpack_api::get_regions();
$this->assertCount(5, $regions);
$this->assertEquals('https://au.badgr.io', backpack_api::get_region_url(0));
$this->assertEquals('https://ca.badgr.io', backpack_api::get_region_url(1));
$this->assertEquals('https://eu.badgr.io', backpack_api::get_region_url(2));
$this->assertEquals('https://sg.badgr.io', backpack_api::get_region_url(3));
$this->assertEquals('https://badgr.io', backpack_api::get_region_url(4));
$this->assertEquals('https://api.au.badgr.io/v2', backpack_api::get_region_api_url(0));
$this->assertEquals('https://api.ca.badgr.io/v2', backpack_api::get_region_api_url(1));
$this->assertEquals('https://api.eu.badgr.io/v2', backpack_api::get_region_api_url(2));
$this->assertEquals('https://api.sg.badgr.io/v2', backpack_api::get_region_api_url(3));
$this->assertEquals('https://api.badgr.io/v2', backpack_api::get_region_api_url(4));
// Wrong index.
$this->assertNull(backpack_api::get_region_url(10));
$this->assertNull(backpack_api::get_region_api_url(10));
// No regions.
$CFG->badges_canvasregions = '';
$this->assertNull(backpack_api::get_region_url(0));
$this->assertNull(backpack_api::get_region_api_url(0));
}
/**
* Test get_regionid_from_url function.
*/
public function test_get_regionid_from_url(): void {
global $CFG;
$this->resetAfterTest();
// Default: 5 regions (Canvas Credentials).
$regions = backpack_api::get_regions();
$this->assertCount(5, $regions);
$this->assertEquals(0, backpack_api::get_regionid_from_url('https://au.badgr.io'));
$this->assertEquals(1, backpack_api::get_regionid_from_url('https://ca.badgr.io'));
$this->assertEquals(2, backpack_api::get_regionid_from_url('https://eu.badgr.io'));
$this->assertEquals(3, backpack_api::get_regionid_from_url('https://sg.badgr.io'));
$this->assertEquals(4, backpack_api::get_regionid_from_url('https://badgr.io'));
// Test with trailing slash.
$this->assertEquals(0, backpack_api::get_regionid_from_url('https://au.badgr.io/'));
// Wrong URL.
$this->assertEquals(4, backpack_api::get_regionid_from_url('https://unknown.badgr.io'));
// One region.
$CFG->badges_canvasregions = 'Australia|https://au.badgr.io|https://api.au.badgr.io/v2';
$regions = backpack_api::get_regions();
$this->assertEquals(0, backpack_api::get_regionid_from_url('https://au.badgr.io'));
// No regions.
$CFG->badges_canvasregions = '';
$this->assertEquals(backpack_api::REGION_EMPTY, backpack_api::get_regionid_from_url('https://au.badgr.io'));
}
/**
* Test is_canvas_credentials_region function.
*/
public function test_is_canvas_credentials_region(): void {
global $CFG;
$this->resetAfterTest();
// Default: 5 regions (Canvas Credentials).
$regions = backpack_api::get_regions();
$this->assertCount(5, $regions);
$this->assertTrue(backpack_api::is_canvas_credentials_region('https://au.badgr.io'));
$this->assertTrue(backpack_api::is_canvas_credentials_region('https://ca.badgr.io'));
$this->assertTrue(backpack_api::is_canvas_credentials_region('https://eu.badgr.io'));
$this->assertTrue(backpack_api::is_canvas_credentials_region('https://sg.badgr.io'));
$this->assertTrue(backpack_api::is_canvas_credentials_region('https://badgr.io'));
// Non Canvas URL.
$this->assertFalse(backpack_api::is_canvas_credentials_region('https://unknown.badgr.io'));
// No regions.
$CFG->badges_canvasregions = '';
$this->assertFalse(backpack_api::is_canvas_credentials_region('https://au.badgr.io'));
}
}
+211 -33
View File
@@ -93,12 +93,111 @@ Feature: Backpack badges
And "Add to backpack" "link" should exist
@javascript
Scenario: Add a new site backpack
Scenario: Add a new site OBv2.1 backpack
Given I am on homepage
And I log in as "admin"
And I navigate to "Badges > Manage backpacks" in site administration
When I press "Add a new backpack"
And I set the field "apiversion" to "2.1"
And I should see "Backpack URL"
And I set the field "backpackweburlv2p1" to "http://backpackweburl.cat"
And I should not see "Backpack API URL"
Then "Connect to backpack provider account" "checkbox" should not be visible
And "Connect to a Canvas Credentials issuer account" "checkbox" should not be visible
And I should not see "Email"
And I should not see "Password"
@javascript
Scenario: Add a new site OBv2.0 backpack with Canvas provider
Given I am on homepage
And I log in as "admin"
And I navigate to "Badges > Manage backpacks" in site administration
When I press "Add a new backpack"
And I set the field "apiversion" to "2"
And I press "Save changes"
And I should see "You must supply a value here"
And I set the field "provider" to "Canvas Credentials"
And I press "Save changes"
And I should see "You must supply a value here"
And I set the field "region" to "Singapore"
And I should not see "Backpack web URL"
And I should not see "Backpack API URL"
And I press "Save changes"
Then I should see "https://sg.badgr.io"
And "Delete" "icon" should exist in the "https://sg.badgr.io" "table_row"
And "Edit settings" "icon" should exist in the "https://sg.badgr.io" "table_row"
And "Test settings" "icon" should exist in the "https://sg.badgr.io" "table_row"
# Check that editing the backpack shows the correct values.
And I click on "Edit settings" "link" in the "https://sg.badgr.io" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And I should see "Provider"
And the field "provider" matches value "Canvas Credentials"
And I should see "Region"
And the field "region" matches value "Singapore"
And I should see "Connect to a Canvas Credentials issuer account"
And the field "Connect to a Canvas Credentials issuer account" matches value "0"
And I should not see "Connect to backpack provider account"
@javascript
Scenario: Add a new site OBv2.0 backpack with Canvas provider and issuer authentication details
Given I am on homepage
And I log in as "admin"
And I navigate to "Badges > Manage backpacks" in site administration
When I press "Add a new backpack"
And I set the field "apiversion" to "2"
And I set the field "provider" to "Canvas Credentials"
And I set the field "region" to "Canada"
And I should see "Connect to a Canvas Credentials issuer account"
And I should not see "Connect to backpack provider account"
And the field "Connect to a Canvas Credentials issuer account" matches value "0"
And I click on "includeauthdetailscanvas" "checkbox"
And I should see "Email"
And I should see "Password"
And I press "Save changes"
And I should see "You must supply a value here"
And I set the field "backpackemailcanvas" to "test@test.com"
And I should see "You must supply a value here"
And I press "Save changes"
And I set the field "backpackpasswordcanvas" to "123456"
And I press "Save changes"
Then I should see "https://ca.badgr.io"
# Check that editing the backpack shows the correct values.
And I click on "Edit settings" "link" in the "https://ca.badgr.io" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And I should see "Provider"
And the field "provider" matches value "Canvas Credentials"
And I should see "Region"
And the field "region" matches value "Canada"
And I should see "Connect to a Canvas Credentials issuer account"
And the field "Connect to a Canvas Credentials issuer account" matches value "1"
And the field "backpackemailcanvas" matches value "test@test.com"
And the field "backpackpasswordcanvas" matches value "123456"
# Disable authentication details and check that email and password are cleared.
But I click on "includeauthdetailscanvas" "checkbox"
And I press "Save changes"
And I click on "Edit settings" "link" in the "https://ca.badgr.io" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And I should see "Provider"
And the field "provider" matches value "Canvas Credentials"
And I should see "Region"
And the field "region" matches value "Canada"
And I should see "Connect to a Canvas Credentials issuer account"
And the field "Connect to a Canvas Credentials issuer account" matches value "0"
And the field "backpackemailcanvas" matches value ""
And the field "backpackpasswordcanvas" matches value ""
@javascript
Scenario: Add a new site OBv2.0 backpack with Other provider
Given I am on homepage
And I log in as "admin"
And I navigate to "Badges > Manage backpacks" in site administration
When I press "Add a new backpack"
And I set the field "apiversion" to "2"
And I set the field "provider" to "Other"
And I should not see "Region"
And I set the field "backpackweburl" to "aaa"
And I press "Save changes"
And I should see "Invalid URL"
@@ -110,6 +209,115 @@ Feature: Backpack badges
Then I should see "http://backpackweburl.cat"
And "Delete" "icon" should exist in the "http://backpackweburl.cat" "table_row"
And "Edit settings" "icon" should exist in the "http://backpackweburl.cat" "table_row"
And "Test settings" "icon" should exist in the "http://backpackweburl.cat" "table_row"
# Check that editing the backpack shows the correct values.
And I click on "Edit settings" "link" in the "http://backpackweburl.cat" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And I should see "Provider"
And the field "provider" matches value "Other"
And I should not see "Region"
And the field "backpackweburl" matches value "http://backpackweburl.cat"
And the field "backpackapiurl" matches value "http://backpackapiurl.cat"
And the field "Connect to backpack provider account" matches value "0"
And I should not see "Connect to a Canvas Credentials issuer account"
@javascript
Scenario: Add a new site OBv2.0 backpack with Other provider and issuer authentication details
Given I am on homepage
And I log in as "admin"
And I navigate to "Badges > Manage backpacks" in site administration
When I press "Add a new backpack"
And I set the field "apiversion" to "2"
And I set the field "provider" to "Other"
And I set the field "backpackweburl" to "http://backpackweburl.cat"
And I set the field "backpackapiurl" to "http://backpackapiurl.cat"
And I should see "Connect to backpack provider account"
And I should not see "Connect to a Canvas Credentials issuer account"
And the field "Connect to backpack provider account" matches value "0"
And I click on "includeauthdetails" "checkbox"
And I should see "Email"
And I should see "Password"
And I press "Save changes"
And I should see "You must supply a value here"
And I set the field "backpackemail" to "test@test.com"
And I press "Save changes"
And I should see "You must supply a value here"
And I set the field "password" to "123456"
And I press "Save changes"
Then I should see "http://backpackweburl.cat"
# Check that editing the backpack shows the correct values.
And I click on "Edit settings" "link" in the "http://backpackweburl.cat" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And the field "provider" matches value "Other"
And the field "backpackweburl" matches value "http://backpackweburl.cat"
And the field "backpackapiurl" matches value "http://backpackapiurl.cat"
And the field "Connect to backpack provider account" matches value "1"
And the field "backpackemail" matches value "test@test.com"
And the field "password" matches value "123456"
# Disable authentication details and check that email and password are cleared.
But I click on "includeauthdetails" "checkbox"
And I press "Save changes"
And I click on "Edit settings" "link" in the "http://backpackweburl.cat" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And the field "provider" matches value "Other"
And the field "backpackweburl" matches value "http://backpackweburl.cat"
And the field "backpackapiurl" matches value "http://backpackapiurl.cat"
And the field "Connect to backpack provider account" matches value "0"
And the field "backpackemail" matches value ""
And the field "password" matches value ""
@javascript
Scenario: Add a new site OBv2.0 backpack without providers
Given the following config values are set as admin:
| badges_canvasregions | |
And I am on homepage
And I log in as "admin"
And I navigate to "Badges > Manage backpacks" in site administration
When I press "Add a new backpack"
And I set the field "apiversion" to "2"
And I should not see "Provider"
And I should see "Backpack URL"
And I should see "Backpack API URL"
And I press "Save changes"
And I should see "You must supply a value here"
And I set the field "backpackweburl" to "https://eu.badgr.io"
And I set the field "backpackapiurl" to "https://api.eu.badgr.io/v2"
And I should see "Connect to backpack provider account"
And I should not see "Connect to a Canvas Credentials issuer account"
And I press "Save changes"
Then I should see "https://eu.badgr.io"
And "Delete" "icon" should exist in the "https://eu.badgr.io" "table_row"
And "Edit settings" "icon" should exist in the "https://eu.badgr.io" "table_row"
And "Test settings" "icon" should exist in the "https://eu.badgr.io" "table_row"
# Check that editing the backpack shows the correct values.
And I click on "Edit settings" "link" in the "https://eu.badgr.io" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And I should not see "Provider"
And the field "backpackweburl" matches value "https://eu.badgr.io"
And the field "backpackapiurl" matches value "https://api.eu.badgr.io/v2"
And I should see "Connect to backpack provider account"
And the field "Connect to backpack provider account" matches value "0"
And I should not see "Connect to a Canvas Credentials issuer account"
And I press "Cancel"
# Add Europe to the providers list and check that editing the backpack shows the correct values.
But the following config values are set as admin:
| badges_canvasregions | Europe\|https://eu.badgr.io\|https://api.eu.badgr.io/v2 |
And I click on "Edit settings" "link" in the "https://eu.badgr.io" "table_row"
And I should see "API version supported"
And the field "apiversion" matches value "2"
And I should see "Provider"
And the field "provider" matches value "Canvas Credentials"
And I should see "Region"
And the field "region" matches value "Europe"
And I should see "Connect to a Canvas Credentials issuer account"
And the field "Connect to a Canvas Credentials issuer account" matches value "0"
And I should not see "Connect to backpack provider account"
And I should not see "Backpack URL"
And I should not see "Backpack API URL"
@javascript
Scenario: Remove a site backpack
@@ -138,36 +346,6 @@ Feature: Backpack badges
And "Move up" "icon" should exist in the "https://dc.imsglobal.org" "table_row"
And "Move down" "icon" should not exist in the "https://dc.imsglobal.org" "table_row"
@javascript
Scenario: Add a new site backpack with authentication details checkbox
Given I am on homepage
And I log in as "admin"
And I navigate to "Badges > Manage backpacks" in site administration
When I press "Add a new backpack"
And I set the field "apiversion" to "2.1"
And I set the field "backpackweburl" to "http://backpackweburl.cat"
And I should not see "Backpack API URL"
Then "Include authentication details with the backpack" "checkbox" should not be visible
And I should not see "Badge issuer email address"
And I should not see "Badge issuer password"
And I set the field "apiversion" to "2"
And "Include authentication details with the backpack" "checkbox" should be visible
And I click on "includeauthdetails" "checkbox"
And I should see "Badge issuer email address"
And I should see "Badge issuer password"
And I set the field "backpackemail" to "test@test.com"
And I set the field "password" to "123456"
And I set the field "backpackapiurl" to "http://backpackapiurl.cat"
And I press "Save changes"
And I click on "Edit" "link" in the "http://backpackweburl.cat" "table_row"
And the field "Include authentication details with the backpack" matches value "1"
And I click on "includeauthdetails" "checkbox"
And I press "Save changes"
And I click on "Edit" "link" in the "http://backpackweburl.cat" "table_row"
And the field "Include authentication details with the backpack" matches value "0"
And I click on "includeauthdetails" "checkbox"
And I should not see "test@test.com"
@javascript
Scenario: View backpack form as a student
Given I log in as "student1"
@@ -175,11 +353,11 @@ Feature: Backpack badges
And I follow "Backpack settings"
When I set the field "externalbackpackid" to "https://dc.imsglobal.org"
Then I should not see "Log in to your backpack"
And I should not see "Email address"
And I should not see "Email"
And I should not see "Password"
But I set the field "externalbackpackid" to "https://test.com/"
And I should see "Log in to your backpack"
And I should see "Email address"
And I should see "Email"
And I should see "Password"
@javascript
+21
View File
@@ -0,0 +1,21 @@
@core @core_badges
Feature: Badge overview
In order to view a badge's information
As an user with the capability to view badges
I need to access the badge overview page
Background:
Given the following config values are set as admin:
| badges_defaultissuername | Test Badge Site |
@javascript @accessibility @_file_upload
Scenario: Test accessibility of badge overview page
Given I log in as "admin"
And I navigate to "Badges > Add a new badge" in site administration
And I set the following fields to these values:
| Name | Cool badge |
| Description | Badge for cool people |
And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
When I select "Overview" from the "Badges navigation" singleselect
Then the "region-main" "region" should meet accessibility standards with "best-practice" extra tests
+2
View File
@@ -118,6 +118,7 @@ Feature: Manage badges
| Admin User |
| User One |
@accessibility
Scenario: View list of badges with recipients
Given the following "users" exist:
| username | firstname | lastname |
@@ -139,6 +140,7 @@ Feature: Manage badges
| Badge #1 | Not available | 2 |
| Badge #2 | Available | 1 |
| Badge #3 | Available | 0 |
And the "Badges" "table" should meet accessibility standards with "best-practice" extra tests
@_file_upload
Scenario: Badge names are not unique anymore
+2
View File
@@ -24,6 +24,7 @@ Feature: Display badges
And I log in as "admin"
And I navigate to "Badges > Manage badges" in site administration
@accessibility
Scenario: Display badge without expired date
# Enable the badge.
Given I press "Enable access" action in the "Testing system badge" report row
@@ -44,6 +45,7 @@ Feature: Display badges
And I follow "More details"
And I should see "Catalan"
And I should see "1.1"
And the page should meet accessibility standards with "best-practice" extra tests
Scenario: Display badge with ALL criteria
# Add another criterion and enable the badge.
@@ -125,6 +125,7 @@ final class badges_test extends core_reportbuilder_testcase {
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname', 'sortenabled' => 1]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:namewithlink']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:namewithimagelink']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:criteria']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:image']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:language']);
@@ -146,11 +147,12 @@ final class badges_test extends core_reportbuilder_testcase {
['id' => $badgeone->id]), ($badgeone->name));
// First badge, issued to user one.
[$badgename, $fullname, $namewithlink, $criteria, $image, $language, $version, $status, $expiry, $tag, $expires,
$visible, $coursename] = array_values($content[0]);
[$badgename, $fullname, $namewithlink, $namewithimagelink, $criteria, $image, $language, $version, $status, $expiry, $tag,
$expires, $visible, $coursename] = array_values($content[0]);
$this->assertEquals($badgeone->name, $badgename);
$this->assertEquals(fullname($user1), $fullname);
$this->assertEquals($expectedbadgeonelink, $namewithlink);
$this->assertEquals($image . ' ' . $expectedbadgeonelink, $namewithimagelink);
$this->assertStringContainsString('Awarded by: Manager', $criteria);
$this->assertStringContainsString('Image caption', $image);
$this->assertEquals('German', $language);
@@ -163,11 +165,12 @@ final class badges_test extends core_reportbuilder_testcase {
$this->assertEquals('PHPUnit test site', $coursename);
// First badge, issued to user two.
[$badgename, $fullname, $namewithlink, $criteria, $image, $language, $version, $status, $expiry, $tag, $expires,
$visible, $coursename] = array_values($content[1]);
[$badgename, $fullname, $namewithlink, $namewithimagelink, $criteria, $image, $language, $version, $status, $expiry, $tag,
$expires, $visible, $coursename] = array_values($content[1]);
$this->assertEquals($badgeone->name, $badgename);
$this->assertEquals(fullname($user2), $fullname);
$this->assertEquals($expectedbadgeonelink, $namewithlink);
$this->assertEquals($image . ' ' . $expectedbadgeonelink, $namewithimagelink);
$this->assertStringContainsString('Awarded by: Manager', $criteria);
$this->assertStringContainsString('Image caption', $image);
$this->assertEquals('German', $language);
@@ -183,11 +186,12 @@ final class badges_test extends core_reportbuilder_testcase {
['id' => $badgetwo->id]), ($badgetwo->name));
// Course badge, not issues to any users.
[$badgename, $fullname, $namewithlink, $criteria, $image, $language, $version, $status, $expiry, $tag, $expires,
$visible, $coursename] = array_values($content[2]);
[$badgename, $fullname, $namewithlink, $namewithimagelink, $criteria, $image, $language, $version, $status, $expiry, $tag,
$expires, $visible, $coursename] = array_values($content[2]);
$this->assertEquals($badgetwo->name, $badgename);
$this->assertEmpty($fullname);
$this->assertEquals($expectedbadgetwolink, $namewithlink);
$this->assertEquals($image . ' ' . $expectedbadgetwolink, $namewithimagelink);
$this->assertEquals('Criteria for this badge have not been set up yet.', $criteria);
$this->assertStringContainsString('Image caption', $image);
$this->assertEquals('English', $language);
@@ -22,9 +22,9 @@ Feature: Block accessreview
When I add the "Accessibility Review" block
Then I should see "Accessibility Review"
And I should see "Your accessibility toolkit needs to be registered."
And I click on "Actions menu" "icon" in the "Accessibility Review" "block"
And I click on "Actions menu" "menuitem" in the "Accessibility Review" "block"
And I follow "Hide Accessibility Review block"
And I should not see "Your accessibility toolkit needs to be registered."
And I click on "Actions menu" "icon" in the "Accessibility Review" "block"
And I click on "Actions menu" "menuitem" in the "Accessibility Review" "block"
And I follow "Show Accessibility Review block"
And I should see "Your accessibility toolkit needs to be registered."
@@ -24,9 +24,9 @@ Feature: Block accessreview
When I add the "Accessibility review" block
Then I should see "Accessibility review"
And I should see "Your accessibility toolkit needs to be registered."
And I click on "Actions menu" "icon" in the "Accessibility review" "block"
And I click on "Actions menu" "menuitem" in the "Accessibility review" "block"
And I follow "Hide Accessibility review block"
And I should not see "Your accessibility toolkit needs to be registered."
And I click on "Actions menu" "icon" in the "Accessibility review" "block"
And I click on "Actions menu" "menuitem" in the "Accessibility review" "block"
And I follow "Show Accessibility review block"
And I should see "Your accessibility toolkit needs to be registered."
+9 -4
View File
@@ -32,7 +32,9 @@
* @todo MDL-36050 improve capability check on stick blocks, so we can check user capability before sending images.
*/
function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
global $DB, $CFG, $USER;
global $CFG;
require_once("{$CFG->dirroot}/user/lib.php");
if ($context->contextlevel != CONTEXT_BLOCK) {
send_file_not_found();
@@ -51,9 +53,12 @@ function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $a
if (!core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) {
send_file_not_found();
}
} else if ($parentcontext->contextlevel === CONTEXT_USER && $parentcontext->instanceid != $USER->id) {
// The block is in the context of a user, it is only visible to the user who it belongs to.
send_file_not_found();
} else if ($parentcontext->contextlevel === CONTEXT_USER) {
$user = core_user::get_user($parentcontext->instanceid, '*', MUST_EXIST);
$extracaps = block_method_result('html', 'get_extra_capabilities');
if (!user_can_view_profile($user, null, $parentcontext) || !has_any_capability($extracaps, $context)) {
send_file_not_found();
}
}
// At this point there is no way to check SYSTEM context, so ignoring it.
}
@@ -297,6 +297,7 @@ final class provider_test extends \core_privacy\tests\provider_testcase {
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$this->setUser($user);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher');
$block = $this->create_course_block($course, $title, $content, $format);
$context = \context_block::instance($block->instance->id);
@@ -324,6 +325,7 @@ final class provider_test extends \core_privacy\tests\provider_testcase {
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$this->setUser($user);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher');
$block = $this->create_course_block($course, $title, $content, $format);
$context = \context_block::instance($block->instance->id);
@@ -369,6 +371,7 @@ final class provider_test extends \core_privacy\tests\provider_testcase {
$this->setUser($user2);
$course = $this->getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
$courseblock = $this->create_course_block($course, $title, $content, $blockformat);
$coursecontext = \context_block::instance($courseblock->instance->id);
@@ -24,8 +24,6 @@
}}
<div id="block-myoverview-{{uniqid}}" class="block-myoverview block-cards" data-region="myoverview" role="navigation">
<hr class="mt-0"/>
<div role="search" data-region="filter" class="d-flex align-items-center my-2" aria-label="{{#str}} aria:controls, block_myoverview {{/str}}">
<div class="row g-0">
{{> block_myoverview/nav-grouping-selector }}
+15 -6
View File
@@ -9,8 +9,9 @@ Feature: Block removal via modal
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| search_forums | Course | C1 | course-view-* | side-pre |
| blockname | contextlevel | reference | pagetypepattern | showinsubcontexts | defaultregion |
| search_forums | Course | C1 | course-view-* | 0 | side-pre |
| myprofile | System | | * | 1 | side-pre |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
@@ -18,16 +19,24 @@ Feature: Block removal via modal
Scenario: Removing a block via modal should remove the block on the page
Given I open the "Search forums" blocks action menu
When I click on "Delete Search forums block" "link" in the "Search forums" "block"
Then "Delete block?" "dialogue" should exist
And I should see "This will delete the block Search forums." in the "Delete block?" "dialogue"
And I click on "Delete" "button" in the "Delete block?" "dialogue"
And I wait to be redirected
And "Search forums" "block" should not exist
Then "Search forums" "block" should not exist
And I open the "Logged in user" blocks action menu
And I click on "Delete Logged in user block" "link" in the "Logged in user" "block"
And I should see "You are about to delete a block that appears elsewhere" in the "Delete block?" "dialogue"
And I should see "Original block location: System" in the "Delete block?" "dialogue"
And I should see "Display on page types: Any page" in the "Delete block?" "dialogue"
And I should see "Are you sure you want to continue?" in the "Delete block?" "dialogue"
And I click on "Delete" "button" in the "Delete block?" "dialogue"
And I wait to be redirected
And "Logged in user" "block" should not exist
@javascript
Scenario: Cancel removing a block via modal should retain the block on the page
Given I open the "Search forums" blocks action menu
When I click on "Delete Search forums block" "link" in the "Search forums" "block"
Then "Delete block?" "dialogue" should exist
And I click on "Cancel" "button" in the "Delete block?" "dialogue"
And I should not see "Delete block?"
And "Search forums" "block" should exist
Then "Search forums" "block" should exist
+6
View File
@@ -44,6 +44,7 @@ final class externallib_test extends externallib_advanced_testcase {
global $DB, $FULLME;
$this->resetAfterTest(true);
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
@@ -77,6 +78,7 @@ final class externallib_test extends externallib_advanced_testcase {
global $DB, $FULLME;
$this->resetAfterTest(true);
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user();
@@ -141,6 +143,7 @@ final class externallib_test extends externallib_advanced_testcase {
global $DB, $FULLME;
$this->resetAfterTest(true);
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
@@ -237,6 +240,7 @@ final class externallib_test extends externallib_advanced_testcase {
global $DB, $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
// Enable MathJax filter in content and headings.
$this->configure_filters([
@@ -362,6 +366,7 @@ final class externallib_test extends externallib_advanced_testcase {
public function test_get_dashboard_blocks_default_dashboard_including_sticky_block(): void {
global $PAGE, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user();
$PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set.
@@ -410,6 +415,7 @@ final class externallib_test extends externallib_advanced_testcase {
public function test_get_dashboard_blocks_custom_user_dashboard(): void {
global $PAGE, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user();
$PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set.
+1 -1
View File
@@ -74,7 +74,7 @@ class footer_options_exporter extends exporter {
*/
protected function get_manage_subscriptions_link(): ?string {
if (calendar_user_can_add_event($this->calendar->course)) {
$managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php');
$managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php', ['course' => $this->calendar->courseid]);
return $managesubscriptionurl->out(true);
}
return null;
@@ -213,7 +213,8 @@ class create extends \moodleform {
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
$group[] = $mform->createElement('date_time_selector', 'timedurationuntil', '');
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
$group[] = $mform->createElement('text', 'timedurationminutes', get_string('durationminutes', 'calendar'));
$group[] = $mform->createElement('text', 'timedurationminutes',
get_string('durationminutes', 'calendar'), 'maxlength="7" size="7"');
$mform->addGroup($group, 'durationgroup', get_string('eventduration', 'calendar'), '<br />', false);
$mform->setAdvanced('durationgroup');
@@ -227,6 +228,9 @@ class create extends \moodleform {
$mform->setType('timedurationminutes', PARAM_INT);
$mform->disabledIf('timedurationminutes', 'duration', 'noteq', 2);
$mform->addGroupRule('durationgroup', [
'timedurationminutes' => [[get_string('maximumchars', '', 7), 'maxlength', 7, 'server']],
]);
$mform->setDefault('duration', 0);
}
@@ -238,11 +242,12 @@ class create extends \moodleform {
*/
protected function add_event_repeat_elements($mform) {
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null);
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="10" size="10"');
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="3" size="3"');
$mform->setType('repeats', PARAM_INT);
$mform->setDefault('repeats', 1);
$mform->disabledIf('repeats', 'repeat', 'notchecked');
$mform->setAdvanced('repeat');
$mform->setAdvanced('repeats');
$mform->addRule('repeats', get_string('maximumchars', '', 3), 'maxlength', 3, 'server');
}
}
+7 -2
View File
@@ -121,7 +121,8 @@ class event_form extends moodleform {
$group[] =& $mform->createElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
$group[] =& $mform->createElement('date_time_selector', 'timedurationuntil', '');
$group[] =& $mform->createElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
$group[] =& $mform->createElement('text', 'timedurationminutes', get_string('durationminutes', 'calendar'));
$group[] =& $mform->createElement('text', 'timedurationminutes',
get_string('durationminutes', 'calendar'), 'maxlength="7" size="7"');
$mform->addGroup($group, 'durationgroup', '', '<br />', false);
@@ -134,6 +135,9 @@ class event_form extends moodleform {
$mform->setType('timedurationminutes', PARAM_INT);
$mform->disabledIf('timedurationminutes','duration','noteq', 2);
$mform->addGroupRule('durationgroup', [
'timedurationminutes' => [[get_string('maximumchars', '', 7), 'maxlength', 7, 'server']],
]);
$mform->setDefault('duration', ($hasduration)?1:0);
@@ -141,10 +145,11 @@ class event_form extends moodleform {
$mform->addElement('header', 'repeatevents', get_string('repeatedevents', 'calendar'));
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null);
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="10" size="10"');
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="3" size="3"');
$mform->setType('repeats', PARAM_INT);
$mform->setDefault('repeats', 1);
$mform->disabledIf('repeats','repeat','notchecked');
$mform->addRule('repeats', get_string('maximumchars', '', 3), 'maxlength', 3, 'server');
} else if ($repeatedevents) {
+3 -3
View File
@@ -915,10 +915,10 @@ class core_calendar_external extends external_api {
$formoptions['eventtypes'] = $allowedeeventtypes;
if ($courseid) {
require_once($CFG->libdir . '/grouplib.php');
$groupcoursedata = groups_get_course_data($courseid);
if (!empty($groupcoursedata->groups)) {
$groupcoursedata = groups_get_all_groups($courseid);
if (!empty($groupcoursedata)) {
$formoptions['groups'] = [];
foreach ($groupcoursedata->groups as $groupid => $groupdata) {
foreach ($groupcoursedata as $groupid => $groupdata) {
$formoptions['groups'][$groupid] = $groupdata->name;
}
}
+6 -6
View File
@@ -3639,10 +3639,10 @@ function calendar_output_fragment_event_form($args) {
if (is_null($eventid)) {
if (!empty($courseid)) {
$groupcoursedata = groups_get_course_data($courseid);
$groupcoursedata = groups_get_all_groups($courseid);
$formoptions['groups'] = [];
foreach ($groupcoursedata->groups as $groupid => $groupdata) {
$formoptions['groups'][$groupid] = $groupdata->name;
foreach ($groupcoursedata as $groupid => $groupdata) {
$formoptions['groups'][$groupid] = format_string($groupdata->name, false, ['context' => $context]);
}
}
@@ -3683,10 +3683,10 @@ function calendar_output_fragment_event_form($args) {
$formoptions['event'] = $event;
if (!empty($event->courseid)) {
$groupcoursedata = groups_get_course_data($event->courseid);
$groupcoursedata = groups_get_all_groups($event->courseid);
$formoptions['groups'] = [];
foreach ($groupcoursedata->groups as $groupid => $groupdata) {
$formoptions['groups'][$groupid] = $groupdata->name;
foreach ($groupcoursedata as $groupid => $groupdata) {
$formoptions['groups'][$groupid] = format_string($groupdata->name, false, ['context' => $context]);
}
}
+6 -4
View File
@@ -48,11 +48,12 @@
<a{{!
}} href="#"{{!
}} class="arrow_link previous"{{!
}} title="{{#str}}monthprev, calendar{{/str}}"{{!
}} title="{{#str}}monthprevwithname, calendar, {{previousperiodname}}{{/str}}"{{!
}} data-year="{{previousperiod.year}}"{{!
}} data-month="{{previousperiod.mon}}"{{!
}} aria-label="{{#str}}monthprevwithname, calendar, {{previousperiodname}}{{/str}}" {{!
}}>
<span class="arrow">{{{larrow}}}</span>
<span class="arrow" aria-hidden="true">{{{larrow}}}</span>
</a>
<span class="hide"> | </span>
<span class="current">
@@ -62,11 +63,12 @@
<a{{!
}} href="#"{{!
}} class="arrow_link next"{{!
}} title="{{#str}}monthnext, calendar{{/str}}"{{!
}} title="{{#str}}monthnextwithname, calendar, {{nextperiodname}}{{/str}}"{{!
}} data-year="{{nextperiod.year}}"{{!
}} data-month="{{nextperiod.mon}}"{{!
}} aria-label="{{#str}}monthnextwithname, calendar, {{nextperiodname}}{{/str}}" {{!
}}>
<span class="arrow">{{{rarrow}}}</span>
<span class="arrow" aria-hidden="true">{{{rarrow}}}</span>
</a>
{{/includenavigation}}
{{^includenavigation}}
+4 -2
View File
@@ -36,10 +36,11 @@
<a{{!
}} href="{{previousperiodlink}}"{{!
}} class="arrow_link previous"{{!
}} title="{{#str}}monthprev, calendar{{/str}}"{{!
}} {{#viewinginblock}}title="{{#str}}monthprevwithname, calendar, {{previousperiodname}}{{/str}}" {{/viewinginblock}} {{!
}} data-year="{{previousperiod.year}}"{{!
}} data-month="{{previousperiod.mon}}"{{!
}} data-drop-zone="nav-link" {{!
}} aria-label="{{#str}}monthprevwithname, calendar, {{previousperiodname}}{{/str}}" {{!
}}>
<span class="arrow" aria-hidden="true">{{{larrow}}}</span>
&nbsp;
@@ -56,10 +57,11 @@
<a{{!
}} href="{{nextperiodlink}}"{{!
}} class="arrow_link next"{{!
}} title="{{#str}}monthnext, calendar{{/str}}"{{!
}} {{#viewinginblock}}title="{{#str}}monthnextwithname, calendar, {{nextperiodname}}{{/str}}" {{/viewinginblock}} {{!
}} data-year="{{nextperiod.year}}"{{!
}} data-month="{{nextperiod.mon}}"{{!
}} data-drop-zone="nav-link" {{!
}} aria-label="{{#str}}monthnextwithname, calendar, {{nextperiodname}}{{/str}}" {{!
}}>
<span class="arrow_text">{{nextperiodname}}</span>
&nbsp;
+14 -5
View File
@@ -27,8 +27,8 @@ Feature: Perform basic calendar functionality
| teacher1 | C1 | teacher |
| admin | C1 | editingteacher |
And the following "groups" exist:
| name | course | idnumber |
| Group 1 | C1 | G1 |
| name | course | idnumber |
| <span lang="en" class="multilang">Group</span><span lang="es" class="multilang">Grupo</span> 1 | C1 | G1 |
And the following "group members" exist:
| user | group |
| student1 | G1 |
@@ -75,7 +75,9 @@ Feature: Perform basic calendar functionality
@javascript
Scenario: Create a group event
Given I log in as "teacher1"
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And I log in as "teacher1"
And I follow "Full calendar"
And I set the field "course" to "Course 1"
And I create a calendar event:
@@ -322,18 +324,23 @@ Feature: Perform basic calendar functionality
@javascript
Scenario: Changing the event type should clear previous data
Given I am on the "Course 1" course page logged in as admin
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And I am on the "Course 1" course page logged in as admin
And I follow "Course calendar"
And I press "New event"
And I set the following fields to these values:
| Event title | Group 1 event |
| Type of event | Group |
And I should see "Group 1"
And I should not see "GroupGrupo 1"
And I press "Save"
And I am on "Course 1" course homepage
And I follow "Course calendar"
And I click on "Group 1 event" "link"
And I should see "Group event"
And I should see "Group 1"
And I should not see "GroupGrupo 1"
When I click on "Edit" "button" in the "Group 1 event" "dialogue"
And I set the following fields to these values:
| Event title | My own user event |
@@ -369,7 +376,9 @@ Feature: Perform basic calendar functionality
@javascript
Scenario: Changing the event type from user to anything else should work
Given I log in as "teacher1"
Given the "multilang" filter is "on"
And the "multilang" filter applies to "content and headings"
And I log in as "teacher1"
# We need this so we can see the groups.
And the following "course enrolments" exist:
| user | course | role |
+4 -2
View File
@@ -144,8 +144,10 @@ $headingstr = get_string('calendar', 'core_calendar');
// If the user is on the course page,
// then make the course name linkable to ease the user's navigation to the course page.
if ($iscoursecalendar) {
$url = new \moodle_url('/course/view.php', ['id' => $courseid]);
$linkcourse = html_writer::link($url, $course->shortname);
$linkcourse = html_writer::link(
course_get_url($course),
format_string($course->shortname, options: ['context' => $PAGE->context]),
);
$headingstr = "{$headingstr}: {$linkcourse}";
}
$PAGE->set_heading($headingstr, false);
+6
View File
@@ -405,6 +405,12 @@ class core_cohort_external extends external_api {
$results = $results + cohort_get_available_cohorts($context, COHORT_ALL, $limitfrom, $limitnum, $query);
}
} else if ($includes == 'all') {
$contextsystem = context_system::instance();
if (!$context instanceof context_system &&
!has_any_capability(['moodle/cohort:view', 'moodle/cohort:manage'], $contextsystem)) {
throw new required_capability_exception($contextsystem, 'moodle/cohort:view', 'nopermissions', '');
}
$results = cohort_get_all_cohorts($limitfrom, $limitnum, $query);
$results = $results['cohorts'];
} else {
+16 -6
View File
@@ -672,6 +672,7 @@ final class externallib_test extends externallib_advanced_testcase {
role_assign($userrole, $catuser->id, $catcontext->id);
// Enrol user in the course.
$this->getDataGenerator()->enrol_user($creator->id, $course->id);
$this->getDataGenerator()->enrol_user($courseuser->id, $course->id, 'courserole');
$syscontext = array('contextid' => \context_system::instance()->id);
@@ -758,18 +759,27 @@ final class externallib_test extends externallib_advanced_testcase {
$this->assertCount(4, $result['cohorts']);
// A user in the course context with the system cohort:view capability. Check that all the system cohorts are returned.
$this->setUser($courseuser);
$result = core_cohort_external::search_cohorts("Cohortsearch", $coursecontext, 'all');
$this->assertCount(2, $result['cohorts']);
$this->assertEquals('Cohortsearch 1', $result['cohorts'][$cohort1->id]->name);
$this->assertCount(4, $result['cohorts']);
// A user in the course context without the ability to view system cohorts.
$this->setUser($courseuser);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $coursecontext, 'all');
$this->fail('Exception expected');
} catch (\Throwable $e) {
$this->assertInstanceOf(\required_capability_exception::class, $e);
$this->assertStringContainsString('(View site-wide cohorts)', $e->getMessage());
}
// Detect invalid parameter $includes.
$this->setUser($creator);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'invalid');
$this->fail('Invalid parameter includes');
} catch (\coding_exception $e) {
// All good.
$this->fail('Exception expected');
} catch (\Throwable $e) {
$this->assertInstanceOf(\coding_exception::class, $e);
$this->assertStringContainsString('Invalid parameter value for \'includes\'', $e->getMessage());
}
}
}
+1
View File
@@ -239,6 +239,7 @@ class provider implements
$params = (isset($user)) ? ['userid' => $user->id, 'coursemoduleid' => $cmid] : ['coursemoduleid' => $cmid];
// Only delete the record for course modules completion.
$DB->delete_records('course_modules_completion', $params);
$DB->delete_records('course_modules_viewed', $params);
return;
}
+30 -15
View File
@@ -14,25 +14,19 @@
// 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 the request helper.
*
* @package core_completion
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_completion\privacy;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/completion/tests/fixtures/completion_creation.php');
/**
* Tests for the \core_completion API's provider functionality.
*
* @package core_completion
* @category test
* @covers \core_completion\privacy\provider
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -122,14 +116,35 @@ final class provider_test extends \core_privacy\tests\provider_testcase {
* Test deleting activity completion information for a user.
*/
public function test_delete_completion_activity_user(): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
\core_completion\privacy\provider::delete_completion($user, null, $this->cm->id);
$activitycompletion = \core_completion\privacy\provider::get_activity_completion_info($user, $this->course,
$this->cm);
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$user = $this->getDataGenerator()->create_and_enrol($course);
$page = $this->getDataGenerator()->create_module('page', [
'course' => $course->id,
'completion' => 1,
'completionview' => 1,
]);
$cm = get_coursemodule_from_id('page', $page->cmid);
$completion = new \completion_info($course);
$completion->set_module_viewed($cm, $user->id);
// Sanity test.
$this->assertTrue($DB->record_exists('course_modules_completion', ['userid' => $user->id, 'coursemoduleid' => $cm->id]));
$this->assertTrue($DB->record_exists('course_modules_viewed', ['userid' => $user->id, 'coursemoduleid' => $cm->id]));
provider::delete_completion($user, null, $cm->id);
$activitycompletion = provider::get_activity_completion_info($user, $course, $cm);
$this->assertEquals(0, $activitycompletion->completionstate);
// Assert we cleared that data.
$this->assertFalse($DB->record_exists('course_modules_completion', ['userid' => $user->id, 'coursemoduleid' => $cm->id]));
$this->assertFalse($DB->record_exists('course_modules_viewed', ['userid' => $user->id, 'coursemoduleid' => $cm->id]));
}
/**
+100 -89
View File
@@ -38,94 +38,106 @@ $CFG = new stdClass();
// will be stored. This database must already have been created //
// and a username/password created to access it. //
$CFG->dbtype = 'pgsql'; // 'pgsql', 'mariadb', 'mysqli', 'auroramysql', 'sqlsrv' or 'oci'
$CFG->dblibrary = 'native'; // 'native' only at the moment
$CFG->dbhost = 'localhost'; // eg 'localhost' or 'db.isp.com' or IP
$CFG->dbname = 'moodle'; // database name, eg moodle
$CFG->dbuser = 'username'; // your database username
$CFG->dbpass = 'password'; // your database password
$CFG->prefix = 'mdl_'; // prefix to use for all table names
$CFG->dboptions = array(
'dbpersist' => false, // should persistent database connections be
// used? set to 'false' for the most stable
// setting, 'true' can improve performance
// sometimes
'dbsocket' => false, // should connection via UNIX socket be used?
// if you set it to 'true' or custom path
// here set dbhost to 'localhost',
// (please note mysql is always using socket
// if dbhost is 'localhost' - if you need
// local port connection use '127.0.0.1')
'dbport' => '', // the TCP port number to use when connecting
// to the server. keep empty string for the
// default port
'dbhandlesoptions' => false,// On PostgreSQL poolers like pgbouncer don't
// support advanced options on connection.
// If you set those in the database then
// the advanced settings will not be sent.
'dbcollation' => 'utf8mb4_unicode_ci', // MySQL has partial and full UTF-8
// support. If you wish to use partial UTF-8
// (three bytes) then set this option to
// 'utf8_unicode_ci'. If using the recommended
// settings with full UTF-8 support this should
// be set to 'utf8mb4_unicode_ci'. This option
// should be removed for all other databases.
// 'versionfromdb' => false, // On MySQL and MariaDB, this can force
// the DB version to be evaluated using
// the VERSION function instead of the version
// provided by the PHP client which could be
// wrong based on the DB server infrastructure,
// e.g. PaaS on Azure. Default is false/unset.
// Uncomment and set to true to force MySQL and
// MariaDB to use 'SELECT VERSION();'.
// 'extrainfo' => [], // Extra information for the DB driver, e.g. SQL Server,
// has additional configuration according to its environment,
// which the administrator can specify to alter and
// override any connection options.
// 'ssl' => '', // A connection mode string from the list below.
// Not supported by all drivers.
// prefer Use SSL if available - postgres default Postgres only
// disable Force non secure connection Postgres only
// require Force SSL Postgres and MySQL
// verify-full Force SSL and verify root CA Postgres and MySQL
// All mode names are adopted from Postgres
// and other databases align where possible:
// Postgres: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE
// MySql: https://www.php.net/manual/en/mysqli.real-connect.php
// It is worth noting that for MySQL require and verify-full are the same - in both cases
// verification will take place if you specify hostname as a name,
// and it will be omitted if you put an IP address.
// 'fetchbuffersize' => 100000, // On PostgreSQL, this option sets a limit
// on the number of rows that are fetched into
// memory when doing a large recordset query
// (e.g. search indexing). Default is 100000.
// Uncomment and set to a value to change it,
// or zero to turn off the limit. You need to
// set to zero if you are using pg_bouncer in
// 'transaction' mode (it is fine in 'session'
// mode).
// 'clientcompress' => true // Use compression protocol to communicate with the database server.
// Decreases traffic from the database server.
// Not needed if the databse is on the same host.
// Currently supported only with mysqli, mariadb, and aurora drivers.
$CFG->dbtype = 'pgsql'; // 'pgsql', 'mariadb', 'mysqli', 'auroramysql', 'sqlsrv' or 'oci'
$CFG->dblibrary = 'native'; // 'native' only at the moment
$CFG->dbhost = 'localhost'; // eg 'localhost' or 'db.isp.com' or IP
$CFG->dbname = 'moodle'; // database name, eg moodle
$CFG->dbuser = 'username'; // your database username
$CFG->dbpass = 'password'; // your database password
$CFG->prefix = 'mdl_'; // prefix to use for all table names
$CFG->dboptions = [
'dbpersist' => false, // Should persistent database connections be
// used? Set to 'false' for the most stable
// setting, 'true' can improve performance
// sometimes
'dbsocket' => false, // Should connection via UNIX socket be used?
// if you set it to 'true' or custom path
// here set dbhost to 'localhost',
// (please note mysql is always using socket
// if dbhost is 'localhost' - if you need
// local port connection use '127.0.0.1')
'dbport' => '', // The TCP port number to use when connecting
// to the server. Keep empty string for the
// default port
'dbhandlesoptions' => false, // On PostgreSQL poolers like pgbouncer don't
// support advanced options on connection.
// If you set those in the database then
// the advanced settings will not be sent.
'dbcollation' => 'utf8mb4_unicode_ci', // MySQL has partial and full UTF-8
// support. If you wish to use partial UTF-8
// (three bytes) then set this option to
// 'utf8_unicode_ci'. If using the recommended
// settings with full UTF-8 support this should
// be set to 'utf8mb4_unicode_ci'. This option
// should be removed for all other databases.
// 'dbschema' => '', // On PostgreSQL this sets the database schema to use
// for the connection.
// 'dbtransactions' => null // Set this to true to explicitly enable database transactions
// for MySQL. By default only the following storage engines are
// configured to use transactions: InnoDB, INNOBASE, BDB, XtraDB,
// Aria, Falcon.
// 'versionfromdb' => false, // On MySQL and MariaDB, this can force
// the DB version to be evaluated using
// the VERSION function instead of the version
// provided by the PHP client which could be
// wrong based on the DB server infrastructure,
// e.g. PaaS on Azure. Default is false/unset.
// Uncomment and set to true to force MySQL and
// MariaDB to use 'SELECT VERSION();'.
// 'extrainfo' => [], // Extra information for the DB driver, e.g. SQL Server,
// has additional configuration according to its environment,
// which the administrator can specify to alter and
// override any connection options.
// 'ssl' => '', // A connection mode string from the list below.
// Not supported by all drivers.
// prefer Use SSL if available - postgres default Postgres only
// disable Force non secure connection Postgres only
// require Force SSL Postgres and MySQL
// verify-full Force SSL and verify root CA Postgres and MySQL
// All mode names are adopted from Postgres
// and other databases align where possible:
// Postgres: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE
// MySQL: https://www.php.net/manual/en/mysqli.real-connect.php
// It is worth noting that for MySQL require and verify-full are the same - in both cases
// verification will take place if you specify hostname as a name,
// and it will be omitted if you put an IP address.
// 'fetchbuffersize' => 100000, // On PostgreSQL, this option sets a limit
// on the number of rows that are fetched into
// memory when doing a large recordset query
// (e.g. search indexing). Default is 100000.
// Uncomment and set to a value to change it,
// or zero to turn off the limit. You need to
// set to zero if you are using pg_bouncer in
// 'transaction' mode (it is fine in 'session'
// mode).
// 'clientcompress' => true // Use compression protocol to communicate with the database server.
// Decreases traffic from the database server.
// Not needed if the databse is on the same host.
// Currently supported only with mysqli, mariadb, and aurora drivers.
// 'connecttimeout' => null, // Set connect timeout in seconds. Not all drivers support it.
// 'logall' => false, // Log all queries to the database. They are stored in the table log_queries.
// 'logslow' => 0, // Log slow queries to the database. They are stored in the table log_queries.
// This parameter controls the threshold in milliseconds and requires an integer,
// not a string!
// 'logerror' => false, // Log erroneous queries to the database. They are stored in the table log_queries.
// 'bulkinsertsize' => null, // Set the bulk insert size for database operations. MySQL has a relatively small
// query length limit by default, make sure 'max_allowed_packet' in my.cnf is
// high enough if you set this setting.
// 'readonly' => [ // Set to read-only slave details, to get safe reads
// from there instead of the master node. Optional.
// Currently supported by pgsql and mysqli variety classes.
// If not supported silently ignored.
// 'instance' => [ // Readonly slave connection parameters
// [
// 'dbhost' => 'slave.dbhost',
// 'dbport' => '', // Defaults to master port
// 'dbuser' => '', // Defaults to master user
// 'dbpass' => '', // Defaults to master password
// ],
// [...],
// ],
/*
'connecttimeout' => null, // Set connect timeout in seconds. Not all drivers support it.
'readonly' => [ // Set to read-only slave details, to get safe reads
// from there instead of the master node. Optional.
// Currently supported by pgsql and mysqli variety classes.
// If not supported silently ignored.
'instance' => [ // Readonly slave connection parameters
[
'dbhost' => 'slave.dbhost',
'dbport' => '', // Defaults to master port
'dbuser' => '', // Defaults to master user
'dbpass' => '', // Defaults to master password
],
[...],
],
Instance(s) can alternatively be specified as:
'instance' => 'slave.dbhost',
'instance' => ['slave.dbhost1', 'slave.dbhost2'],
'instance' => ['dbhost' => 'slave.dbhost', 'dbport' => '', 'dbuser' => '', 'dbpass' => ''],
@@ -146,9 +158,8 @@ $CFG->dboptions = array(
More info available in lib/dml/moodle_read_slave_trait.php where the feature is implemented.
]
*/
// For all database config settings see https://docs.moodle.org/en/Database_settings
);
*/
];
//=========================================================================
@@ -133,7 +133,8 @@ class course_content_item_exporter extends exporter {
'title' => $this->contentitem->get_title()->get_value(),
'link' => $this->contentitem->get_link()->out(false),
'icon' => $this->contentitem->get_icon(),
'help' => format_text($this->contentitem->get_help(), FORMAT_MARKDOWN),
// Help text should not be parsed using course filters.
'help' => format_text($this->contentitem->get_help(), FORMAT_MARKDOWN, ['filter' => false]),
'archetype' => $this->contentitem->get_archetype(),
'componentname' => $this->contentitem->get_component_name(),
'favourite' => $favourite,
@@ -51,7 +51,9 @@ class content_item_readonly_repository implements content_item_readonly_reposito
if ($sm->string_exists('modulename_help', $modname)) {
$help = get_string('modulename_help', $modname);
if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs.
$link = get_string('modulename_link', $modname);
// The link is stored in a language file but should not be translated, use value for English.
$link = $sm->get_string('modulename_link', $modname, null, 'en');
// The text 'More help' and other strings should be in the current language.
$linktext = get_string('morehelp');
$arialabel = get_string('morehelpaboutmodule', '', get_string('modulename', $modname));
$doclink = $OUTPUT->doc_link($link, $linktext, true, ['aria-label' => $arialabel]);
+7 -3
View File
@@ -71,7 +71,7 @@ class core_course_management_renderer extends plugin_renderer_base {
final: true,
)]
public function management_heading() {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
}
/**
@@ -187,7 +187,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$a->parentcategory = $parent->get_formatted_name();
$textlabel = get_string('categorysubcategoryof', 'moodle', $a);
}
$courseicon = $this->output->pix_icon('i/course', get_string('courses'));
$courseicon = $this->output->pix_icon('i/course', get_string('courses'), 'core', ['class' => 'ps-1']);
$bcatinput = array(
'id' => 'categorylistitem' . $category->id,
'type' => 'checkbox',
@@ -343,6 +343,10 @@ class core_course_management_renderer extends plugin_renderer_base {
$actions = \core_course\management\helper::get_category_listitem_actions($category);
}
$menu = new action_menu();
$label = get_string('actionsmenu');
$actionicon = $this->output->pix_icon('t/edit_menu', '') . html_writer::span($label, 'sr-only');
$menu->set_menu_trigger($actionicon, 'iconsmall actionmenu');
$menu->triggerattributes['title'] = $label;
$menu->attributes['class'] .= ' category-item-actions item-actions';
$hasitems = false;
foreach ($actions as $key => $action) {
@@ -1286,7 +1290,7 @@ class core_course_management_renderer extends plugin_renderer_base {
final: true,
)]
public function course_search_form() {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
}
/**
@@ -71,7 +71,7 @@ class completion {
*/
#[\core\attribute\deprecated(null, mdl: 'MDL-82467', since: '4.5')]
public static function get_days(?int $value, stdClass $row): ?int {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
// Do not show anything if there is no userid.
if (!$row->userid) {
+6 -2
View File
@@ -896,8 +896,12 @@ class core_course_external extends external_api {
'showreports' => new external_value(PARAM_INT,
'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
$courseconfig->showreports),
'visible' => new external_value(PARAM_INT,
'1: available to student, 0:not available', VALUE_OPTIONAL),
'visible' => new external_value(
PARAM_INT,
'1: available to student, 0: not available',
VALUE_DEFAULT,
$courseconfig->visible,
),
'hiddensections' => new external_value(PARAM_INT,
'(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students',
VALUE_OPTIONAL),
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

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