Compare commits

..

220 Commits

Author SHA1 Message Date
Huong Nguyen fb57854de5 weekly release 4.5.10+ 2026-03-27 15:41:10 +07:00
Huong Nguyen 4fa5e7e6ea Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-03-27 15:40:57 +07:00
Andi Permana aedff688e9 MDL-87548 repository_wikimedia: Handle HTTP 429 rate limiting 2026-03-27 10:28:05 +07:00
Huong Nguyen a7a6d32f3b Merge branch 'MDL-81241-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-03-26 10:51:27 +07:00
Huong Nguyen 1d39913329 Merge branch 'MDL-88279-405' of https://github.com/meirzamoodle/moodle into MOODLE_405_STABLE 2026-03-26 07:49:41 +07:00
AMOS bot 1cdbf1089b Automatically generated installer lang files 2026-03-26 00:07:41 +00:00
Huong Nguyen b1aacfaab4 Merge branch 'MDL-85451-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-03-25 14:30:16 +07:00
meirzamoodle 4260b9a0bf MDL-88279 core_session: fix cluster test retry count assertion
Update session_redis_cluster_test to expect 3 connection attempts
instead of 5, matching the $maxretries default value in redis.php.
2026-03-24 14:40:17 +07:00
AMOS bot 7e150c3221 Automatically generated installer lang files 2026-03-21 00:07:44 +00:00
Mihail Geshoski 4d8935e15e Merge branch 'MDL-88122-navbar_empty-breadcrumb-M4.5' of https://github.com/stopfstedt/moodle into MOODLE_405_STABLE 2026-03-20 15:35:57 +08:00
Shamim Rezaie 8926183e8e weekly release 4.5.10+ 2026-03-20 15:32:43 +11:00
Shamim Rezaie 8288ac9e11 NOBUG: Add upgrade notes 2026-03-20 15:32:43 +11:00
Shamim Rezaie 6bd427fad3 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-03-20 15:32:31 +11:00
Jun Pataleta 03608d8fc6 MDL-81241 theme_boost: Accessibility test for the footer region links 2026-03-19 20:32:07 +08:00
Jun Pataleta 01ae2a52cb MDL-81241 theme_boost: Ensure footer links have sufficient spacing 2026-03-19 20:32:07 +08:00
Safat ba49df6fbb MDL-87959 tool_installaddon: Fix behats for activity chooser footer 2026-03-19 12:16:12 +07:00
Mihail Geshoski 2c1e8c2d2d Merge branch 'MDL-87810-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-03-19 09:50:33 +08:00
Ilya Tregubov 828b8f51b4 Merge branch 'MDL-75067-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2026-03-18 14:03:02 +01:00
Jun Pataleta 9b5861e5d1 MDL-85451 block_timeline: Add accessibility Behat test coverage 2026-03-18 13:26:07 +08:00
Jun Pataleta b4dddbfd75 MDL-85451 block_timeline: Group the due date filter options
To ensure that the filter options for the day filter menu matches
the menu pattern and pass accessibility checks, we need to:
* Group the due date filter options, labelled by the "Due date"
"heading".
* Set the "Due date" heading with a presentation role to prevent
assistive technologies from announcing it when the user goes through
the menu options.
2026-03-18 13:26:07 +08:00
Jun Pataleta d7eb914233 MDL-85451 block_timeline: Fix heading levels 2026-03-18 13:26:04 +08:00
Jake Dallimore 9e62c289ce Merge branch 'MDL-83096-405' of https://github.com/jason-platts/moodle into MOODLE_405_STABLE 2026-03-16 09:43:35 +08:00
Paul Holden 564da75657 MDL-88211 backup: remove redundant usage of task manager from tests.
The asynchronouse backup/restore tests are only concerned with testing
the implementation of the accompanying task class directly. When they
try to do so at a distance via the task manager API they introduce
random failures by failing to account for different queued tasks that
come from other components (e.g. Workplace).
2026-03-13 15:26:15 +01:00
Daniel Poggenpohl e7582fab55 MDL-83096 mod_lti: Register view completion when launching in new window 2026-03-13 08:59:48 +00:00
Huong Nguyen 47e76e08b6 Merge branch 'MDL-87959-405' of https://github.com/safatshahin/moodle into MOODLE_405_STABLE 2026-03-13 14:06:05 +07:00
Safat 8126a90efb MDL-87959 admin: Add marketplace availability in notifications 2026-03-13 15:58:30 +11:00
Safat e8b7ff09db MDL-87959 tool_installaddon: Add support for marketplace 2026-03-13 15:58:30 +11:00
Mihail Geshoski 71b78c2d21 Merge branch 'MDL-81608-405-2' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-03-13 09:54:41 +08:00
Huong Nguyen f35845fcad Merge branch 'MDL-87100-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2026-03-13 08:39:25 +07:00
Safat a76cc39623 Merge branch 'MDL-87708-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2026-03-12 15:19:06 +11:00
Huong Nguyen f896d4b381 MDL-87708 tool_moodlenet: Remove post install task 2026-03-12 10:48:23 +07:00
Huong Nguyen 3ca9f3a5e0 MDL-87708 admin: Improve MoodleNet removal warnings message 2026-03-12 10:16:06 +07:00
Andi Permana e77c82dc1d MDL-87708 tool_moodlenet: Clean up moodle.net configs in stable
Part of MDL-87351

- Discourage tool_moodlenet unless pointing to custom installation
- Clear activity chooser footer if set to MoodleNet
- Cleanup happens for: moodle.net URL, www.moodle.net, or empty config
- Preserves custom/local MoodleNet installations
2026-03-12 10:16:05 +07:00
Stefan Topfstedt 857fa9d371 MDL-88122 navigation: omit empty breadcrumb container in output. 2026-03-11 17:46:10 -07:00
Mihail Geshoski 1abad7745b Merge branch 'MDL-87955-405-2' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-03-11 11:56:07 +08:00
AMOS bot 4449bd5399 Automatically generated installer lang files 2026-03-11 00:09:24 +00:00
Jun Pataleta b7dcc4e960 MDL-81608 quiz: Add Add menu spacing to prevent overlap 2026-03-10 17:59:28 +08:00
Jun Pataleta b1d5327464 MDL-81608 quiz: Set more meaningful button labels
* Add/remove page break buttons
* Move question buttons
2026-03-10 17:59:28 +08:00
Jun Pataleta 1f2ecfb129 MDL-81608 core: Add alt text to the YUI spinner image
The accessibility Behat test added to the quiz editing page uncovered
that the loading spinner icon does not have an appropriate alt text.
2026-03-10 17:59:27 +08:00
Jun Pataleta e05f5eb00e MDL-81608 quiz: Adjust size and spacing for the quiz edit page buttons
* In order to meet WCAG 2.2 criterion for minimum target size and make
it easier for users who have mobility limitations to use the quiz
editing page, we need to use the Bootstrap icon button styles and
increase the size and the spacing for the following buttons:
  * Add/remove page break button
  * Lock/unlock question dependency button
* This commit also makes the icons within the buttons as decorative and
moves the accessible name to the button elements themselves via the
aria-label attribute.
* Adjust Behat tests for checking the dependency buttons.
* Add accessibility Behat coverage for the quiz edit page.
2026-03-10 17:59:27 +08:00
Jun Pataleta d348e7a481 MDL-81608 core: Label YUI dragdrop handles appropriately
The YUI dragdrop handles rely on the button's title attribute for its
accessible label. We should label it appropriately using the
aria-label attribute.
2026-03-10 17:59:27 +08:00
Huong Nguyen 67b633fa5b Merge branch 'MDL-86698-405' of https://github.com/mickhawkins/moodle into MOODLE_405_STABLE 2026-03-10 08:26:30 +07:00
Huong Nguyen e14847283c Merge branch 'MDL-87883-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2026-03-09 09:06:32 +07:00
Michael Hawkins 8bbcf2a84c MDL-86698 core: Update security.txt expiry 2026-03-06 19:10:00 +08:00
Jake Dallimore c5b9e1af04 weekly release 4.5.10+ 2026-03-06 12:10:31 +08:00
Jake Dallimore d52b85dfb5 NOBUG: Add upgrade notes 2026-03-06 12:10:31 +08:00
Jake Dallimore 971fc7f1a8 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-03-06 12:10:23 +08:00
Jun Pataleta 465cbf2813 MDL-87810 quiz: Accessibility Behat coverage for quiz attempt 2026-03-05 08:47:42 +08:00
Jun Pataleta 0f51395604 MDL-87810 question: Fix colour contrast issues on question view
* Lighten the background for the question formulation container.
* Lighten the background for the question feedback container while
  slightly darkening the text font as well.
2026-03-04 23:14:37 +08:00
Jun Pataleta b9f78d526d Merge branch 'MDL-87949-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2026-03-04 21:26:03 +08:00
Jun Pataleta 0528058a20 MDL-87955 mod_forum: Manage subscribers accessibility improvements
* Replace the layout table with responsive Bootstrap columns.
* Add accessible labels for the existing and potential subscribers
  select boxes.
* Add accessibility Behat coverage.
* Remove unused CSS styles
2026-03-04 12:58:11 +08:00
Huong Nguyen 10837f283a Merge branch 'MDL-87905-m405' of https://github.com/sammarshallou/moodle into MOODLE_405_STABLE 2026-03-04 08:50:50 +07:00
AMOS bot 920eb9e0eb Automatically generated installer lang files 2026-03-04 00:07:43 +00:00
Huong Nguyen 56d29efbd1 Merge branch 'MDL-87993-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-03-03 08:36:30 +07:00
Huong Nguyen 17ccafb6f0 Merge branch 'MDL-87710-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2026-03-03 08:28:38 +07:00
Paul Holden 6bfb046d86 MDL-75067 behat: always interact with filepicker inside active dialogue.
This allows for tests to interact with multiple elements of this type
during scenarios, whereas previously Behat would always try to interact
with the first (even when referring to latter file managers).
2026-03-02 15:53:57 +00:00
Huong Nguyen 1619586794 weekly release 4.5.10+ 2026-02-27 09:39:21 +07:00
Huong Nguyen 86079406d9 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-02-27 09:39:09 +07:00
AMOS bot 8a79c5fb4b Automatically generated installer lang files 2026-02-27 00:07:36 +00:00
sam marshall 27c9a39712 MDL-87905 Behat: pause incorrectly thinks terminal not interactive
When run from behat/cli/run.php, the pause command may fail because
it thinks the terminal is not interactive, even if the input and
output is passed through from an interactive terminal run.
2026-02-26 09:38:23 +00:00
Huong Nguyen 5a75954abb Merge branch 'MDL-88020-405' of https://github.com/rxdelacruz/moodle into MOODLE_405_STABLE 2026-02-26 09:03:17 +07:00
Huong Nguyen 58168c958c Merge branch 'MDL-88036-405' of https://github.com/mioyu-ubiq/moodle into MOODLE_405_STABLE 2026-02-26 09:01:47 +07:00
Mihail Geshoski 80e9a89704 Merge branch 'MDL-85902-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-02-26 09:58:25 +08:00
AMOS bot df91fc31a3 Automatically generated installer lang files 2026-02-26 00:07:34 +00:00
rxdelacruz 4b26a84586 MDL-88020 enrol_self: Add Behat test for cohort restriction 2026-02-25 14:57:54 +08:00
mioyu-ubiq 6ee622ad13 MDL-88036 mod_scorm: Fix duplicate scenario name 2026-02-25 14:38:51 +08:00
Jun Pataleta f7cf98ea86 MDL-85902 course: Better labeling of activity navigation components
* The arrow icons produced by the `$OUTPUT->larrow()` and
`$OUTPUT->rarrow()` functions are announced by screen readers, and they
do not make sense when heard by screen reader users. To fix this,
we need to override the accessible names of the previous and next
activity links using the `aria-label` attribute and specify whether the
activity links point to the previous or next activities.
* The label for the jump menu "Jump to" is not informative enough to
convey the element's purpose. Let's improve it's accessible label to
"Jump to activity".
2026-02-24 22:03:04 +08:00
Jun Pataleta 8c1fe31370 MDL-85902 course: Convert activity navigation to a navigation landmark 2026-02-24 21:04:51 +08:00
AMOS bot fe92590370 Automatically generated installer lang files 2026-02-24 00:07:36 +00:00
Jun Pataleta 1718b04587 MDL-87993 core: Use visually hidden core/toast for the srlogger 2026-02-20 16:19:49 +08:00
Jun Pataleta 44cb65c5a3 MDL-87993 core: Allow toasts to be visually hidden 2026-02-20 16:16:10 +08:00
Mihail Geshoski dc7dea0cf2 weekly release 4.5.10+ 2026-02-20 09:17:52 +08:00
Mihail Geshoski 81a7de4721 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-02-20 09:17:41 +08:00
AMOS bot 4027f44c77 Automatically generated installer lang files 2026-02-19 00:07:34 +00:00
Huong Nguyen 5c572f8a6b Merge branch 'MDL-87482-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2026-02-18 15:40:21 +07:00
AMOS bot 08cc1f3d32 Automatically generated installer lang files 2026-02-18 00:07:37 +00:00
Shamim Rezaie 5a1fb35680 weekly release 4.5.10+ 2026-02-17 12:56:21 +11:00
Shamim Rezaie 6f18148684 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-02-17 12:56:09 +11:00
Mihail Geshoski 6e6d5fa3cf Merge branch 'MDL-85658-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2026-02-16 13:47:58 +08:00
Paul Holden 37170e5714 MDL-87949 gradingform_rubric: accessible "Click to edit" colour.
Darken it to meet the WCAG AA standards for accessible text. This
colour is also consistent with the accompanying buttons.
2026-02-13 08:11:31 +00:00
Mihail Geshoski fb680bb33d Merge branch 'MDL-87600-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-02-13 14:18:36 +08:00
Jun Pataleta 4c872ab444 Merge branch 'MDL-87946-405' of https://github.com/andrewnicols/moodle into MOODLE_405_STABLE 2026-02-13 11:59:31 +08:00
Huong Nguyen 7cbd576777 Merge branch 'MDL-77209-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-02-13 09:42:36 +07:00
Andrew Nicols 2c8cfb9e13 MDL-87946 github: Update GHA Database connections
Both the pgsql and mysql steps we were using use `docker:stable`, which
has been abandoned and is not a recommended approach. They are also both
abandoned.

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

We can't use the services entry for MySQL because GHA does not support
providing arguments to service containers.
2026-02-13 10:34:28 +08:00
Huong Nguyen 1bfe81f05d MDL-87482 core: Better text comparison 2026-02-13 09:31:03 +07:00
Andi Permana 2cf09ff8f3 MDL-87482 core_message: Fix compatibility with libxml2 >= 2.14.0 2026-02-13 09:17:47 +07:00
Huong Nguyen d9284eee0f MDL-85658 core_h5p: Implement contentLoaded event handler
This improvement will help to avoid random errors
because H5P content needs time to be rendered.
2026-02-13 08:55:10 +07:00
Huong Nguyen aa1723e748 MDL-85658 behat: Wait until the iframe is completely loaded 2026-02-13 08:55:10 +07:00
Huong Nguyen c079d2c3ea Merge branch 'MDL-87095-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2026-02-12 10:24:43 +07:00
Simey Lameze 39aa019a86 MDL-87095 behat: reorganize lesson question importing tests 2026-02-12 09:51:53 +08:00
Kim Jared Lucas 882235917f MDL-87095 mod_lesson: add behat test for importing questions into lesson 2026-02-12 09:51:53 +08:00
AMOS bot e12ffcdda0 Automatically generated installer lang files 2026-02-12 00:09:30 +00:00
Huong Nguyen b2c2f2a0c5 Moodle release 4.5.10 2026-02-11 08:44:13 +07:00
Huong Nguyen 7a7f029149 weekly release 4.5.9+ 2026-02-10 08:42:00 +07:00
Shamim Rezaie 6cfefde94b Merge branch 'MDL-87892-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2026-02-09 21:07:28 +11:00
Huong Nguyen be8d3d004f MDL-87892 admin: Fix MoodleNet check 2026-02-09 16:34:16 +07:00
Shamim Rezaie d42c5d9311 Merge branch 'MDL-87892-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2026-02-09 19:36:47 +11:00
Huong Nguyen 1c0e0f55bf MDL-87892 admin: Fix MoodleNet removal warning
Instead of introducing a new optional parameter,
we will make all the changes in the Admin Renderer
2026-02-09 09:05:49 +07:00
Paul Holden c5aebec657 MDL-87883 core: correct license page link in project readme. 2026-02-06 09:09:12 +00:00
Jun Pataleta 4e689e468e MDL-77209 core: Make More help links open on a new tab 2026-02-06 10:57:26 +08:00
Huong Nguyen 6b4d33a98f Moodle release 4.5.9 2026-02-06 09:16:09 +07:00
Huong Nguyen 506a4a33d4 NOBUG: Add upgrade notes 2026-02-06 09:16:09 +07:00
Mihail Geshoski afe3c2b88f weekly release 4.5.8+ 2026-02-05 20:24:01 +08:00
Mihail Geshoski 1a8415f1f1 NOBUG: Add upgrade notes 2026-02-05 20:24:01 +08:00
Mihail Geshoski caab968624 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-02-05 20:23:47 +08:00
Paul Holden f3748c1006 MDL-87870 admin: tighten up validation of colour picker values. 2026-02-05 17:27:27 +07:00
Paul Holden 72f49e4c76 MDL-87870 filter_tex: escape background argument content. 2026-02-05 17:27:27 +07:00
Huong Nguyen 4e8e5ca6ca Merge branch 'MDL-87352-405' of https://github.com/andimendunia/moodle into MOODLE_405_STABLE 2026-02-05 17:27:27 +07:00
yusufwib01 3cf9457a36 MDL-87594 composer: Upgrade symfony/process to v6.4.33 2026-02-05 17:27:27 +07:00
Mihail Geshoski 31d96fb54f Merge branch 'MDL-87258-405' of https://github.com/rjnl/moodle into MOODLE_405_STABLE 2026-02-05 17:27:27 +07:00
Jun Pataleta 43874f1138 Merge branch 'MDL-87757-405' of https://github.com/muhammadarnaldo/moodle into MOODLE_405_STABLE 2026-02-05 17:27:27 +07:00
Jun Pataleta 77e3a9f987 MDL-87600 gradereport_overview: Fix heading levels on the index page 2026-02-04 18:51:23 +08:00
Andi Permana e5269918b5 MDL-87352 admin: Add MoodleNet removal warnings 2026-02-04 11:30:54 +07:00
yusufwib01 857c90735e MDL-86785 tiny_equation: Add timeout handling for mimetex execution 2026-02-04 10:22:22 +07:00
Muhammad Arnaldo 1d95966c57 MDL-87757 block_myoverview: Remove navigation role and behat update
- Removal of the navigation role on main and zero-state template
- Add accessibility extra tests for the block_overview
2026-02-04 10:12:52 +07:00
Muhammad Arnaldo f9a2bb1125 MDL-87757 block_myoverview: fix the heading-order 2026-02-04 10:11:51 +07:00
Mihail Geshoski cba59e58ec Merge branch 'MDL-86437-405' of https://github.com/snake/moodle into MOODLE_405_STABLE 2026-02-04 10:38:41 +08:00
Huong Nguyen 6d92262ecb MDL-87843 filter_tex: Switch to use Colour picker setting
This will allow users to easily choose the background colour
and prevent invalid input values
2026-02-04 10:33:56 +08:00
Paul Holden f3227bee2e MDL-87612 repository: safer unserializing of file source data. 2026-02-04 09:24:47 +07:00
Mihail Geshoski b1ba8e672e Merge branch 'MDL-87789-405' of https://github.com/snake/moodle into MOODLE_405_STABLE 2026-02-04 10:19:17 +08:00
AMOS bot 1ecd11e72e Automatically generated installer lang files 2026-02-04 00:07:34 +00:00
Jake Dallimore 3e2dbf557b Merge branch 'MDL-85535-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-02-03 16:59:56 +08:00
Jun Pataleta 50c2251ef2 Merge branch 'MDL-77162-filepicker-a11y-tab-order-repository-keyboard-selection-M4.5' of https://github.com/stopfstedt/moodle into MOODLE_405_STABLE 2026-02-03 16:44:00 +08:00
Jake Dallimore 4dce7cdcd0 Merge branch 'MDL-87638-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-02-03 16:18:29 +08:00
Rajneel Totaram 1241a17d4b MDL-87258 mod_lti: request access for PNA
Since Chrome 142, public servers accessing resources on local networks
gets blocked by default. Users need to allow access for such requests
to proceed.

This patch introduces a new config setting `ltiallowlocalnetwork`.
This determines if the local-network-access flag should be added to LTI
iframes so that Chrome users get prompted when a local network access
is being made.
2026-02-03 19:37:09 +12:00
Jun Pataleta d3ec08f146 Merge branch 'MDL-87694-course-participant-profile-missing-heading-M4.5' of https://github.com/stopfstedt/moodle into MOODLE_405_STABLE 2026-02-03 15:18:03 +08:00
Huong Nguyen c2bd37c0ec Merge branch 'MDL-86905-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2026-02-03 10:27:16 +07:00
Huong Nguyen 417c399bf6 Merge branch 'MDL-87675-405' of https://github.com/nvhai248/moodle into MOODLE_405_STABLE 2026-02-03 10:23:52 +07:00
AMOS bot ffb5bfee38 Automatically generated installer lang files 2026-02-03 00:07:48 +00:00
Stefan Topfstedt 6f84540075 MDL-87694 core: prevent empty heading in context header. 2026-02-02 11:34:20 -08:00
Stefan Topfstedt c6d921e53a MDL-87694 profile: set page title to "Login required".
This realigns the page title with user/view.php.
2026-02-02 11:05:28 -08:00
Stefan Topfstedt 890849b3e1 MDL-87694 profile: set top level heading when accessed as guest user. 2026-02-02 10:15:08 -08:00
Jun Pataleta f3b0bb1896 MDL-87694 core: headinglevel display option for $OUTPUT->confirm()
`\core\output\core_renderer::confirm()`'s `$displayoptions` parameter
now also accepts a `headinglevel` option that developers can use to
specify the heading level of the confirmation's heading. If not
specified, the confirmation heading will be rendered in an `h4` tag.
2026-02-02 09:24:53 -08:00
Jun Pataleta a528f9ca2e MDL-86905 core: Use proper link markup for email links 2026-02-02 18:06:21 +08:00
AMOS bot 1768ae93e8 Automatically generated installer lang files 2026-01-31 00:07:36 +00:00
Huong Nguyen aa24806a89 weekly release 4.5.8+ 2026-01-30 15:39:52 +07:00
Huong Nguyen 156d7a5845 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-01-30 15:39:42 +07:00
fgo 6bf10930c7 MDL-86437 enrol_lti: Cast scoreGiven to float for LTI compliance
scoreGiven was being passed as a string (e.g. "8.0"), which caused
validation errors in some LMS implementations such as Rustici Engine.
According to the LTI specification, scoreGiven must be numeric if
present.

Co-authored-by: Jake Dallimore <jake@moodle.com>
2026-01-29 15:00:09 +08:00
Jake Dallimore adfde92245 MDL-87789 libraries: patch lti1p3 for certification support
https://github.com/packbackbooks/lti-1-3-php-library/issues/169 blocks
us from using the new build.1edtech certification tooling. This issue
commits that fix as a library customisation/hack for stables, so the
suite can be used for annual/bi-annual re-certification.
2026-01-29 13:29:59 +08:00
Huong Nguyen 641639c0a7 Merge branch 'MDL-87521_405' of https://github.com/timhunt/moodle into MOODLE_405_STABLE 2026-01-29 11:03:40 +07:00
Huong Nguyen f4254a24ad Merge branch 'MDL-87087-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2026-01-29 11:01:46 +07:00
Huong Nguyen cbeefb6854 Merge branch 'MDL-86994-405' of https://github.com/mioyu-ubiq/moodle into MOODLE_405_STABLE 2026-01-29 10:58:35 +07:00
Huong Nguyen 5567567e5b Merge branch 'MDL-87018-405' of https://github.com/bhect0/moodle into MOODLE_405_STABLE 2026-01-29 10:55:18 +07:00
Angelia Dela Cruz ec6551ce80 MDL-87087 tool_dataprivacy: Test data request by authorized user 2026-01-29 11:09:48 +08:00
AMOS bot 9118477079 Automatically generated installer lang files 2026-01-29 00:07:37 +00:00
Tim Hunt 53f41838e7 MDL-87521 behat: better error reporting if an adhoc task fails 2026-01-28 14:59:13 +00:00
Jun Pataleta 1b56df014c Merge branch 'MDL-86639-405' of https://github.com/yusufwib01/moodle into MOODLE_405_STABLE 2026-01-28 16:17:13 +08:00
mioyu-ubiq 2edd3e1155 MDL-86994 mod_assign: Behat to test permitted users can edit submission 2026-01-28 14:23:55 +08:00
Hai Nguyen Van a68bc9319a MDL-87675 core: Add delay for displaying the YUI alert dialogue message
* Add a slight delay for displaying the message content of the YUI
alert dialogue's ARIA live container to allow screen readers to
announce the alert message accordingly.
2026-01-28 03:34:01 +00:00
Paul Holden 23a0e2a9a4 MDL-87100 core: infer upgrade note issue number from current branch. 2026-01-27 10:55:10 +00:00
Paul Holden d88cbc344d MDL-87710 behat: move upgrade notes into the correct location.
They were put in the wrong location by MDL-87443 (my bad).
2026-01-27 10:47:09 +00:00
Huong Nguyen e4c595aac8 weekly release 4.5.8+ 2026-01-26 10:39:32 +07:00
Huong Nguyen d3eaf24089 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-01-26 10:39:22 +07:00
Héctor Benedicte 39012d0b09 MDL-87018 qtype_essay: Add unit tests for un_summarise_response(). 2026-01-23 19:20:42 +01:00
AMOS bot 3dcfe6520b Automatically generated installer lang files 2026-01-23 00:07:54 +00:00
Safat d4c569d95e Merge branch 'MDL-86527-405' of https://github.com/HuongNV13/moodle into MOODLE_405_STABLE 2026-01-22 15:40:16 +11:00
Huong Nguyen 35ad89a715 Merge branch 'MDL-87092-405' of https://github.com/Kredelirious/moodle into MOODLE_405_STABLE 2026-01-22 10:37:12 +07:00
Huong Nguyen f557352f22 Merge branch 'MDL-87090-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2026-01-22 10:33:01 +07:00
Muhammad Arnaldo 894f2f161e MDL-86527 behat: Avoid element-click-intercepted failures
Adjusts the window size to accommodate wide tables.
This ensures that columns which were previously clipped
are now visible and accessible to the Behat driver.
2026-01-21 15:05:19 +07:00
Muhammad Arnaldo f6c8f4a098 MDL-86527 behat: Fix random failures when toggling edit mode 2026-01-21 15:05:19 +07:00
AMOS bot 93045eb621 Automatically generated installer lang files 2026-01-21 00:08:01 +00:00
Yusuf Wibisono 5bff1a62b1 MDL-86639 groups: Wrap group names in grade report group selector 2026-01-21 03:09:49 +07:00
Kim Jared Lucas e195cb3905 MDL-87092 tool_uploaduser: suspend enrolments via CSV upload 2026-01-20 11:21:27 +08:00
AMOS bot 3c3f130d2c Automatically generated installer lang files 2026-01-20 00:08:02 +00:00
Simey Lameze 99809ba681 MDL-87090 behat: improve new test 2026-01-19 14:14:01 +08:00
mioyu-ubiq 9c9b31658d MDL-87090 mod_assign : Teacher can override assignment deadlines 2026-01-19 14:14:01 +08:00
Jake Dallimore 23ad73ee56 weekly release 4.5.8+ 2026-01-16 16:38:38 +08:00
Jake Dallimore 9ce4d3a2de Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2026-01-16 16:38:32 +08:00
Jake Dallimore b17cba4ef7 Merge branch 'MDL-87634-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2026-01-15 09:58:59 +08:00
Paul Holden f0c84172fb MDL-87634 core: exclude fixtures from Github actions "one-by-one" job.
This is consistent with the PHPUnit general configuration in 20aa9d05.
2026-01-14 09:25:36 +00:00
Huong Nguyen c9913935bf Merge branch 'MDL-87443-405' of https://github.com/paulholden/moodle into MOODLE_405_STABLE 2026-01-13 10:15:59 +07:00
AMOS bot 72fb26ef25 Automatically generated installer lang files 2026-01-13 00:07:57 +00:00
Jun Pataleta 8e40b622bd MDL-87638 feedback: Fix usage of sortable list
The sortable list module in 4.5 still depends on jQuery instead of
native events. We need to adjust the usage of sortable list for the
edit questions page of the feedback activity accordingly.
2026-01-12 17:24:44 +08:00
Jun Pataleta f009e8e887 MDL-85535 forum: Render discussion navigation at the bottom only 2026-01-12 15:04:46 +08:00
Jun Pataleta e46b6a53ed MDL-85535 forum: Label discussion navigation 2026-01-12 15:04:46 +08:00
Simey Lameze 1646ab8620 MDL-87088 behat: add missing @javascript tags to required tests 2026-01-12 13:38:03 +07:00
Huong Nguyen 4290c0db02 Merge branch 'MDL-87088-405' of https://github.com/andelacruz/moodle into MOODLE_405_STABLE 2026-01-12 09:53:30 +07:00
Stefan Topfstedt 61dc480cfa MDL-77162 filepicker: default to selecting first repository available. 2026-01-09 08:05:18 -08:00
Angelia Dela Cruz c13c75342f MDL-87088 core_grades: Behat to view grades from user menu 2026-01-09 10:52:25 +08:00
Huong Nguyen e394149084 weekly release 4.5.8+ 2026-01-09 08:41:57 +07:00
Paul Holden b275637a13 Merge branch 'MDL-87288-405' of https://github.com/ilyatregubov/moodle into MOODLE_405_STABLE 2026-01-07 08:55:54 +00:00
Ilya Tregubov 771e06bb2f MDL-87288 output: Fix pagination in small containers. 2026-01-07 10:19:14 +02:00
Jun Pataleta e3a4f45aa7 Merge branch 'MDL-86436-navigation-accessibility-page-heading-menu-visible-on-larger-screens-even-if-empty-M4.5' of https://github.com/stopfstedt/moodle into MOODLE_405_STABLE 2026-01-07 11:18:01 +08:00
Jun Pataleta 794b06cf63 Merge branch 'MDL-87416-glossary-entry-page-title-missing-M4.5' of https://github.com/stopfstedt/moodle into MOODLE_405_STABLE 2026-01-07 11:13:48 +08:00
Stefan Topfstedt f833fba135 MDL-87416 glossary: set page title on glossary entry details page. 2026-01-05 11:32:57 -08:00
Huong Nguyen 8fec0dc9e6 Merge branch 'MDL-81882-405' of https://github.com/yusufwib01/moodle into MOODLE_405_STABLE 2025-12-29 13:51:56 +07:00
Huong Nguyen df9acfa2ec Merge branch 'MDL-87433-405' of https://github.com/muhammadarnaldo/moodle into MOODLE_405_STABLE 2025-12-29 09:54:51 +07:00
yusufwib01 52ef39651d MDL-81882 core: Optimize block instance deletion with batch processing
Improve upgrade execution by gathering block instance IDs upfront
and processing deletions of related records in context, block_positions, user_preferences, and block_instances tables in batches.
2025-12-29 09:33:29 +07:00
Muhammad Arnaldo c03ad2f40d MDL-87433 course: Add sorting to get_nearest_editable_subcategory
Add ORDER BY to the query to ensure categories are returned in
consistent order. This fixes random test failures, especially
on PostgreSQL.
2025-12-24 16:38:25 +07:00
Huong Nguyen 9e2daa9d6c weekly release 4.5.8+ 2025-12-19 09:05:00 +07:00
Huong Nguyen 6eb5c80e75 Merge branch 'MDL-87340-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2025-12-18 10:22:56 +07:00
Jun Pataleta d807b51210 Merge branch 'MDL-87428-405' of https://github.com/muhammadarnaldo/moodle into MOODLE_405_STABLE 2025-12-18 10:53:50 +08:00
Huong Nguyen 6880fa0c12 Merge branch 'MDL-86420-405' of https://github.com/muhammadarnaldo/moodle into MOODLE_405_STABLE 2025-12-18 09:30:09 +07:00
Huong Nguyen 31c83f8aa5 Merge branch 'MDL-87094-405' of https://github.com/andelacruz/moodle into MOODLE_405_STABLE 2025-12-17 14:15:33 +07:00
Mihail Geshoski 1ef32165a0 Merge branch 'MDL-86416-405' of https://github.com/junpataleta/moodle into MOODLE_405_STABLE 2025-12-17 12:15:40 +08:00
Jun Pataleta ede9478779 Merge branch 'MDL-87341_405' of https://github.com/TomoTsuyuki/moodle into MOODLE_405_STABLE 2025-12-15 18:48:29 +08:00
Angelia Dela Cruz c083984fd0 MDL-87094 core_course: Behat to restrict available activities/resources 2025-12-15 12:07:28 +08:00
Tomo Tsuyuki 7049a8334c MDL-87341 javascript: Fix aria label issue for navigation bar 2025-12-15 10:37:52 +11:00
Stefan Topfstedt 2025178343 MDL-86436 navigation: hide empty page heading menu.
Co-authored-by: Jun Pataleta <jun@moodle.com>
2025-12-12 18:04:44 -08:00
Stefan Topfstedt 0620c07639 MDL-87415 profile: set page title when accessed as guest user. 2025-12-12 13:44:02 -08:00
Paul Holden 39096ac3d1 MDL-87443 reportbuilder: use Toast selector in Behat scenarios. 2025-12-12 10:30:18 +00:00
Paul Holden d404adf500 MDL-87443 behat: define new Toast message named selector. 2025-12-12 10:12:57 +00:00
Muhammad Arnaldo dd9b7e8deb MDL-87428 core_course: Fix RTL keyboard navigation
Ensure consistent navigation experience across LTR and
RTL interfaces in activity chooser.
2025-12-12 11:01:54 +07:00
Shamim Rezaie b357491d00 weekly release 4.5.8+ 2025-12-12 14:06:51 +11:00
Shamim Rezaie 6139a04b64 Merge branch 'install_405_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_405_STABLE 2025-12-12 14:06:41 +11:00
Muhammad Arnaldo 9ebe2f8676 MDL-86420 enrol_database: avoid duplicate key violations 2025-12-11 10:25:31 +07:00
Huong Nguyen a2aaba5f5d Merge branch 'MDL-84736-405' of https://github.com/lameze/moodle into MOODLE_405_STABLE 2025-12-11 10:19:30 +07:00
AMOS bot 4112aa8957 Automatically generated installer lang files 2025-12-11 00:07:58 +00:00
Simey Lameze 39026940b3 MDL-84736 behat: add custom step to edit a criterion
This commit also improves the existing tests and add a new scenario to
verify the 'Do not mark for regrade' behavior.
2025-12-09 17:23:58 +08:00
Angelia Dela Cruz 0e0bdbdd76 MDL-84736 gradingform : Behat for editing guide with graded submissions 2025-12-09 17:23:58 +08:00
Jun Pataleta 5a40f5524e Merge branch 'MDL-86807-405' of https://github.com/muhammadarnaldo/moodle into MOODLE_405_STABLE 2025-12-09 16:43:04 +08:00
Jun Pataleta 806b3f2d69 Merge branch 'MDL-86805-405' of https://github.com/andimendunia/moodle into MOODLE_405_STABLE 2025-12-09 16:36:07 +08:00
Muhammad Arnaldo 3d812fe6c3 MDL-86807 core_courseformat: RTL chevron in move activity modal 2025-12-09 15:23:21 +07:00
Andi Permana 367a2a7678 MDL-86805 courseindex: Fix RTL keyboard navigation in tree 2025-12-09 15:20:23 +07:00
Stefan Topfstedt 70ba060e57 MDL-86416 navigation: Render the more menu only when it's not empty
Co-authored-by: Jun Pataleta <jun@moodle.com>
2025-12-09 11:16:37 +08:00
Mihail Geshoski f5fc38f35a Merge branch 'MDL-82281-405' of https://github.com/lucaboesch/moodle into MOODLE_405_STABLE 2025-12-09 09:46:35 +08:00
Mihail Geshoski dcd55b53a7 Merge branch 'mdl-87356_405' of https://github.com/james-cnz/moodle into MOODLE_405_STABLE 2025-12-09 09:18:17 +08:00
Simey Lameze a45cfb7c7b MDL-87340 mod_quiz: make test_question_shuffle more robust
This commit improves test_question_shuffle by retrying if questions
remains in the same order after shuffling.
2025-12-09 07:34:19 +08:00
Luca Bösch c300545e9e MDL-82281 quiz: Amend test to circumvent modinfo cache fails. 2025-12-07 19:54:07 +01:00
James C 66a22969e1 MDL-87356 qtype_random: orphaned random questions should be deleted
This patch reinstates the task from MDL-63260 and MDL-66273.
2025-12-04 22:58:52 +13:00
232 changed files with 4047 additions and 1024 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
chunk=$(((($count % $chunks)) + 1))
echo "$testname $testfile" >> ./chunk_$chunk.txt
done < <(grep "function test_" "${testfile}" | sed -r "s/^.*function (test_[a-zA-Z0-9_]+).*/\1/")
done < <(find . -name "*_test.php")
done < <(find . -name "*_test.php" -not -path "*/fixtures/*")
# Generate the matrix to run tests.
echo "matrix=$(ls -1 chunk_*.txt | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
echo "$count individual tests collected in $chunks files"
+21 -20
View File
@@ -56,6 +56,14 @@ jobs:
image: redis
ports:
- 6379:6379
postgres:
image: ${{ matrix.db == 'pgsql' && 'postgres:13' || '' }}
env:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
strategy:
fail-fast: false
matrix:
@@ -71,27 +79,20 @@ jobs:
db: pgsql
steps:
- name: Setting up DB mysql
- name: Run MySQL Server
if: ${{ matrix.db == 'mysqli' }}
uses: moodlehq/mysql-action@v1
with:
collation server: utf8mb4_bin
mysql version: 8.0
mysql database: test
mysql user: test
mysql password: test
use tmpfs: true
tmpfs size: '1024M'
extra conf: --skip-log-bin
- name: Setting up DB pgsql
if: ${{ matrix.db == 'pgsql' }}
uses: m4nu56/postgresql-action@v1
with:
postgresql version: 13
postgresql db: test
postgresql user: test
postgresql password: test
run: |
docker run --rm \
-e MYSQL_DATABASE=test \
-e MYSQL_USER=test \
-e MYSQL_PASSWORD=test \
-e MYSQL_ROOT_PASSWORD=test \
-p 3306:3306 \
-d \
--tmpfs /var/lib/mysql:rw,noexec,nosuid,size=1024M \
mysql:8.0 \
--skip-log-bin \
--collation-server=utf8mb4_bin
- name: Configuring git vars
uses: rlespinasse/github-slug-action@v4
+2 -2
View File
@@ -18,7 +18,7 @@ import inquirer from 'inquirer';
import chalk from 'chalk';
import { createNote } from './note.mjs';
import { getInitialValues } from './helpers.mjs';
import { getInitialValues, getIssueNumberFromBranchName } from './helpers.mjs';
import * as Prompts from './prompts.mjs';
import logger from './logger.mjs';
@@ -29,7 +29,7 @@ export default async (options) => {
// Fetch information.
const messages = [];
const { issueNumber } = await inquirer.prompt([
Prompts.getIssuePrompt(),
Prompts.getIssuePrompt(getIssueNumberFromBranchName()),
], initialValues);
let selection = {};
+14
View File
@@ -14,6 +14,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
import chalk from 'chalk';
import branchName from 'current-git-branch';
import { isStandardComponent, isCommunityComponent, rewritePlugintypeAsSubsystem } from './components.mjs';
import { isValidNoteName } from './noteTypes.mjs';
import logger from './logger.mjs';
@@ -59,6 +60,19 @@ export const formatIssueNumber = (input) => {
return input;
};
/**
* Get the issue number, based off current git branch name
*
* @returns {string|null}
*/
export const getIssueNumberFromBranchName = () => {
const issueNumber = String(branchName()).match(/^[a-zA-Z]*-\d+/);
if (issueNumber) {
return formatIssueNumber(issueNumber[0]);
}
return null;
};
/**
* Validate a component.
*
@@ -0,0 +1,8 @@
issueNumber: MDL-75067
notes:
tool_behat:
- message: >-
The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods
now accept optional `$container` parameter to define the parent node to
look within
type: improved
@@ -0,0 +1,7 @@
issueNumber: MDL-87100
notes:
core:
- message: >-
When creating upgrade notes, the issue number will be inferred from the
current Git branch name by default
type: improved
@@ -0,0 +1,7 @@
issueNumber: MDL-87443
notes:
core:
- message: >-
There is a new Behat `toast_message` named selector to more easily
assert the presence of Toast messages on the page
type: improved
@@ -0,0 +1,8 @@
issueNumber: MDL-87993
notes:
core:
- message: >-
The `core/toast` JS module now accepts a `visuallyHidden` configuration
parameter to render visually hidden toast messages for screen reader
users.
type: improved
+1 -1
View File
@@ -32,7 +32,7 @@ Moodle also offers hosting through both [MoodleCloud][11], and our [partner netw
## License
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. For more information on our license see
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. See our [license page][12] for more information.
[1]: https://moodle.org
[2]: https://moodle.com
+34
View File
@@ -6,6 +6,40 @@ 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.10+
### core
#### Added
- When creating upgrade notes, the issue number will be inferred from the current Git branch name by default
For more information see [MDL-87100](https://tracker.moodle.org/browse/MDL-87100)
- There is a new Behat `toast_message` named selector to more easily assert the presence of Toast messages on the page
For more information see [MDL-87443](https://tracker.moodle.org/browse/MDL-87443)
- The `core/toast` JS module now accepts a `visuallyHidden` configuration parameter to render visually hidden toast messages for screen reader users.
For more information see [MDL-87993](https://tracker.moodle.org/browse/MDL-87993)
### tool_behat
#### Added
- The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods now accept optional `$container` parameter to define the parent node to look within
For more information see [MDL-75067](https://tracker.moodle.org/browse/MDL-75067)
## 4.5.9
### core
#### Changed
- `\core\output\core_renderer::confirm()`'s `$displayoptions` parameter now also accepts a `headinglevel` option that developers can use to specify the heading level of the confirmation's heading. If not specified, the confirmation heading will be rendered in an `h4` tag.
For more information see [MDL-87694](https://tracker.moodle.org/browse/MDL-87694)
## 4.5.8
### core
+24 -6
View File
@@ -941,9 +941,27 @@ admin_externalpage_setup('adminnotifications');
$output = $PAGE->get_renderer('core', 'admin');
echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed, $cronoverdue, $dbproblems,
$maintenancemode, $availableupdates, $availableupdatesfetch, $buggyiconvnomb,
$registered, $cachewarnings, $eventshandlers, $themedesignermode, $devlibdir,
$mobileconfigured, $overridetossl, $invalidforgottenpasswordurl, $croninfrequent,
$showcampaigncontent, $showfeedbackencouragement, $servicesandsupportcontent,
$xmlrpcwarning);
echo $output->admin_notifications_page(
$maturity,
$insecuredataroot,
$errorsdisplayed,
$cronoverdue,
$dbproblems,
$maintenancemode,
$availableupdates,
$availableupdatesfetch,
$buggyiconvnomb,
$registered,
$cachewarnings,
$eventshandlers,
$themedesignermode,
$devlibdir,
$mobileconfigured,
$overridetossl,
$invalidforgottenpasswordurl,
$croninfrequent,
$showcampaigncontent,
$showfeedbackencouragement,
$servicesandsupportcontent,
$xmlrpcwarning
);
+53 -7
View File
@@ -282,13 +282,30 @@ class core_admin_renderer extends plugin_renderer_base {
*
* @return string HTML to output.
*/
public function admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
$cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch,
$buggyiconvnomb, $registered, array $cachewarnings = array(), $eventshandlers = 0,
$themedesignermode = false, $devlibdir = false, $mobileconfigured = false,
$overridetossl = false, $invalidforgottenpasswordurl = false, $croninfrequent = false,
$showcampaigncontent = false, bool $showfeedbackencouragement = false, bool $showservicesandsupport = false,
$xmlrpcwarning = '') {
public function admin_notifications_page(
$maturity,
$insecuredataroot,
$errorsdisplayed,
$cronoverdue,
$dbproblems,
$maintenancemode,
$availableupdates,
$availableupdatesfetch,
$buggyiconvnomb,
$registered,
array $cachewarnings = [],
$eventshandlers = 0,
$themedesignermode = false,
$devlibdir = false,
$mobileconfigured = false,
$overridetossl = false,
$invalidforgottenpasswordurl = false,
$croninfrequent = false,
$showcampaigncontent = false,
bool $showfeedbackencouragement = false,
bool $showservicesandsupport = false,
$xmlrpcwarning = ''
) {
global $CFG;
$output = '';
@@ -314,6 +331,8 @@ class core_admin_renderer extends plugin_renderer_base {
$output .= $this->mobile_configuration_warning($mobileconfigured);
$output .= $this->forgotten_password_url_warning($invalidforgottenpasswordurl);
$output .= $this->mnet_deprecation_warning($xmlrpcwarning);
$output .= $this->moodlenet_removal_warning();
$output .= $this->marketplace_integration_notice();
$output .= $this->userfeedback_encouragement($showfeedbackencouragement);
$output .= $this->services_and_support_content($showservicesandsupport);
$output .= $this->campaign_content($showcampaigncontent);
@@ -2267,6 +2286,33 @@ class core_admin_renderer extends plugin_renderer_base {
return $this->warning($xmlrpcwarning);
}
/**
* Display a warning about the removal of MoodleNet integration.
*
* @return string HTML to output.
*/
protected function moodlenet_removal_warning(): string {
$moodlenetenabled = get_config('tool_moodlenet', 'enablemoodlenet');
if (!empty($moodlenetenabled)) {
$moodlenetwarning = get_string('moodlenetremovalwarning', 'admin');
return $this->warning($moodlenetwarning);
}
return '';
}
/**
* Display a notice about Moodle Marketplace integration.
*
* @return string HTML to output.
*/
protected function marketplace_integration_notice(): string {
$installer = tool_installaddon_installer::instance();
$url = $installer->get_marketplace_url();
$notice = get_string('marketplaceavailablenotice', 'admin', $url->out());
return $this->warning($notice, 'info');
}
/**
* Renders the theme selector list.
*
+8
View File
@@ -1,5 +1,13 @@
# tool_behat Upgrade notes
## 4.5.10+
### Added
- The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods now accept optional `$container` parameter to define the parent node to look within
For more information see [MDL-75067](https://tracker.moodle.org/browse/MDL-75067)
## 4.5.2
### Added
+5 -1
View File
@@ -224,6 +224,11 @@ if (empty($parallelrun)) {
$cmds['singlerun'] = $runtestscommand;
echo "Running single behat site:" . PHP_EOL;
// The inner PHP process is not marked as having an interactive terminal even if it's passed
// through from this one which does, so we need to pass it through as an environment variable.
if (function_exists('posix_isatty') && posix_isatty(STDOUT)) {
putenv('MOODLE_BEHAT_RUNNING_IN_TTY=1');
}
passthru("php $runtestscommand", $status);
$exitcodes['singlerun'] = $status;
chdir($cwd);
@@ -525,4 +530,3 @@ function get_status_lines_from_run_op(Symfony\Component\Process\Process $process
return $statusstr;
}
@@ -0,0 +1,100 @@
@tool @tool_dataprivacy
Feature: Authorized users can request others personal data
In order to export or access another users data
As a designated role
I need the correct permissions
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User1 | One | user1@example.com |
| user2 | User2 | Two | user2@example.com |
| officer1 | Officer1 | One | officer1@example.com |
# Create Privacy Officer Role.
And the following "role" exists:
| shortname | privacyofficer |
| name | Privacy Officer |
| context_system | 1 |
| tool/dataprivacy:managedataregistry | allow |
| tool/dataprivacy:managedatarequests | allow |
| tool/dataprivacy:makedatarequestsforchildren | allow |
| moodle/site:configview | allow |
| moodle/category:viewhiddencategories | allow |
| moodle/course:viewhiddencourses | allow |
| moodle/course:viewhiddenactivities | allow |
| moodle/course:view | allow |
# Create Parent Role.
And the following "role" exists:
| shortname | parentrole |
| name | Parent Role |
| context_user | 1 |
| moodle/user:viewdetails | allow |
| moodle/user:viewalldetails | allow |
| moodle/user:readuserblogs | allow |
| moodle/user:readuserposts | allow |
| moodle/user:viewuseractivitiesreport | allow |
| moodle/user:editprofile | allow |
| tool/policy:acceptbehalf | allow |
| tool/dataprivacy:makedatarequestsforchildren | allow |
# Add permission to allow parent to make requests on behalf of child user.
And the following config values are set as admin:
| contactdataprotectionofficer | 1 | tool_dataprivacy |
And I log in as "admin"
@javascript
Scenario: Privacy officer can request for other user's personal data
Given I navigate to "Users > Permissions > Assign system roles" in site administration
# Assign Privacy Officer role to officer1.
And I follow "Privacy Officer"
And I set the field "addselect_searchtext" to "Officer1"
And I set the field "addselect" to "Officer1 One (officer1@example.com)"
And I press "Add"
# Navigate to home in order to navigate properly to Privacy settings.
And I am on site homepage
# Select Privacy officer in the Orivacy officer role mapping setting.
And I navigate to "Users > Privacy and policies > Privacy settings" in site administration
And I click on "Privacy Officer" "checkbox"
And I press "Save changes"
And I log in as "officer1"
And I navigate to "Users > Privacy and policies > Data requests" in site administration
# Create a new request as the designated privacy officer.
When I follow "New request"
And I set the field "User" to "User1 One"
And I set the field "Comment" to "User One data"
And I press "Save changes"
# Confirm that the new data request is successfully created for selected user with status "Awaiting approval".
Then the following should exist in the "generaltable" table:
| Type | User | Requested by | Status | Message |
| Export | User1 One | Officer1 One | Awaiting approval | User One data |
@javascript
Scenario: Parent user can request data on behalf of child user
Given I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "User1 One"
And I click on "Preferences" "link" in the ".profile_tree" "css_element"
# Assign user2 as parent for user1.
And I follow "Assign roles relative to this user"
And I follow "Parent"
And I set the field "Potential users" to "User2 Two (user2@example.com)"
And I click on "Add" "button" in the "#page-content" "css_element"
And I log in as "user2"
And I follow "Profile" in the user menu
And I follow "Data requests"
# As parent, create a data request for a child user.
And I follow "New request"
And I click on "User" "field"
When I type "User1 One"
# Confirm that only the parent's child users can be searched and selected.
Then I should see "User1 One"
And I type "User2 Two"
And I should see "No suggestions"
And I type "Officer1 One"
And I should see "No suggestions"
And I set the field "Search" to "User1"
And I set the field "Comment" to "This is a comment"
And I press "Save changes"
# Confirm that data request was successfully made by parent on behalf of child user.
And I should see "Your request has been submitted to the privacy officer"
And the following should exist in the "generaltable" table:
| Type | Requested by | Status | Message |
| Export all of my personal data (User1 One) | User2 Two | Awaiting approval | This is a comment |
+10
View File
@@ -0,0 +1,10 @@
define("tool_installaddon/footer",["exports","jquery","core_course/local/activitychooser/selectors"],(function(_exports,_jquery,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Activity chooser footer handlers for tool_installaddon.
*
* @module tool_installaddon/footer
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.footerClickListener=void 0,_jquery=_interopRequireDefault(_jquery),_selectors=_interopRequireDefault(_selectors);_exports.footerClickListener=(e,footerData,modal)=>{const closeOption=e.target.closest(_selectors.default.actions.closeOption);if(!closeOption)return;const moduleName=closeOption.dataset.modname;if(!moduleName)return;const carousel=(0,_jquery.default)(modal.getBody()[0].querySelector(_selectors.default.regions.carousel));carousel.carousel("prev"),modal.setFooter(footerData.customfootertemplate),carousel.one("slid.bs.carousel",(()=>{const caller=modal.getBody()[0].querySelector(_selectors.default.regions.modules).querySelector(_selectors.default.regions.getModuleSelector(moduleName));caller&&caller.focus()}))}}));
//# sourceMappingURL=footer.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"footer.min.js","sources":["../src/footer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Activity chooser footer handlers for tool_installaddon.\n *\n * @module tool_installaddon/footer\n * @copyright 2026 Safat Shahin <safat.shahin@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport selectors from 'core_course/local/activitychooser/selectors';\n\n/**\n * Handle clicks in the chooser footer when installaddon is the active footer plugin.\n *\n * @param {Event} e The event being triggered\n * @param {Object} footerData The footer data generated for the chooser\n * @param {Object} modal The chooser modal\n */\nexport const footerClickListener = (e, footerData, modal) => {\n const closeOption = e.target.closest(selectors.actions.closeOption);\n\n if (!closeOption) {\n return;\n }\n\n const moduleName = closeOption.dataset.modname;\n\n if (!moduleName) {\n return;\n }\n\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n modal.setFooter(footerData.customfootertemplate);\n carousel.one('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(moduleName));\n if (caller) {\n caller.focus();\n }\n });\n};\n"],"names":["e","footerData","modal","closeOption","target","closest","selectors","actions","moduleName","dataset","modname","carousel","getBody","querySelector","regions","setFooter","customfootertemplate","one","caller","modules","getModuleSelector","focus"],"mappings":";;;;;;;oNAiCmC,CAACA,EAAGC,WAAYC,eACzCC,YAAcH,EAAEI,OAAOC,QAAQC,mBAAUC,QAAQJ,iBAElDA,yBAICK,WAAaL,YAAYM,QAAQC,YAElCF,wBAICG,UAAW,mBAAET,MAAMU,UAAU,GAAGC,cAAcP,mBAAUQ,QAAQH,WAGtEA,SAASA,SAAS,QAClBT,MAAMa,UAAUd,WAAWe,sBAC3BL,SAASM,IAAI,oBAAoB,WAEvBC,OADahB,MAAMU,UAAU,GAAGC,cAAcP,mBAAUQ,QAAQK,SAC5CN,cAAcP,mBAAUQ,QAAQM,kBAAkBZ,aACxEU,QACAA,OAAOG"}
+59
View File
@@ -0,0 +1,59 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Activity chooser footer handlers for tool_installaddon.
*
* @module tool_installaddon/footer
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import selectors from 'core_course/local/activitychooser/selectors';
/**
* Handle clicks in the chooser footer when installaddon is the active footer plugin.
*
* @param {Event} e The event being triggered
* @param {Object} footerData The footer data generated for the chooser
* @param {Object} modal The chooser modal
*/
export const footerClickListener = (e, footerData, modal) => {
const closeOption = e.target.closest(selectors.actions.closeOption);
if (!closeOption) {
return;
}
const moduleName = closeOption.dataset.modname;
if (!moduleName) {
return;
}
const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));
// Trigger the transition between 'pages'.
carousel.carousel('prev');
modal.setFooter(footerData.customfootertemplate);
carousel.one('slid.bs.carousel', () => {
const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);
const caller = allModules.querySelector(selectors.regions.getModuleSelector(moduleName));
if (caller) {
caller.focus();
}
});
};
@@ -70,6 +70,25 @@ class tool_installaddon_installer {
$url = 'https://moodle.org/plugins/get.php';
}
return $this->get_external_service_url($url);
}
/**
* Returns URL to Moodle Marketplace.
*
* @return moodle_url
*/
public function get_marketplace_url(): moodle_url {
return $this->get_external_service_url('https://marketplace.moodle.com/');
}
/**
* Returns URL to external service with optional encoded site information.
*
* @param string $url The external service base URL.
* @return moodle_url
*/
protected function get_external_service_url(string $url): moodle_url {
if (!$this->should_send_site_info()) {
return new moodle_url($url);
}
@@ -16,20 +16,23 @@
declare(strict_types=1);
namespace tool_moodlenet\task;
namespace tool_installaddon\task;
/**
* Ad-hoc task to perform post install tasks.
* We use this to set the active activity chooser footer plugin to tool_moodlenet.
* We use this to set the active activity chooser footer plugin to tool_installaddon.
* We couldn't do this directly in install.php, because there is an admin_apply_default_settings() call after all plugins are
* installed and that would reset whatever value we had set earlier to 'hidden'.
*
* @package tool_moodlenet
* @copyright 2022 Shamim Rezaie <shamim@moodle.com>
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_install extends \core\task\adhoc_task {
public function execute() {
set_config('activitychooseractivefooter', 'tool_moodlenet');
/**
* Sets the active activity chooser footer plugin after install defaults are applied.
*/
public function execute(): void {
set_config('activitychooseractivefooter', 'tool_installaddon');
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Install script for tool_installaddon.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Perform the post-install procedures.
*/
function xmldb_tool_installaddon_install(): void {
// Use an ad-hoc task to set the active activity chooser footer plugin to tool_installaddon.
// We couldn't do this in admin/settings/courses.php for 2 reasons:
// - First, because it would be a breach of component communications principles to do so there.
// - Second, because we can't call get_plugins_with_function() during install and upgrade (or it will return []).
// We couldn't do this directly here either, because there is an admin_apply_default_settings() call after all plugins are
// installed and that would reset whatever value we set here to 'hidden'.
$postinstall = new tool_installaddon\task\post_install();
core\task\manager::queue_adhoc_task($postinstall);
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Upgrade script for tool_installaddon.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Upgrade the plugin.
*
* @param int $oldversion
* @return bool always true
*/
function xmldb_tool_installaddon_upgrade(int $oldversion): bool {
if ($oldversion < 2024100701) {
// Set the activity chooser active footer to include marketplace regardless of the previous setting.
// We are deliberately setting this to increase awareness of marketplace.
set_config('activitychooseractivefooter', 'tool_installaddon');
upgrade_plugin_savepoint(true, 2024100701, 'tool', 'installaddon');
}
return true;
}
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
$string['acknowledgement'] = 'Acknowledgement';
$string['acknowledgementtext'] = 'I understand that it is my responsibility to have full backups of this site prior to installing additional plugins. I accept and understand that plugins (especially but not only those originating in unofficial sources) may contain security holes, can make the site unavailable, or cause private data leaks or loss.';
$string['activitychooserfootertext'] = 'Browse more activities on';
$string['featuredisabled'] = 'The plugin installer is disabled on this site.';
$string['installaddon'] = 'Install plugin!';
$string['installaddons'] = 'Install plugins';
@@ -44,6 +45,8 @@ $string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
$string['installfromziptype'] = 'Plugin type';
$string['installfromziptype_help'] = 'For plugins that correctly declare their component name, the installer is able to detect the plugin type automatically. If the auto-detection fails, choose the correct type of plugin manually. Warning: The installation procedure can fail badly if an incorrect plugin type is specified.';
$string['installfromziptype_link'] = 'Development:Plugins';
$string['marketplaceadminlinktext'] = 'Browse new plugins';
$string['marketplacelink'] = 'Moodle Marketplace';
$string['permcheck'] = 'Make sure the plugin type root location is writable by the web server process.';
$string['permcheckerror'] = 'Error while checking for write permission';
$string['permcheckprogress'] = 'Checking for write permission ...';
+48
View File
@@ -0,0 +1,48 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Public API for tool_installaddon.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_course\local\entity\activity_chooser_footer;
/**
* Build activity chooser footer content for Marketplace.
*
* @param int $courseid The selected course id.
* @param int $sectionid The selected section id.
* @return activity_chooser_footer
*/
function tool_installaddon_custom_chooser_footer(int $courseid, int $sectionid): activity_chooser_footer {
global $OUTPUT;
$installer = tool_installaddon_installer::instance();
$marketplaceurl = $installer->get_marketplace_url();
$renderedfooter = $OUTPUT->render_from_template('tool_installaddon/chooser_footer', [
'url' => $marketplaceurl->out(false),
]);
return new activity_chooser_footer(
'tool_installaddon/footer',
$renderedfooter
);
}
+6
View File
@@ -26,6 +26,12 @@
defined('MOODLE_INTERNAL') || die();
if ($hassiteconfig and empty($CFG->disableupdateautodeploy)) {
$installer = tool_installaddon_installer::instance();
$ADMIN->add('modules', new admin_externalpage(
'tool_installaddon_marketplace',
get_string('marketplaceadminlinktext', 'tool_installaddon'),
$installer->get_marketplace_url()->out(false)
), 'modsettings');
$ADMIN->add('modules', new admin_externalpage('tool_installaddon_index',
get_string('installaddons', 'tool_installaddon'),
@@ -0,0 +1,30 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_installaddon/chooser_footer
Activity chooser footer link to Marketplace.
Example context (json):
{
"url": "https://marketplace.moodle.com/?site=hash"
}
}}
<div class="w-100 d-flex px-2">
<span class="my-auto me-1">{{#str}} activitychooserfootertext, tool_installaddon {{/str}}</span>
{{> tool_installaddon/marketplace_link }}
</div>
@@ -0,0 +1,36 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_installaddon/marketplace_link
Reusable link to Moodle Marketplace.
Example context (json):
{
"url": "https://marketplace.moodle.com/?site=hash"
}
}}
<a class="d-inline my-auto" href="{{url}}" target="_blank" rel="noopener noreferrer">
{{#str}} marketplacelink, tool_installaddon {{/str}}
<i
class="fa fa-external-link"
role="img"
aria-label="{{#str}} opensinnewwindow, core {{/str}}"
title="{{#str}} opensinnewwindow, core {{/str}}"
>
</i>
</a>
@@ -0,0 +1,37 @@
@tool @tool_installaddon @javascript
Feature: Marketplace activity chooser footer
In order to browse plugins from the activity chooser
As a teacher
I need to see the Marketplace footer link when installaddon is the active footer plugin
Background:
Given the following config values are set as admin:
| activitychooseractivefooter | tool_installaddon |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | format |
| Course 1 | C1 | 0 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
Scenario: Activity chooser footer includes Marketplace link
Given I log in as "teacher1"
When I am on "Course 1" course homepage with editing mode on
And I open the activity chooser
Then "Browse more activities on" "text" should exist in the "Add an activity or resource" "dialogue"
And "Marketplace" "link" should exist in the "Add an activity or resource" "dialogue"
Scenario: Back button works from activity help when the Marketplace footer is active
Given I log in as "teacher1"
When I am on "Course 1" course homepage with editing mode on
And I open the activity chooser
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
Then "help" "core_course > Activity chooser screen" should be visible
When I click on "Back" "button" in the "Add an activity or resource" "dialogue"
Then "modules" "core_course > Activity chooser screen" should be visible
And "help" "core_course > Activity chooser screen" should not be visible
And "Marketplace" "link" should exist in the "Add an activity or resource" "dialogue"
And "Back" "button" should not exist in the "Add an activity or resource" "dialogue"
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides a mock testable_tool_installaddon_installer_without_site_info class.
*
* @package tool_installaddon
* @subpackage fixtures
* @category test
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/testable_installer.php');
/**
* Testable subclass with site-info sharing disabled.
*
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_tool_installaddon_installer_without_site_info extends testable_tool_installaddon_installer {
/**
* Disable site info sharing.
*
* @return bool
*/
protected function should_send_site_info() {
return false;
}
}
@@ -18,12 +18,14 @@
namespace tool_installaddon;
use testable_tool_installaddon_installer;
use testable_tool_installaddon_installer_without_site_info;
use tool_installaddon_installer;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__.'/fixtures/testable_installer.php');
require_once(__DIR__ . '/fixtures/testable_installer.php');
require_once(__DIR__ . '/fixtures/testable_installer_without_site_info.php');
/**
* Unit tests for the {@link tool_installaddon_installer} class
@@ -32,16 +34,14 @@ require_once(__DIR__.'/fixtures/testable_installer.php');
* @category test
* @copyright 2013 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_installaddon_installer
*/
final class installer_test extends \advanced_testcase {
public function test_get_addons_repository_url(): void {
$installer = testable_tool_installaddon_installer::instance();
$url = $installer->get_addons_repository_url();
$query = parse_url($url, PHP_URL_QUERY);
$this->assertEquals(1, preg_match('~^site=(.+)$~', $query, $matches));
$site = rawurldecode($matches[1]);
$site = json_decode(base64_decode($site), true);
$site = $this->decode_site_info_from_url($url);
$this->assertIsArray($site);
$this->assertEquals(3, count($site));
$this->assertSame('Nasty site', $site['fullname']);
@@ -49,6 +49,34 @@ final class installer_test extends \advanced_testcase {
$this->assertSame("2.5'; DROP TABLE mdl_user; --", $site['majorversion']);
}
/**
* Tests that marketplace URL includes expected host, scheme and site payload.
*/
public function test_get_marketplace_url(): void {
$installer = testable_tool_installaddon_installer::instance();
$addonsurl = $installer->get_addons_repository_url();
$marketplaceurl = $installer->get_marketplace_url();
$this->assertSame('marketplace.moodle.com', parse_url($marketplaceurl, PHP_URL_HOST));
$this->assertSame('https', parse_url($marketplaceurl, PHP_URL_SCHEME));
$this->assertSame(
$this->decode_site_info_from_url($addonsurl),
$this->decode_site_info_from_url($marketplaceurl),
);
}
/**
* Tests that marketplace URL has no query string when site info is unavailable.
*/
public function test_get_marketplace_url_without_site_info(): void {
$installer = testable_tool_installaddon_installer_without_site_info::instance();
$marketplaceurl = $installer->get_marketplace_url();
$this->assertSame('marketplace.moodle.com', parse_url($marketplaceurl, PHP_URL_HOST));
$this->assertSame('https', parse_url($marketplaceurl, PHP_URL_SCHEME));
$this->assertEmpty(parse_url($marketplaceurl, PHP_URL_QUERY));
}
public function test_decode_remote_request(): void {
$installer = testable_tool_installaddon_installer::instance();
@@ -132,7 +160,7 @@ $plugin->version = 2014121300;
$versionphp = file_get_contents($fixtures.'/github/moodle-repository_mahara-master/version.php');
$this->assertEquals('repository_mahara', $installer->testable_detect_plugin_component_from_versionphp($versionphp));
$versionphp = file_get_contents($fixtures.'/nocomponent/baz/version.php');
$versionphp = file_get_contents($fixtures . '/nocomponent/baz/version.php');
$this->assertFalse($installer->testable_detect_plugin_component_from_versionphp($versionphp));
}
@@ -143,15 +171,28 @@ $plugin->version = 2014121300;
$storage1 = $installer->make_installfromzip_storage();
$this->assertTrue(is_dir($storage1));
$this->assertTrue(is_writable($storage1));
file_put_contents($storage1.'/hello.txt', 'Find me if you can!');
file_put_contents($storage1 . '/hello.txt', 'Find me if you can!');
// Check we get unique directory on each call.
$storage2 = $installer->make_installfromzip_storage();
$this->assertTrue(is_dir($storage2));
$this->assertTrue(is_writable($storage2));
$this->assertFalse(file_exists($storage2.'/hello.txt'));
$this->assertFalse(file_exists($storage2 . '/hello.txt'));
// Check both are in the same parent directory.
$this->assertEquals(dirname($storage1), dirname($storage2));
}
/**
* Decodes the encoded 'site' URL query parameter.
*
* @param moodle_url $url
* @return array
*/
private function decode_site_info_from_url(\moodle_url $url): array {
$query = parse_url($url, PHP_URL_QUERY);
$this->assertEquals(1, preg_match('~^site=(.+)$~', $query, $matches));
$site = rawurldecode($matches[1]);
return json_decode(base64_decode($site), true);
}
}
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for tool_installaddon lib.
*
* @package tool_installaddon
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_installaddon;
use core_course\local\entity\activity_chooser_footer;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/admin/tool/installaddon/lib.php');
/**
* Test installaddon lib functions.
*/
final class lib_test extends \advanced_testcase {
/**
* Tests chooser footer generation for marketplace link content.
*
* @covers ::tool_installaddon_custom_chooser_footer
*/
public function test_tool_installaddon_custom_chooser_footer(): void {
$this->resetAfterTest();
$footer = \tool_installaddon_custom_chooser_footer(1, 1);
$this->assertInstanceOf(activity_chooser_footer::class, $footer);
$this->assertSame('tool_installaddon/footer', $footer->get_footer_js_file());
$this->assertStringContainsString('https://marketplace.moodle.com/', $footer->get_footer_template());
$this->assertStringContainsString('site=', $footer->get_footer_template());
}
}
@@ -0,0 +1,47 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace tool_installaddon\task;
/**
* Unit tests for the post install task.
*
* @package tool_installaddon
* @category test
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_installaddon\task\post_install
*/
final class post_install_test extends \advanced_testcase {
/**
* Tests that the task sets the activity chooser footer plugin to tool_installaddon.
*/
public function test_execute_sets_activitychooseractivefooter_config(): void {
$this->resetAfterTest();
set_config('activitychooseractivefooter', 'hidden');
$task = new post_install();
$task->execute();
$this->assertSame(
'tool_installaddon',
get_config('core', 'activitychooseractivefooter'),
);
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'tool_installaddon';
$plugin->version = 2024100700;
$plugin->version = 2024100701;
$plugin->requires = 2024100100;
$plugin->maturity = MATURITY_STABLE;
+1 -8
View File
@@ -28,12 +28,5 @@ declare(strict_types=1);
* Perform the post-install procedures.
*/
function xmldb_tool_moodlenet_install() {
// Use an ad-hoc task to set the active activity chooser footer plugin to tool_moodlenet.
// We couldn't do this in admin/settings/courses.php for 2 reasons:
// - First, because it would be a breach of component communications principles to do so there.
// - Second, because we can't call get_plugins_with_function() during install and upgrade (or it will return []).
// We couldn't do this directly here either, because there is an admin_apply_default_settings() call after all plugins are
// installed and that would reset whatever value we set here to 'hidden'.
$postinstall = new tool_moodlenet\task\post_install();
core\task\manager::queue_adhoc_task($postinstall);
}
@@ -38,6 +38,9 @@ $string['defaultmoodlenet_desc'] = 'The URL of the MoodleNet instance available
$string['defaultmoodlenetname'] = "MoodleNet instance name";
$string['defaultmoodlenetnamevalue'] = 'MoodleNet Central';
$string['defaultmoodlenetname_desc'] = 'The name of the MoodleNet instance available via the activity chooser.';
$string['removalwarning_feature'] = 'If you need to continue using MoodleNet, contact your site administrator about setting up a self-hosted MoodleNet instance.';
$string['removalwarning_service'] = 'After the date, you will no longer be able to browse or add content from MoodleNet Central.';
$string['removalwarning_title'] = 'The MoodleNet service will be shut down on 20 April 2026.';
$string['enablemoodlenet'] = 'Enable MoodleNet integration (inbound)';
$string['enablemoodlenet_desc'] = 'If enabled, a user with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their course. In addition, a user with the capability to restore backups can select a backup file on MoodleNet and restore it into Moodle.';
$string['errorduringdownload'] = 'An error occurred while downloading the file: {$a}';
@@ -27,8 +27,16 @@
<div class="content text-start mb-5 px-5 py-4" data-region="chooser-option-summary-content-container">
<div data-region="moodle-net">
<div class="overlay-icon-container z-index-1 d-none" data-region="spinner"></div>
<img class="w-25 mb-4" aria-hidden="true" src="{{{img}}}">
<img class="w-25 mb-4" alt="" aria-hidden="true" src="{{{img}}}">
<p>{{#str}} instancedescription, tool_moodlenet {{/str}}</p>
{{! Removal warning - always visible when MoodleNet integration is enabled }}
<div class="alert alert-warning w-75 mx-auto mb-3" role="alert">
<strong>{{#str}}removalwarning_title, tool_moodlenet{{/str}}</strong>
<p class="mb-1">{{#str}}removalwarning_service, tool_moodlenet{{/str}}</p>
<p class="mb-0">{{#str}}removalwarning_feature, tool_moodlenet{{/str}}</p>
</div>
<p class="w-75 mx-auto mb-1 mt-5">{{#str}} connectandbrowse, tool_moodlenet {{/str}}</p>
<a class="btn btn-secondary d-block w-75 mx-auto mb-4"
data-action="browse"
@@ -0,0 +1,41 @@
@tool @tool_uploaduser @_file_upload
Feature: Admin can suspend user course enrolment via CSV upload
In order to manage enrolments in bulk
As an administrator
I need to be able to enrol and suspend users using CSV upload
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
| Course 3 | C3 |
@javascript
Scenario: Admin uploads enrol and suspend CSVs and verifies enrolment status
Given I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/QA_user_enrol.txt" file to "File" filemanager
And I press "Upload users"
And I press "Upload users"
And I press "Continue"
And I upload "lib/tests/fixtures/QA_user_suspend.txt" file to "File" filemanager
And I press "Upload users"
And I set the field "Upload type" to "Update existing users only"
And I press "Upload users"
And I press "Continue"
And I am on the "Course 1" "enrolled users" page
Then the following should exist in the "participants" table:
| First name | Status |
| Learner One | Active |
| Learner Two | Active |
And I am on the "Course 2" "enrolled users" page
And the following should exist in the "participants" table:
| First name | Status |
| Learner One | Active |
| Learner Two | Suspended |
And I am on the "Course 3" "enrolled users" page
And the following should exist in the "participants" table:
| First name | Status |
| Learner One | Suspended |
| Learner Two | Active |
+2 -6
View File
@@ -197,9 +197,7 @@ class api {
$data->link = $confirmationurl->out(false);
$message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
$data->link = $confirmationurl->out();
$messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
$messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false);
$user->mailformat = 1; // Always send HTML version as well.
@@ -339,9 +337,7 @@ class api {
$data->link = $confirmationurl->out(false);
$message = get_string('confirmaccountemail', 'auth_oauth2', $data);
$data->link = $confirmationurl->out();
$messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
$messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false);
$user->mailformat = 1; // Always send HTML version as well.
+7 -16
View File
@@ -27,19 +27,14 @@ $string['auth_oauth2description'] = 'OAuth 2 standards based authentication';
$string['auth_oauth2settings'] = 'OAuth 2 authentication settings.';
$string['confirmaccountemail'] = 'Hi {$a->firstname},
A new account has been requested at \'{$a->sitename}\'
using your email address.
A new account has been requested at \'{$a->sitename}\' using your email address.
To confirm your new account, please go to this web address:
To confirm your new account, please click the link below:
{$a->link}
<a href="{$a->link}">Confirm your account</a>
In most mail programs, this should appear as a blue link
which you can just click on. If that doesn\'t work,
then cut and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
If you need help, please contact the site administrator.
{$a->admin}
If you did not do this, someone else could be trying to compromise your account.
@@ -53,16 +48,12 @@ A request has been made to link the {$a->issuername} login
{$a->linkedemail} to your account at \'{$a->sitename}\'
using your email address.
To confirm this request and link these logins, please go to this web address:
To confirm this request and link these logins, please click the link below:
{$a->link}
<a href="{$a->link}">Link your accounts</a>
In most mail programs, this should appear as a blue link
which you can just click on. If that doesn\'t work,
then cut and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
If you need help, please contact the site administrator.
{$a->admin}
If you did not do this, someone else could be trying to compromise your account.
+3 -1
View File
@@ -35,10 +35,12 @@ Feature: Test if the login form provides the correct feedback
Given I log in as "admin"
And I navigate to "Appearance > Logos" in site administration
And I upload "course/tests/fixtures/image.jpg" file to "Logo" filemanager
And I upload "course/tests/fixtures/image.jpg" file to "Favicon" filemanager
And I press "Save changes"
And I log out
And I follow "Log in"
Then "//img[@id='logoimage']" "xpath_element" should exist
Then "//img[@id='logoimage' and contains(@src, '/image.jpg')]" "xpath_element" should exist
And "//link[@rel='shortcut icon' and contains(@href, '/image.jpg')]" "xpath_element" should exist
Scenario: Add a custom welcome message
Given the following config values are set as admin:
+9
View File
@@ -5285,6 +5285,15 @@ class restore_create_categories_and_questions extends restore_structure_step {
$this->set_mapping('question_bank_entry', $this->latestqbe->oldid, $this->latestqbe->newid);
}
if (
($data->qtype === 'random')
&& ($this->latestversion->status == \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN)
) {
// Ensure that this newly created question is considered by
// \qtype_random\task\remove_unused_questions.
$this->latestversion->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT;
}
// Now store the question.
$newitemid = $DB->insert_record('question', $data);
$this->set_mapping('question', $oldid, $newitemid);
+3 -20
View File
@@ -107,17 +107,12 @@ final class async_backup_test extends \advanced_testcase {
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(['backupid' => $backupid]);
$asynctask->set_userid($USER->id);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting trace output during this test.
$this->expectOutputRegex("/$backupid/");
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
$asynctask->execute();
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
@@ -151,13 +146,9 @@ final class async_backup_test extends \advanced_testcase {
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(['backupid' => $backupid]);
\core\task\manager::queue_adhoc_task($asynctask);
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
$asynctask->execute();
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
@@ -241,19 +232,11 @@ final class async_backup_test extends \advanced_testcase {
// Now queue an adhoc task and check it handles and completes gracefully.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(array('backupid' => $backupid));
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/invalid controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
$asynctask->execute();
}
}
+2 -18
View File
@@ -223,20 +223,12 @@ final class async_restore_test extends \advanced_testcase {
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_restore_task();
$asynctask->set_custom_data(['backupid' => $restoreid]);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/invalid controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
$asynctask->execute();
// Now delete the record and confirm an entirely missing controller is handled.
$DB->delete_records('backup_controllers');
@@ -244,19 +236,11 @@ final class async_restore_test extends \advanced_testcase {
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_restore_task();
$asynctask->set_custom_data(['backupid' => $restoreid]);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/Unable to find restore controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
$asynctask->execute();
}
}
+1 -1
View File
@@ -23,7 +23,7 @@
{}
}}
<div id="block-myoverview-{{uniqid}}" class="block-myoverview block-cards" data-region="myoverview" role="navigation">
<div id="block-myoverview-{{uniqid}}" class="block-myoverview block-cards" data-region="myoverview">
<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 }}
@@ -44,7 +44,7 @@
}
}}
<div class="block-myoverview block-cards" data-region="myoverview" role="navigation">
<div class="block-myoverview block-cards" data-region="myoverview">
<hr class="mt-0"/>
<div class="container-fluid p-0">
@@ -53,7 +53,7 @@
src="{{nocoursesimg}}"
alt=""
>
<h5 class="h5 mt-3 mb-0">{{{ title }}}</h5>
<h4 class="h4 mt-3 mb-0">{{{ title }}}</h4>
<p class="mt-3 mb-0">{{{ intro }}}</p>
<div class="mt-5 mb-0 whitebutton" id="action_bar">
@@ -314,6 +314,6 @@ Feature: The my overview block allows users to easily access their courses
Then I should not see "Category 1" in the "Course overview" "block"
@accessibility
Scenario: The dashboard page must have sufficient colour contrast
Scenario: The My courses page must meet accessibility standards
When I am on the "My courses" page logged in as "student1"
Then the page should meet "wcag143" accessibility standards
Then the page should meet accessibility standards with "best-practice" extra tests
@@ -88,4 +88,4 @@ Feature: Zero state on my overview block
@javascript @accessibility
Scenario: Evaluate the accessibility of the My courses (zero state)
When I am on the "My courses" page logged in as "manager"
Then the page should meet accessibility standards
Then the page should meet accessibility standards with "best-practice" extra tests
@@ -66,7 +66,7 @@
<div class="pb-2" data-region="event-list-wrapper">
{{#eventsbyday}}
<div class="mt-3" data-region="event-list-content-date" data-timestamp="{{dayTimestamp}}">
<h5 class="h6 d-inline {{^courseview}}font-weight-bold px-2{{/courseview}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}</h5>
<h4 class="h6 d-inline {{^courseview}}font-weight-bold px-2{{/courseview}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}</h4>
</div>
{{> block_timeline/event-list-items }}
{{/eventsbyday}}
@@ -66,13 +66,13 @@
</div>
<div class="event-name-container flex-grow-1 line-height-3 nowrap text-truncate">
<div class="d-flex">
<h6 class="event-name mb-0 pb-1 text-truncate">
<h5 class="h6 event-name mb-0 pb-1 text-truncate">
{{#overdue}}<span class="badge rounded-pill bg-danger text-white ms-1 float-end">{{#str}} overdue, block_timeline {{/str}}</span>{{/overdue}}
<a href="{{url}}"
title="{{name}}"
aria-label='{{#cleanstr}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{activityname}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/cleanstr}}'>
{{{activityname}}}</a>
</h6>
</h5>
</div>
<small class="mb-0">
{{#courseview}}
@@ -86,7 +86,7 @@
</div>
{{#action.actionable}}
<div class="d-flex timeline-action-button">
<h6 class="event-action">
<h5 class="h6 event-action">
<a class="list-group-item-action btn btn-outline-secondary btn-sm text-nowrap"
href="{{action.url}}"
aria-label="{{action.name}}"
@@ -96,7 +96,7 @@
<span class="badge bg-secondary text-dark">{{action.itemcount}}</span>
{{/action.showitemcount}}
</a>
</h6>
</h5>
</div>
{{/action.actionable}}
</div>
@@ -60,54 +60,56 @@
{{#str}} overdue, block_timeline {{/str}}
</a>
<div class="dropdown-divider" role="separator"></div>
<h6 class="dropdown-header">{{#str}} duedate, block_timeline {{/str}}</h6>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="7"
data-filtername="next7days"
{{#next7days}}aria-current="true"{{/next7days}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next7days, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="30"
data-filtername="next30days"
{{#next30days}}aria-current="true"{{/next30days}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next30days, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="90"
data-filtername="next3months"
{{#next3months}}aria-current="true"{{/next3months}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next3months, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="180"
data-filtername="next6months"
{{#next6months}}aria-current="true"{{/next6months}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next6months, block_timeline {{/str}}
</a>
<div role="group" aria-labelledby="duedatefiltergrouplabel">
<div class="h6 dropdown-header" role="presentation" id="duedatefiltergrouplabel">{{#str}} duedate, block_timeline {{/str}}</div>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="7"
data-filtername="next7days"
{{#next7days}}aria-current="true"{{/next7days}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next7days, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="30"
data-filtername="next30days"
{{#next30days}}aria-current="true"{{/next30days}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next30days, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="90"
data-filtername="next3months"
{{#next3months}}aria-current="true"{{/next3months}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next3months, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="180"
data-filtername="next6months"
{{#next6months}}aria-current="true"{{/next6months}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next6months, block_timeline {{/str}}
</a>
</div>
</div>
</div>
@@ -37,8 +37,10 @@ Feature: The timeline block allows users to see upcoming courses
| student1 | C3 | student |
| student1 | C4 | student |
@accessibility
Scenario: Next 30 days in course view
Given I log in as "student1"
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Next 30 days" "link" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
@@ -61,6 +63,7 @@ Feature: The timeline block allows users to see upcoming courses
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test assign 1" "link" should not exist in the "Timeline" "block"
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
Scenario: All in course view
Given I log in as "student1"
@@ -50,9 +50,11 @@ Feature: The timeline block allows users to see upcoming activities
And "Test assign 1" "link" should not exist in the "Timeline" "block"
And I should not see "Assignment is due · Course 1" in the "Timeline" "block"
@accessibility
Scenario: Overdue in date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
When I click on "Overdue" "link" in the "Timeline" "block"
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
@@ -63,6 +65,7 @@ Feature: The timeline block allows users to see upcoming activities
And "Test choice 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 4" "link" should not exist in the "Timeline" "block"
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
Scenario: All in date view
Given I log in as "student1"
Generated
+6 -6
View File
@@ -4037,16 +4037,16 @@
},
{
"name": "symfony/process",
"version": "v6.4.4",
"version": "v6.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "710e27879e9be3395de2b98da3f52a946039f297"
"reference": "596cc67dc84d1718679240e0805dd50dbbd33992"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/710e27879e9be3395de2b98da3f52a946039f297",
"reference": "710e27879e9be3395de2b98da3f52a946039f297",
"url": "https://api.github.com/repos/symfony/process/zipball/596cc67dc84d1718679240e0805dd50dbbd33992",
"reference": "596cc67dc84d1718679240e0805dd50dbbd33992",
"shasum": ""
},
"require": {
@@ -4078,7 +4078,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v6.4.4"
"source": "https://github.com/symfony/process/tree/v6.4.33"
},
"funding": [
{
@@ -4094,7 +4094,7 @@
"type": "tidelift"
}
],
"time": "2024-02-20T12:31:00+00:00"
"time": "2026-01-28T10:02:31+00:00"
},
{
"name": "symfony/service-contracts",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -22,10 +22,11 @@
*/
import $ from 'jquery';
import CustomEvents from 'core/custom_interaction_events';
import * as ModalEvents from 'core/modal_events';
import selectors from 'core_course/local/activitychooser/selectors';
import * as Templates from 'core/templates';
import {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';
import {enter, space} from 'core/key_codes';
import {addIconToContainer} from 'core/loadingicon';
import * as Repository from 'core_course/local/activitychooser/repository';
import Notification from 'core/notification';
@@ -246,7 +247,54 @@ const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOption
const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);
Array.from(chooserOptions).forEach((element) => {
return element.addEventListener('keydown', (e) => {
const $element = $(element);
// Set up custom interaction events for RTL-aware keyboard navigation.
CustomEvents.define($element, [
CustomEvents.events.next,
CustomEvents.events.previous,
CustomEvents.events.home,
CustomEvents.events.end,
]);
// Handle focus move (automatically handles RTL).
const createNavHandler = (resolver) => (e, data) => {
const currentOption = data.originalEvent.target.closest(
selectors.regions.chooserOption.container
);
if (currentOption !== null) {
const toFocusOption = resolver(currentOption);
if (toFocusOption) {
focusChooserOption(toFocusOption, currentOption);
}
}
};
$element.on(
CustomEvents.events.next,
createNavHandler(
(current) => current.nextElementSibling || chooserOptionsContainer.firstElementChild
)
);
$element.on(
CustomEvents.events.previous,
createNavHandler(
(current) => current.previousElementSibling || chooserOptionsContainer.lastElementChild
)
);
$element.on(
CustomEvents.events.home,
createNavHandler(() => chooserOptionsContainer.firstElementChild)
);
$element.on(
CustomEvents.events.end,
createNavHandler(() => chooserOptionsContainer.lastElementChild)
);
element.addEventListener('keydown', (e) => {
// Check for enter/ space triggers for showing the help.
if (e.keyCode === enter || e.keyCode === space) {
@@ -267,40 +315,6 @@ const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOption
showModuleHelp(carousel, moduleData, modal);
}
}
// Next.
if (e.keyCode === arrowRight) {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const nextOption = currentOption.nextElementSibling;
const firstOption = chooserOptionsContainer.firstElementChild;
const toFocusOption = clickErrorHandler(nextOption, firstOption);
focusChooserOption(toFocusOption, currentOption);
}
// Previous.
if (e.keyCode === arrowLeft) {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const previousOption = currentOption.previousElementSibling;
const lastOption = chooserOptionsContainer.lastElementChild;
const toFocusOption = clickErrorHandler(previousOption, lastOption);
focusChooserOption(toFocusOption, currentOption);
}
if (e.keyCode === home) {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const firstOption = chooserOptionsContainer.firstElementChild;
focusChooserOption(firstOption, currentOption);
}
if (e.keyCode === end) {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const lastOption = chooserOptionsContainer.lastElementChild;
focusChooserOption(lastOption, currentOption);
}
});
});
};
@@ -348,22 +362,6 @@ const toggleFocusableChooserOption = (chooserOption, isFocusable) => {
}
};
/**
* Small error handling function to make sure the navigated to object exists
*
* @method clickErrorHandler
* @param {HTMLElement} item What we want to check exists
* @param {HTMLElement} fallback If we dont match anything fallback the focus
* @return {HTMLElement}
*/
const clickErrorHandler = (item, fallback) => {
if (item !== null) {
return item;
} else {
return fallback;
}
};
/**
* Render the search results in a defined container
*
+1
View File
@@ -3274,6 +3274,7 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
ON uc.roleid = rc.roleid
AND ( $eqpaths OR $likechild )
WHERE (ra.id IS NOT NULL OR uc.upath IS NOT NULL)
ORDER BY cc.sortorder, cc.id
";
$params = [
@@ -74,6 +74,7 @@ class activity_navigation implements renderable, templatable {
$attributes = [
'class' => 'btn btn-link',
'id' => 'prev-activity-link',
'aria-label' => get_string('activitynavigation:preva', 'course', $linkname),
];
$this->prevlink = new \action_link($linkurl, $OUTPUT->larrow() . ' ' . $linkname, null, $attributes);
}
@@ -89,15 +90,16 @@ class activity_navigation implements renderable, templatable {
$attributes = [
'class' => 'btn btn-link',
'id' => 'next-activity-link',
'aria-label' => get_string('activitynavigation:nexta', 'course', $linkname),
];
$this->nextlink = new \action_link($linkurl, $linkname . ' ' . $OUTPUT->rarrow(), null, $attributes);
}
// Render the activity list dropdown menu if available.
if (!empty($activitylist)) {
$select = new url_select($activitylist, '', array('' => get_string('jumpto')));
$select->set_label(get_string('jumpto'), array('class' => 'sr-only'));
$select->attributes = array('id' => 'jump-to-activity');
$select = new url_select($activitylist, '', ['' => get_string('activitynavigation:jumptoactivity', 'course')]);
$select->set_label(get_string('activitynavigation:jumptoactivity', 'course'), ['class' => 'sr-only']);
$select->attributes = ['id' => 'jump-to-activity'];
$this->activitylist = $select;
}
}
@@ -39,23 +39,23 @@ Feature: Activity navigation in a single activity course
Scenario: Jump to a hidden activity as a teacher
Given I log in as "teacher1"
When I am on "Course 1" course homepage
Then "Jump to..." "field" should exist
Then "Jump to activity" "field" should exist
# The current activity (Forum 1) will not be listed.
And the "Jump to..." select box should not contain "Forum 1"
And the "Jump to activity" select box should not contain "Forum 1"
# Check drop down menu contents.
And the "Jump to..." select box should contain "Assignment 1 (hidden)"
And the "Jump to..." select box should contain "Lesson 1 (hidden)"
And the "Jump to activity" select box should contain "Assignment 1 (hidden)"
And the "Jump to activity" select box should contain "Lesson 1 (hidden)"
# Jump to a hidden activity somewhere in the middle.
When I select "Assignment 1 (hidden)" from the "Jump to..." singleselect
When I select "Assignment 1 (hidden)" from the "Jump to activity" singleselect
Then I should see "Assignment 1"
And I should see "Forum 1" in the "#prev-activity-link" "css_element"
And I should see "Lesson 1 (hidden)" in the "#next-activity-link" "css_element"
# Jump to the first activity.
And I select "Forum 1" from the "Jump to..." singleselect
And I select "Forum 1" from the "Jump to activity" singleselect
And I should see "Assignment 1 (hidden)" in the "#next-activity-link" "css_element"
But "#prev-activity-link" "css_element" should not exist
# Jump to the last activity.
And I select "Lesson 1 (hidden)" from the "Jump to..." singleselect
And I select "Lesson 1 (hidden)" from the "Jump to activity" singleselect
And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element"
But "#next-activity-link" "css_element" should not exist
@@ -65,7 +65,7 @@ Feature: Activity navigation in a single activity course
# The first activity won't have the previous activity link.
Then "#prev-activity-link" "css_element" should not exist
And "#next-activity-link" "css_element" should not exist
And "Jump to..." "field" should not exist
And "Jump to activity" "field" should not exist
Scenario: The activity navigation asks for login to guest user
Given I log in as "guest"
@@ -73,7 +73,8 @@
class="collapse-list-link icons-collapse-expand collapsed"
>
<span class="collapsed-icon icon-no-margin me-1" data-toggle="tooltip" title="{{#str}} expand, core {{/str}}">
{{#pix}} t/collapsedchevron, core {{/pix}}
<span class="dir-rtl-hide">{{#pix}} t/collapsedchevron, core {{/pix}}</span>
<span class="dir-ltr-hide">{{#pix}} t/collapsedchevron_rtl, core {{/pix}}</span>
<span class="sr-only">{{#str}} expand, core {{/str}}</span>
</span>
<span class="expanded-icon icon-no-margin me-1" data-toggle="tooltip" title="{{#str}} collapse, core {{/str}}">
@@ -56,7 +56,7 @@
"id": "url_select_test",
"action": "#",
"options": [
{"name": "Jump to...", "value": "#0"},
{"name": "Jump to activity", "value": "#0"},
{"name": "Activity A", "value": "#1"},
{"name": "Activity B", "value": "#2"},
{"name": "Activity C", "value": "#3"}
@@ -64,7 +64,7 @@
}
}
}}
<div class="mt-5 mb-1 activity-navigation container-fluid">
<nav class="mt-5 mb-1 activity-navigation container-fluid" aria-label="{{#str}}activitynavigation, course{{/str}}">
{{< core/columns-1to1to1}}
{{$column1}}
<div class="float-start">
@@ -82,4 +82,4 @@
</div>
{{/column3}}
{{/ core/columns-1to1to1}}
</div>
</nav>
+4 -4
View File
@@ -38,7 +38,7 @@ Feature: Display and choose from the available activities in course
Scenario: The teacher can choose to add an activity from the activity summary in the activity chooser
Given I click on "Add an activity or resource" "button" in the "New section" "section"
When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
When I click on "Add a new Assignment" "link" in the "help" "core_course > Activity chooser screen"
And I click on "Add a new Assignment" "link" in the ".modal-footer" "css_element"
Then I should see "New Assignment"
Scenario: Show summary
@@ -57,8 +57,8 @@ Feature: Display and choose from the available activities in course
Given I click on "Add an activity or resource" "button" in the "New section" "section"
When I click on "Information about the Assignment activity" "button" in the "modules" "core_course > Activity chooser screen"
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "help" "core_course > Activity chooser screen"
And I should see "Back" in the "help" "core_course > Activity chooser screen"
When I click on "Back" "button" in the "help" "core_course > Activity chooser screen"
And I should see "Back" in the ".modal-footer" "css_element"
When I click on "Back" "button" in the ".modal-footer" "css_element"
Then "modules" "core_course > Activity chooser screen" should be visible
And "help" "core_course > Activity chooser screen" should not be visible
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
@@ -67,7 +67,7 @@ Feature: Display and choose from the available activities in course
And I am on "C2" course homepage with editing mode on
And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section"
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
And I click on "Back" "button" in the "help" "core_course > Activity chooser screen"
And I click on "Back" "button" in the ".modal-footer" "css_element"
And "modules" "core_course > Activity chooser screen" should be visible
And "help" "core_course > Activity chooser screen" should not be visible
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
+52 -52
View File
@@ -191,49 +191,49 @@ Feature: Activity navigation
Given I log in as "teacher1"
When I am on "Course 1" course homepage
And I follow "Assignment 1"
Then "Jump to..." "field" should exist
Then "Jump to activity" "field" should exist
# The current activity will not be listed.
And the "Jump to..." select box should not contain "Assignment 1"
And the "Jump to activity" select box should not contain "Assignment 1"
# Stealth activities will not be listed.
And the "Jump to..." select box should not contain "Forum 1"
And the "Jump to activity" select box should not contain "Forum 1"
# Resources without view URL (e.g. labels) will not be listed.
And the "Jump to..." select box should not contain "Label 1"
And the "Jump to activity" select box should not contain "Label 1"
# Check drop down menu contents.
And the "Jump to..." select box should contain "Book 1"
And the "Jump to..." select box should contain "Chat 1"
And the "Jump to..." select box should contain "Choice 1"
And the "Jump to..." select box should contain "Database 1"
And the "Jump to..." select box should contain "Feedback 1"
And the "Jump to..." select box should contain "Folder 1"
And the "Jump to..." select box should contain "Imscp 1"
And the "Jump to..." select box should contain "Lesson 1"
And the "Jump to..." select box should contain "Lti 1"
And the "Jump to..." select box should contain "Page 1"
And the "Jump to..." select box should contain "Quiz 1"
And the "Jump to..." select box should contain "Url 1"
And the "Jump to..." select box should contain "Wiki 1"
And the "Jump to..." select box should contain "Workshop 1"
And the "Jump to activity" select box should contain "Book 1"
And the "Jump to activity" select box should contain "Chat 1"
And the "Jump to activity" select box should contain "Choice 1"
And the "Jump to activity" select box should contain "Database 1"
And the "Jump to activity" select box should contain "Feedback 1"
And the "Jump to activity" select box should contain "Folder 1"
And the "Jump to activity" select box should contain "Imscp 1"
And the "Jump to activity" select box should contain "Lesson 1"
And the "Jump to activity" select box should contain "Lti 1"
And the "Jump to activity" select box should contain "Page 1"
And the "Jump to activity" select box should contain "Quiz 1"
And the "Jump to activity" select box should contain "Url 1"
And the "Jump to activity" select box should contain "Wiki 1"
And the "Jump to activity" select box should contain "Workshop 1"
# Hidden activities will be rendered with a '(hidden)' text.
And the "Jump to..." select box should contain "Glossary 1 (hidden)"
And the "Jump to activity" select box should contain "Glossary 1 (hidden)"
# Activities in hidden sections will be rendered with a '(hidden)' text.
And the "Jump to..." select box should contain "Resource 1 (hidden)"
And the "Jump to..." select box should contain "Scorm 1 (hidden)"
And the "Jump to..." select box should contain "Survey 1 (hidden)"
And the "Jump to activity" select box should contain "Resource 1 (hidden)"
And the "Jump to activity" select box should contain "Scorm 1 (hidden)"
And the "Jump to activity" select box should contain "Survey 1 (hidden)"
# Jump to an activity somewhere in the middle.
When I select "Page 1" from the "Jump to..." singleselect
When I select "Page 1" from the "Jump to activity" singleselect
Then I should see "Page 1"
And I should see "Lti 1" in the "#prev-activity-link" "css_element"
And I should see "Quiz 1" in the "#next-activity-link" "css_element"
# Jump to the first activity.
And I select "Assignment 1" from the "Jump to..." singleselect
And I select "Assignment 1" from the "Jump to activity" singleselect
And I should see "Book 1" in the "#next-activity-link" "css_element"
But "#prev-activity-link" "css_element" should not exist
# Jump to the last activity.
And I select "Workshop 1" from the "Jump to..." singleselect
And I select "Workshop 1" from the "Jump to activity" singleselect
And I should see "Wiki 1" in the "#prev-activity-link" "css_element"
But "#next-activity-link" "css_element" should not exist
# Jump to a hidden activity.
And I select "Glossary 1" from the "Jump to..." singleselect
And I select "Glossary 1" from the "Jump to activity" singleselect
And I should see "Folder 1" in the "#prev-activity-link" "css_element"
And I should see "Imscp 1" in the "#next-activity-link" "css_element"
@@ -241,45 +241,45 @@ Feature: Activity navigation
Given I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Assignment 1"
And "Jump to..." "field" should exist
And "Jump to activity" "field" should exist
# The current activity will not be listed.
And the "Jump to..." select box should not contain "Assignment 1"
And the "Jump to activity" select box should not contain "Assignment 1"
# Stealth activities will not be listed for students.
And the "Jump to..." select box should not contain "Forum 1"
And the "Jump to activity" select box should not contain "Forum 1"
# Resources without view URL (e.g. labels) will not be listed.
And the "Jump to..." select box should not contain "Label 1"
And the "Jump to activity" select box should not contain "Label 1"
# Hidden activities will not be listed for students.
And the "Jump to..." select box should not contain "Glossary 1"
And the "Jump to activity" select box should not contain "Glossary 1"
# Activities in hidden sections will not be listed for students.
And the "Jump to..." select box should not contain "Resource 1"
And the "Jump to..." select box should not contain "Scorm 1"
And the "Jump to..." select box should not contain "Survey 1"
And the "Jump to activity" select box should not contain "Resource 1"
And the "Jump to activity" select box should not contain "Scorm 1"
And the "Jump to activity" select box should not contain "Survey 1"
# Only activities visible to students will be listed.
And the "Jump to..." select box should contain "Book 1"
And the "Jump to..." select box should contain "Chat 1"
And the "Jump to..." select box should contain "Choice 1"
And the "Jump to..." select box should contain "Database 1"
And the "Jump to..." select box should contain "Feedback 1"
And the "Jump to..." select box should contain "Folder 1"
And the "Jump to..." select box should contain "Imscp 1"
And the "Jump to..." select box should contain "Lesson 1"
And the "Jump to..." select box should contain "Lti 1"
And the "Jump to..." select box should contain "Page 1"
And the "Jump to..." select box should contain "Quiz 1"
And the "Jump to..." select box should contain "Url 1"
And the "Jump to..." select box should contain "Wiki 1"
And the "Jump to..." select box should contain "Workshop 1"
And the "Jump to activity" select box should contain "Book 1"
And the "Jump to activity" select box should contain "Chat 1"
And the "Jump to activity" select box should contain "Choice 1"
And the "Jump to activity" select box should contain "Database 1"
And the "Jump to activity" select box should contain "Feedback 1"
And the "Jump to activity" select box should contain "Folder 1"
And the "Jump to activity" select box should contain "Imscp 1"
And the "Jump to activity" select box should contain "Lesson 1"
And the "Jump to activity" select box should contain "Lti 1"
And the "Jump to activity" select box should contain "Page 1"
And the "Jump to activity" select box should contain "Quiz 1"
And the "Jump to activity" select box should contain "Url 1"
And the "Jump to activity" select box should contain "Wiki 1"
And the "Jump to activity" select box should contain "Workshop 1"
# Jump to an activity somewhere in the middle.
When I select "Page 1" from the "Jump to..." singleselect
When I select "Page 1" from the "Jump to activity" singleselect
Then I should see "Page 1"
And I should see "Lti 1" in the "#prev-activity-link" "css_element"
And I should see "Quiz 1" in the "#next-activity-link" "css_element"
# Jump to the first activity.
And I select "Assignment 1" from the "Jump to..." singleselect
And I select "Assignment 1" from the "Jump to activity" singleselect
And I should see "Book 1" in the "#next-activity-link" "css_element"
But "#prev-activity-link" "css_element" should not exist
# Jump to the last activity.
And I select "Workshop 1" from the "Jump to..." singleselect
And I select "Workshop 1" from the "Jump to activity" singleselect
And I should see "Wiki 1" in the "#prev-activity-link" "css_element"
But "#next-activity-link" "css_element" should not exist
@@ -289,4 +289,4 @@ Feature: Activity navigation
And I follow "Assignment 1"
Then "#prev-activity-link" "css_element" should not exist
And "#next-activity-link" "css_element" should not exist
And "Jump to..." "field" should not exist
And "Jump to activity" "field" should not exist
@@ -48,13 +48,13 @@ Feature: Activity navigation involving activities with access restrictions
Given I am on the "Page 1" "page activity" page logged in as student1
Then I should see "Page 2" in the "#next-activity-link" "css_element"
# Activity that has access restriction should not show up in the dropdown.
And the "Jump to..." select box should not contain "Page 3"
And I select "Page 4" from the "Jump to..." singleselect
And the "Jump to activity" select box should not contain "Page 3"
And I select "Page 4" from the "Jump to activity" singleselect
# Page 2 should be shown in the previous link since Page 3 is not yet available.
And I should see "Page 2" in the "#prev-activity-link" "css_element"
And the "Jump to..." select box should not contain "Page 3"
And the "Jump to activity" select box should not contain "Page 3"
# Navigate to Page 2.
And I click on "Page 2" "link" in the "page-content" "region"
# Since Page 2 has now been viewed and deemed completed, Page 3 can now be accessed.
And I should see "Page 3" in the "#next-activity-link" "css_element"
And the "Jump to..." select box should contain "Page 3"
And the "Jump to activity" select box should contain "Page 3"
@@ -17,6 +17,7 @@ Feature: Restrict activities availability
And the following "activities" exist:
| activity | course | name |
| assign | C1 | Test assign name |
And I enable "bigbluebuttonbn" "mod" plugin
Scenario: Activities can be added with the default permissions
Given I log in as "teacher1"
@@ -27,17 +28,34 @@ Feature: Restrict activities availability
And I should see "Test assign name"
@javascript @skip_chrome_zerosize
Scenario: Activities can not be added when the admin restricts the permissions
Scenario Outline: Activities can not be added when the admin restricts the permissions
Given the following "role capability" exists:
| role | editingteacher |
| mod/assign:addinstance | prohibit |
And I log in as "admin"
And I am on the "Course 1" "permissions" page
And I override the system permissions of "Teacher" role with:
| mod/glossary:addinstance | Prohibit |
And I log out
| role | editingteacher |
| <activityrole> | prohibit |
And I log in as "teacher1"
When I am on "Course 1" course homepage with editing mode on
And I click on "Add an activity or resource" "button" in the "New section" "section"
Then "Add a new Assignment" "link" should not exist in the "Add an activity or resource" "dialogue"
Then "Add a new Glossary" "link" should not exist in the "Add an activity or resource" "dialogue"
And I open the activity chooser
Then "Add a new <activitytype>" "link" should not exist in the "Add an activity or resource" "dialogue"
Examples:
| activityrole | activitytype |
| mod/assign:addinstance | Assignment |
| mod/bigbluebuttonbn:addinstance | BigBlueButton |
| mod/book:addinstance | Book |
| mod/choice:addinstance | Choice |
| mod/data:addinstance | Database |
| mod/feedback:addinstance | Feedback |
| mod/folder:addinstance | Folder |
| mod/forum:addinstance | Forum |
| mod/glossary:addinstance | Glossary |
| mod/h5pactivity:addinstance | H5P |
| mod/imscp:addinstance | IMS content package |
| mod/label:addinstance | Text and media area |
| mod/lesson:addinstance | Lesson |
| mod/page:addinstance | Page |
| mod/quiz:addinstance | Quiz |
| mod/resource:addinstance | File |
| mod/scorm:addinstance | SCORM package |
| mod/url:addinstance | URL |
| mod/wiki:addinstance | Wiki |
| mod/workshop:addinstance | Workshop |
+6 -2
View File
@@ -58,15 +58,19 @@ function xmldb_enrol_database_upgrade($oldversion) {
// Migrate enrolments where possible.
// First, get the user enrolments that can be migrated.
// Only select the earliest (MIN id) user_enrolments per user to avoid
// duplicate key violations when a user has multiple enrolments across
// duplicate database enrol instances.
$migrateusers = $DB->get_records_sql(
"SELECT ue.id
"SELECT MIN(ue.id) AS id
FROM {user_enrolments} ue
WHERE ue.enrolid $insql
AND NOT EXISTS (
SELECT 1
FROM {user_enrolments} ue2
WHERE ue2.userid = ue.userid
AND ue2.enrolid = :idtokeep)",
AND ue2.enrolid = :idtokeep)
GROUP BY ue.userid",
array_merge($inparams, ['idtokeep' => $idtokeep]),
);
@@ -122,6 +122,7 @@ class sync_tool_grades extends adhoc_task {
mtrace("Skipping - Invalid grade $mtracecontent.");
continue;
}
$grade = floatval($grade); // Grade must be sent as a numeric value, not a string.
if (empty($grademax)) {
mtrace("Skipping - Invalid grademax $mtracecontent.");
@@ -0,0 +1,33 @@
@enrol @enrol_self
Feature: Self-enrolment cohort restriction
In order to prevent unauthorized access
As a admin
I need to restrict self-enrolment to cohort members
Background:
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "cohorts" exist:
| name | idnumber | contextid |
| Cohort A | CH1 | 1 |
And the following "cohort members" exist:
| user | cohort |
| student1 | CH1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And I log in as "admin"
And I add "Self enrolment" enrolment method in "Course 1" with:
| Custom instance name | Test student enrolment |
| Only cohort members | Cohort A |
Scenario: Self enrolment as cohort member
Given I am on the "C1" "Course" page logged in as "student1"
When I press "Enrol me"
Then I should see "You are enrolled in the course."
Scenario: Self enrolment as non cohort member
Given I am on the "C1" "Course" page logged in as "student2"
Then I should see "Only members of cohort 'Cohort A' can self-enrol."
+1 -1
View File
@@ -143,7 +143,7 @@
// Run convert on document (.ps to .gif/.png) or run dvisvgm (.ps to .svg).
if ($background) {
$bg_opt = "-transparent \"$background\""; // Makes transparent background
$bg_opt = '-transparent ' . escapeshellarg($background); // Makes transparent background
} else {
$bg_opt = "";
}
+181 -2
View File
@@ -26,10 +26,22 @@
defined('MOODLE_INTERNAL') || die();
// Default timeout in seconds for mimetex command execution.
defined('FILTER_TEX_MIMETEX_TIMEOUT') || define('FILTER_TEX_MIMETEX_TIMEOUT', 5);
/**
* Check if the current operating system is Windows.
*
* @return bool True if running on Windows, false otherwise.
*/
function filter_tex_is_windows(): bool {
return (PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows");
}
function filter_tex_get_executable($debug=false) {
global $CFG;
if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
if (filter_tex_is_windows()) {
return "$CFG->dirroot/filter/tex/mimetex.exe";
}
@@ -134,7 +146,7 @@ function filter_tex_get_cmd($pathname, $texexp) {
$texexp = escapeshellarg($texexp);
$executable = filter_tex_get_executable(false);
if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
if (filter_tex_is_windows()) {
$executable = str_replace(' ', '^ ', $executable);
return "$executable ++ -e \"$pathname\" -- $texexp";
@@ -143,6 +155,173 @@ function filter_tex_get_cmd($pathname, $texexp) {
}
}
/**
* Run mimetex command with a timeout on Windows.
*
* @param string $cmd Command string to execute.
* @param int $timeoutmicros Timeout in microseconds.
* @return array Array with keys: code, timedout, status, errors.
*/
function filter_tex_exec_windows(string $cmd, int $timeoutmicros): array {
// Create temporary file for stderr.
$temperr = tempnam(sys_get_temp_dir(), 'err_');
$descriptors = [
0 => ['file', 'NUL', 'r'], // STDIN.
1 => ['file', 'NUL', 'w'], // STDOUT.
2 => ['file', $temperr, 'w'], // STDERR.
];
$process = proc_open($cmd, $descriptors, $pipes);
if (!is_resource($process)) {
unlink($temperr);
return [
'code' => 127, // Command not found.
'timedout' => false,
'status' => [],
'errors' => '',
];
}
$timedout = false;
while ($timeoutmicros > 0) {
$start = microtime(true);
$status = proc_get_status($process);
if (!$status['running']) {
break;
}
$timeoutmicros -= (microtime(true) - $start) * 1000000;
if ($timeoutmicros <= 0) {
$timedout = true;
$pid = (int)($status['pid'] ?? 0);
exec('taskkill /F /T /PID ' . $pid . ' 2>NUL');
break;
}
usleep(50000); // Sleep for 50ms.
}
$status = proc_get_status($process);
$code = proc_close($process);
// Capture stderr from temp file.
$errors = file_get_contents($temperr);
unlink($temperr);
return [
'code' => $code,
'timedout' => $timedout,
'status' => $status,
'errors' => $errors,
];
}
/**
* Run mimetex command with a timeout on Unix-like systems.
*
* @param string $cmd Command string to execute.
* @param int $timeoutmicros Timeout in microseconds.
* @return array Array with keys: code, timedout, status, errors.
*/
function filter_tex_exec_unix(string $cmd, int $timeoutmicros): array {
// File descriptors passed to the process.
$descriptors = [
0 => ['pipe', 'r'], // STDIN.
1 => ['pipe', 'w'], // STDOUT.
2 => ['pipe', 'w'], // STDERR.
];
$process = proc_open('exec ' . $cmd, $descriptors, $pipes);
if (!is_resource($process)) {
return [
'code' => 127, // Command not found.
'timedout' => false,
'status' => [],
'errors' => '',
];
}
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$errors = '';
$timedout = false;
while ($timeoutmicros > 0) {
$start = microtime(true);
$read = [$pipes[1], $pipes[2]];
$other = [];
stream_select($read, $other, $other, 0, (int)$timeoutmicros);
$status = proc_get_status($process);
stream_get_contents($pipes[1]); // Discard stdout to prevent pipe blocking.
$errors .= stream_get_contents($pipes[2]);
if (!$status['running']) {
break;
}
$timeoutmicros -= (microtime(true) - $start) * 1000000;
if ($timeoutmicros <= 0) {
$timedout = true;
proc_terminate($process);
break;
}
usleep(50000); // Sleep for 50ms.
}
// Read any remaining data from pipes.
stream_get_contents($pipes[1]); // Discard remaining stdout.
$errors .= stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$status = proc_get_status($process);
$code = proc_close($process);
return [
'code' => $code,
'timedout' => $timedout,
'status' => $status,
'errors' => $errors,
];
}
/**
* Run mimetex command with a timeout.
*
* @param string $cmd Command string to execute.
* @param int|null &$code Exit code (passed by reference, set by function).
* @return void
*/
function filter_tex_exec(string $cmd, ?int &$code): void {
$timeoutmicros = FILTER_TEX_MIMETEX_TIMEOUT * 1000000;
if (filter_tex_is_windows()) {
$result = filter_tex_exec_windows($cmd, $timeoutmicros);
} else {
$result = filter_tex_exec_unix($cmd, $timeoutmicros);
}
if ($result['errors']) {
debugging('filter_tex_exec errors: ' . $result['errors'], DEBUG_DEVELOPER);
}
if ($result['timedout']) {
$code = 124;
} else if ($result['code'] === -1 && isset($result['status']['exitcode']) && $result['status']['exitcode'] !== -1) {
$code = $result['status']['exitcode'];
} else {
$code = $result['code'];
}
}
/**
* Purge all caches when settings changed.
*/
+1 -2
View File
@@ -18,7 +18,6 @@ define('NO_MOODLE_COOKIES', true); // Because it interferes with caching
require_once($CFG->dirroot.'/filter/tex/latex.php');
$cmd = ''; // Initialise these variables
$status = '';
$relativepath = get_file_argument();
@@ -58,7 +57,7 @@ define('NO_MOODLE_COOKIES', true); // Because it interferes with caching
$texexp = preg_replace('!\r\n?!', ' ', $texexp);
$texexp = '\Large '.$texexp;
$cmd = filter_tex_get_cmd($pathname, $texexp);
system($cmd, $status);
filter_tex_exec($cmd, $status);
}
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ if ($ADMIN->fulltree) {
$items[] = new admin_setting_heading('filter_tex/latexheading', get_string('latexsettings', 'filter_tex'), '');
$items[] = new admin_setting_configtextarea('filter_tex/latexpreamble', get_string('latexpreamble','filter_tex'),
'', "\\usepackage[latin1]{inputenc}\n\\usepackage{amsmath}\n\\usepackage{amsfonts}\n\\RequirePackage{amsmath,amssymb,latexsym}\n");
$items[] = new admin_setting_configtext('filter_tex/latexbackground', get_string('backgroundcolour', 'admin'), '', '#FFFFFF');
$items[] = new admin_setting_configcolourpicker('filter_tex/latexbackground', get_string('backgroundcolour', 'admin'), '', '#FFFFFF');
$items[] = new admin_setting_configtext('filter_tex/density', get_string('density', 'admin'), '', '120', PARAM_INT);
$default_filter_tex_pathlatex = '';
+5 -1
View File
@@ -153,7 +153,7 @@
$texexp = '\Large '.$texexp;
$commandpath = filter_tex_get_executable(true);
$cmd = filter_tex_get_cmd($pathname, $texexp);
system($cmd, $status);
filter_tex_exec($cmd, $status);
if ($return) {
return $image;
@@ -172,6 +172,10 @@
echo "Status corresponds to bus error<br />\n";
} else if ($status == 22) {
echo "Status corresponds to abnormal termination<br />\n";
} else if ($status == 124) {
echo "Status corresponds to timeout<br />\n";
} else if ($status == 127) {
echo "Status corresponds to command not found<br />\n";
}
if (file_exists($commandpath)) {
echo "File size of mimetex executable $commandpath is " . filesize($commandpath) . "<br />";
+1 -1
View File
@@ -79,7 +79,7 @@
.gradingform_guide .plainvalue.empty {
font-style: italic;
color: #aaa;
color: #555;
}
.gradingform_guide .plainvalue.editname {
@@ -97,6 +97,55 @@ class behat_gradingform_guide extends behat_base {
}
}
/**
* Edits an existing marking guide with the provided data.
*
* This method edits the marking guide of the marking guide definition
* form; the provided TableNode should contain one row for
* each field and each cell of the row should contain:
* | Field name | New value |
* | shortname | Updated Grade criterion |
*
* @When /^I edit the marking guide criterion "([^"]*)" with the following values:$/
* @param string $criterionname
* @param TableNode $fields
*/
public function i_edit_the_marking_guide_criterion_with_the_following_values(string $criterionname, TableNode $fields) {
if ($fieldvalues = $fields->getHash()) {
$criterionid = 0;
$locator = "//tr[contains(@class, 'criterion')]//div[@class='criterionname']"
. "//span[@class='textvalue'][text()='$criterionname']/ancestor::tr";
if ($criterionrow = $this->find('xpath', $locator)) {
$criterionid = str_replace('guide-criteria-', '', $criterionrow->getAttribute('id'));
}
if ($criterionid) {
$criterionroot = 'guide[criteria]' . '[' . $criterionid . ']';
foreach ($fieldvalues as $fieldvalue) {
// Make sure the fieldvalue array has 2 elements.
if (count($fieldvalue) != 2) {
throw new ExpectationException(
'The field definition should contain field name and new value. ' .
'Please follow this format: | Field name | New value |',
$this->getSession()
);
}
$fieldname = $fieldvalue['Field name'];
$newvalue = $fieldvalue['New value'];
$this->set_guide_field_value($criterionroot . "[$fieldname]", $newvalue);
}
} else {
throw new ExpectationException(
'Criterion with name "' . $criterionname . '" not found.',
$this->getSession()
);
}
}
}
/**
* Defines the marking guide with the provided data, following marking guide's definition grid cells.
*
@@ -0,0 +1,86 @@
@gradingform @gradingform_guide
Feature: Editing a marking guide already used for grading updates regrade state and student visible grades
In order to update marking guide details
As a teacher
I need to be able to grade a submission
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | Student | One | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | name | advancedgradingmethod_submissions | assignsubmission_onlinetext_enabled |
| assign | C1 | Assign 1 | guide | 1 |
And the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Assign 1 | student1 | Assign 1 submission |
And I am on the "Course 1" course page logged in as teacher1
And I go to "Assign 1" advanced grading definition page
And I set the following fields to these values:
| Name | Assign 1 marking guide |
| Description | Marking guide description |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Grade criterion A | Grade 1 description for students | Grade 1 description for markers | 70 |
| Grade criterion B | Grade 2 description for students | Grade 2 description for markers | 30 |
And I press "Save marking guide and make it ready"
And I navigate to "Assignment" in current page administration
And I go to "Student One" "Assign 1" activity advanced grading page
And I grade by filling the marking guide with:
| Grade criterion A | 25 | Needs improvement |
| Grade criterion B | 20 | Excellent! |
And I press "Save changes"
And I am on "Course 1" course homepage
And I go to "Assign 1" advanced grading definition page
And I edit the marking guide criterion "Grade criterion A" with the following values:
| Field name | New value |
| shortname | Updated Grade criterion A |
| description | Updated description for students |
| descriptionmarkers | Updated description for markers |
| maxscore | 60 |
And I press "Save"
@javascript
Scenario: Teacher edits a used marking guide and mark it for regrade and student sees breakdown only after teacher regrades
# Set the marking guide to be "Mark for regrade".
Given I set the field "menuguideregrade" to "Mark for regrade"
And I should see "You are about to save changes to a marking guide that has already been used for grading. Please indicate if existing grades need to be reviewed. If you set this then the marking guide will be hidden from students until their item is regraded."
And I click on "Continue" "button"
And I am on the "Assign 1" "assign activity" page logged in as student1
# Student should not see the grade breakdown as the activity is marked for regrade.
And I should not see "Grade breakdown"
When I am on the "Assign 1" "assign activity" page logged in as teacher1
And I go to "Student One" "Assign 1" activity advanced grading page
And I grade by filling the marking guide with:
| Updated Grade criterion A | 50 | I changed my mind |
| Grade criterion B | 30 | It's all good now |
And I press "Save changes"
And I am on the "Assign 1" "assign activity" page logged in as student1
# Now the student should see the updated grade breakdown.
Then I should see "Grade breakdown"
And I should see the marking guide information displayed as:
| criteria | description | remark | maxscore | criteriascore |
| Updated Grade criterion A | Updated description for students | I changed my mind | 60 | 50 / 60 |
| Grade criterion B | Grade 2 description for students | It's all good now | 30 | 30 / 30 |
@javascript
Scenario: Teacher edits a used marking guide does not mark for regrade existing student grades remain visible and unchanged
# Set the marking guide to be "Do not mark for regrade".
Given I set the field "menuguideregrade" to "Do not mark for regrade"
And I should see "You are about to save changes to a marking guide that has already been used for grading. Please indicate if existing grades need to be reviewed. If you set this then the marking guide will be hidden from students until their item is regraded."
And I click on "Continue" "button"
When I am on the "Assign 1" "assign activity" page logged in as student1
# Student should see the updated grade breakdown as the activity was not marked for regrading.
Then I should see "Grade breakdown"
And I should see the marking guide information displayed as:
| criteria | description | remark | maxscore | criteriascore |
| Updated Grade criterion A | Updated description for students | Needs improvement | 60 | 25 / 60 |
| Grade criterion B | Grade 2 description for students | Excellent! | 30 | 20 / 30 |
+1 -1
View File
@@ -136,7 +136,7 @@
.gradingform_rubric .plainvalue.empty {
font-style: italic;
color: #aaa;
color: #555;
}
/* Make invisible the buttons 'Move up' for the first criterion and
+2 -2
View File
@@ -169,7 +169,7 @@ if (has_capability('moodle/grade:viewall', $context) && $courseid != SITEID) {
echo $OUTPUT->header();
if ($report->fill_table(true, true)) {
echo html_writer::tag('h3', get_string('coursesiamtaking', 'grades'));
echo $OUTPUT->heading(get_string('coursesiamtaking', 'grades'));
echo '<br />' . $report->print_table(true);
}
} else { // We have a course context. We must be navigating from the gradebook.
@@ -189,7 +189,7 @@ if (has_capability('moodle/grade:viewall', $context) && $courseid != SITEID) {
}
if (count($report->teachercourses)) {
echo html_writer::tag('h3', get_string('coursesiamteaching', 'grades'));
echo $OUTPUT->heading(get_string('coursesiamteaching', 'grades'));
$report->print_teacher_table();
}
@@ -0,0 +1,30 @@
@gradereport @gradereport_overview
Feature: Grade overview report can be viewed
In order to see the courses I am enrolled in
As a user
I should be able to access the grade overview report's index page
Background:
Given the following "courses" exist:
| fullname | shortname |
| Awesome course | C1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | t1@example.com |
| student1 | Student | 1 | s1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
@javascript @accessibility
Scenario Outline: The grade overview report index page should be accessible
Given I am logged in as "<user>"
When I follow "Grades" in the user menu
Then "<headingname>" "heading" should exist
And the page should meet accessibility standards with "best-practice" extra tests
Examples:
| user | headingname |
| student1 | Courses I am taking |
| teacher1 | Courses I am teaching |
+4 -2
View File
@@ -250,7 +250,8 @@ Feature: We can use calculated grade totals
| Aggregation | Natural |
| Include outcomes in aggregation | 1 |
| Exclude empty grades | 0 |
And I change window size to "large"
# Change window size to ultra-wide to avoid 'element-click-intercepted' failures.
And I change window size to "5120x2160"
And I navigate to "View > Grader report" in the course gradebook
And I give the grade "Excellent" to the user "Student 1" for the grade item "Test outcome item one"
And I press "Save changes"
@@ -305,7 +306,8 @@ Feature: We can use calculated grade totals
And I set the following settings for grade item "Test outcome item one" of type "gradeitem" on "setup" page:
| Weight adjusted | 1 |
| aggregationcoef2 | 100 |
And I change window size to "large"
# Change window size to ultra-wide to avoid 'element-click-intercepted' failures.
And I change window size to "5120x2160"
And I navigate to "View > Grader report" in the course gradebook
And I give the grade "Excellent" to the user "Student 1" for the grade item "Test outcome item one"
And I press "Save changes"
+8 -1
View File
@@ -1,4 +1,4 @@
@core @core_grades @javascript
@core @core_grades
Feature: We can enter in grades and view reports from the gradebook
In order to check the expected results are displayed
As a teacher
@@ -46,6 +46,7 @@ Feature: We can enter in grades and view reports from the gradebook
And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment name 2"
And I press "Save changes"
@javascript
Scenario: Grade a grade item and ensure the results display correctly in the gradebook
When I navigate to "View > User report" in the course gradebook
And the "Gradebook navigation menu" select menu should contain "Grader report"
@@ -68,6 +69,7 @@ Feature: We can enter in grades and view reports from the gradebook
And "Course 1" row "Grade" column of "overview-grade" table should contain "170.00"
And "Course 1" row "Grade" column of "overview-grade" table should not contain "90.00"
@javascript
Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
When I navigate to "Setup > Gradebook setup" in the course gradebook
And I set the following settings for grade item "Course 1" of type "course" on "setup" page:
@@ -92,3 +94,8 @@ Feature: We can enter in grades and view reports from the gradebook
| Test assignment name 1 | 0.72% | 0.72% |
| Test assignment name 2 | 1.00% | 1.00% |
| Course total | 1.00% | 1.00% |
Scenario: User not enrolled in any course should not see the grades report
Given I log in as "admin"
When I follow "Grades" in the user menu
Then I should see "You are not enrolled in, nor teaching any courses on this site."
@@ -30,11 +30,11 @@
}}
{{<core/local/comboboxsearch/resultitem }}
{{$content}}
<div class="pe-2 ps-1 w-25">
<div class="pe-3 ps-1 flex-shrink-0">
<img class="rounded-circle mx-auto img-fluid" src="{{groupimageurl}}" alt=""/>
</div>
<div class="pe-3 w-75">
<span class="d-block w-100 p-0 text-truncate">
<div class="pe-3">
<span class="d-block w-100 p-0 text-wrap text-break">
{{name}}
</span>
</div>
+44
View File
@@ -226,6 +226,48 @@ document.onreadystatechange = async() => {
const Pending = await getPendingClass();
var resizePending = new Pending('core_h5p/iframe:resize');
// Track when the embedded H5P content is fully attached.
const contentLoadedPending = new Pending('core_h5p/iframe:contentLoaded');
let contentLoaded = false;
const markContentLoaded = function() {
const body = iFrame?.contentDocument?.body;
const hasContent = body && body.querySelector('.h5p-container, .h5p-content');
// Check that H5P instance is actually ready with proper initialization
if (contentLoaded || !hasContent || !H5P?.instances?.[0]) {
return false;
}
contentLoaded = true;
contentLoadedPending.resolve();
H5PEmbedCommunicator.send('contentLoaded');
return true;
};
// If the content is already there, mark it immediately.
markContentLoaded();
// Observe the iframe document for the first appearance of the H5P container.
if (!contentLoaded && iFrame.contentDocument?.body) {
const contentObserver = new MutationObserver(function() {
if (markContentLoaded()) {
contentObserver.disconnect();
}
});
contentObserver.observe(iFrame.contentDocument.body, {childList: true, subtree: true});
}
// Extended fallback timeout to ensure pending resolves even if content detection fails.
if (!contentLoaded) {
setTimeout(function() {
if (!contentLoaded) {
contentLoaded = true;
contentLoadedPending.resolve();
H5PEmbedCommunicator.send('contentLoaded');
}
}, 1000);
}
H5P.on(instance, 'resize', function() {
if (H5P.isFullscreen) {
return; // Skip iframe resize.
@@ -249,6 +291,8 @@ document.onreadystatechange = async() => {
}, 150);
});
H5P.externalDispatcher.on('domChanged', markContentLoaded);
// Get emitted xAPI data.
H5P.externalDispatcher.on('xAPI', function(event) {
statementPosted = false;
+3 -3
View File
@@ -33,7 +33,7 @@ $string['cannotcreatedboninstall'] = '<p>No es pot crear la base de dades.</p> <
<p>L\'administrador del lloc hauria de verificar la configuració de la base de dades.</p>';
$string['cannotcreatelangdir'] = 'No s\'ha pogut crear el directori d\'idiomes';
$string['cannotcreatetempdir'] = 'No s\'ha pogut crear el directori temporal';
$string['cannotdownloadcomponents'] = 'No s\'han pogut baixar components';
$string['cannotdownloadcomponents'] = 'No s\'han pogut descarregar els components';
$string['cannotdownloadzipfile'] = 'No s\'ha pogut baixar el fitxer ZIP';
$string['cannotfindcomponent'] = 'No s\'ha pogut trobar el component';
$string['cannotsavemd5file'] = 'No s\'ha pogut desar el fitxer md5';
@@ -46,6 +46,6 @@ $string['invalidmd5'] = 'L\'md5 no és vàlid. Torneu a provar-ho';
$string['missingrequiredfield'] = 'Falta algun camp necessari';
$string['remotedownloaderror'] = '<p>No s\'ha pogut baixar el component al vostre servidor. Verifiqueu els paràmetres del servidor intermediari. Es recomana vivament l\'extensió cURL de PHP.</p>
<p>Haureu de baixar manualment el fitxer <a href="{$a->url}">{$a->url}</a>, copiar-lo a la ubicació «{$a->dest}» del vostre servidor i descomprimir-lo allà.</p>';
$string['wrongdestpath'] = 'El camí de destinació és erroni';
$string['wrongsourcebase'] = 'L\'adreça (URL) base de la font és errònia';
$string['wrongdestpath'] = 'El camí de destinació és incorrecte';
$string['wrongsourcebase'] = 'L\'URL base de la font és errònia';
$string['wrongzipfilename'] = 'El nom del fitxer ZIP és erroni';
+7 -9
View File
@@ -30,20 +30,20 @@
defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'Directori d\'administració';
$string['availablelangs'] = 'Llista d\'idiomes disponibles';
$string['availablelangs'] = 'Paquets d\'idiomes disponibles';
$string['chooselanguagehead'] = 'Trieu un idioma';
$string['chooselanguagesub'] = 'Trieu un idioma per a la instal·lació. S\'utilitzarà també com a idioma per defecte del lloc, tot i que després podeu canviar-lo.';
$string['clialreadyconfigured'] = 'El fitxer config.php ja existeix; feu servir admin/cli/install_database.php si voleu instal·lar Moodle en aquest lloc web.';
$string['clialreadyinstalled'] = 'El fitxer de configuració config.php ja existeix. Feu servir admin/cli/upgrade.php si voleu actualitzar Moodle per a aquest lloc web.';
$string['cliinstallheader'] = 'Programa d\'instal·lació de línia d\'ordres de Moodle {$a}';
$string['clitablesexist'] = 'Les taules de la base de dades ja existeixen. La instal·lació per línia d\'ordres no pot continuar.';
$string['databasehost'] = 'Servidor de base de dades:';
$string['databasename'] = 'Nom de la base de dades:';
$string['databasehost'] = 'Servidor de base de dades';
$string['databasename'] = 'Nom de la base de dades';
$string['databasetypehead'] = 'Trieu el controlador de la base de dades';
$string['dataroot'] = 'Directori de dades';
$string['datarootpermission'] = 'Permisos dels directoris de dades';
$string['dbprefix'] = 'Prefix de taules';
$string['dirroot'] = 'Directori de Moodle';
$string['dirroot'] = 'Directori del Moodle';
$string['environmenthead'] = 'S\'està comprovant el vostre entorn';
$string['environmentsub2'] = 'Cada versió de Moodle uns requeriments mínims de versió de PHP i un nombre d\'extensions de PHP necessàries.
Abans de cada instal·lació o actualització es realitza una comprovació exhaustiva de l\'entorn. Contacteu amb l\'administrador si no sabeu com instal·lar una nova versió de PHP o com habilitar les extensions.';
@@ -72,7 +72,7 @@ $string['pathssubdataroot'] = '<p>Directori on Moodle emmagatzemarà els materia
<p>Aquest directori hauria de tenir permisos de lectura i escriptura per a l\'usuari del servidor web (normalment «www-data», «nobody», or «apache»).</p>
<p>No ha de ser accessible directament via web.</p>
<p>L\'instal·lador provarà de crear-lo si no existeix.</p>';
$string['pathssubdirroot'] = 'Camí complet del directori d\'instal·lació de Moodle.';
$string['pathssubdirroot'] = '<p>El camí complet al directori que conté el codi del Moodle.</p>';
$string['pathssubwwwroot'] = '<p>L\'adreça web completa on s\'accedirà a Moodle; per exemple, l\'adreça que els usuaris introduiran a la barra d\'adreces del navegador per accedir a Moodle.</p> <p> No és possible accedir a Moodle utilitzant diferents adreces. Si el vostre lloc és accessible a través de diferents adreces, trieu-ne la més fàcil i configureu una redirecció permanent per a cadascuna de les altres adreces.</p> <p>
Si el vostre lloc és accessible tant des d\'Internet com des d\'una xarxa interna (anomenada de vegades intranet), utilitzeu l\'adreça pública aquí.</p> <p>Si l\'adreça actual no és correcta, canvieu l\'URL a la barra d\'adreces del navegador i reinicieu la instal·lació.';
$string['pathsunsecuredataroot'] = 'La ubicació del dataroot no és segura.';
@@ -86,9 +86,7 @@ $string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
$string['welcomep20'] = 'Esteu veient aquesta pàgina perquè heu instal·lat amb èxit i heu executat el paquet <strong>{$a->packname} {$a->packversion}</strong>. Felicitacions.';
$string['welcomep30'] = 'Aquesta versió de <strong>{$a->installername}</strong> inclou les aplicacions necessàries per crear un entorn en el qual funcioni <strong>Moodle</strong>, concretament:';
$string['welcomep40'] = 'El paquet inclou també <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>.';
$string['welcomep50'] = 'L\'ús de totes les aplicacions d\'aquest paquet és governat per les seves llicències respectives. El paquet <strong>{$a->installername}</strong> complet és
<a href="http://www.opensource.org/docs/definition_plain.html">codi font obert</a> i es distribueix
sota llicència <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>.';
$string['welcomep60'] = 'Les pàgines següents us guiaran per una sèrie de passos fàcils de seguir per configurar <strong>Moodle</strong> en el vostre ordinador. Podeu acceptar els paràmetres per defecte o, opcionalment, modificar-los perquè s\'ajustin a les vostres necessitats.';
$string['welcomep50'] = 'L\'ús de totes les aplicacions d\'aquest paquet es regeix per les seves respectives llicències. El paquet complet <strong>{$a->installername}</strong> és de <a href="https://www.opensource.org/docs/definition_plain.html">codi obert</a> i es distribueix sota la llicència <a href="https://www.gnu.org/copyleft/gpl.html">GPL</a>.';
$string['welcomep60'] = 'Les pàgines següents us guiaran per una sèrie de passos fàcils de seguir per configurar el <strong>Moodle</strong> en el vostre ordinador. Podeu acceptar els paràmetres per defecte o, opcionalment, modificar-los perquè s\'ajustin a les vostres necessitats.';
$string['welcomep70'] = 'Feu clic al botó «Següent» per continuar amb la configuració de <strong>Moodle</strong>.';
$string['wwwroot'] = 'Adreça web';
+41
View File
@@ -0,0 +1,41 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link https://moodledev.io/general/projects/api/amos}) using the
* list of strings defined in install/stringnames.txt file.
*
* @package installer
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['clianswerno'] = 'n';
$string['cliansweryes'] = 's';
$string['cliincorrectvalueerror'] = 'Error, valor incorrecto "{$a->value}" para "{$a->option}"';
$string['cliincorrectvalueretry'] = 'Valor incorrecto, por favor intenta de nuevo';
$string['clitypevalue'] = 'escribe el valor';
$string['clitypevaluedefault'] = 'escribe el valor, presiona Enter para usar el valor predeterminado ({$a})';
$string['cliunknowoption'] = 'Opciones no reconocidas:
{$a}
Por favor usa la opción --help.';
$string['cliyesnoprompt'] = 'escribe s (significa sí) o n (significa no)';
+51
View File
@@ -0,0 +1,51 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link https://moodledev.io/general/projects/api/amos}) using the
* list of strings defined in install/stringnames.txt file.
*
* @package installer
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['cannotcreatedboninstall'] = '<p>Chan urrainn an stòr-dàta a chruthachadh.</p>
<p>Chan eil an stòr-dàta a chaidh a shònrachadh ann am bith agus chan eil cead aig an neach-cleachdaidh tugta an stòr-dàta a chruthachadh.</p>
<p>Bu chòir do rianaire na làraich rèiteachadh an stòr-dàta a dhearbhadh.</p>';
$string['cannotcreatelangdir'] = 'Chan urrainn iùl-lann cànain a chruthachadh';
$string['cannotcreatetempdir'] = 'Chan urrainn iùl-lann shealach a chruthachadh';
$string['cannotdownloadcomponents'] = 'Chan urrainn co-phàirtean a luchdachadh a-nuas';
$string['cannotdownloadzipfile'] = 'Chan urrainn faidhle ZIP a luchdachadh a-nuas';
$string['cannotfindcomponent'] = 'Chan urrainn a cho-phàirt a lorg';
$string['cannotsavemd5file'] = 'Chan urrainn faidhle md5 a shàbhaladh';
$string['cannotsavezipfile'] = 'Chan urrainn faidhle ZIP a shàbhaladh';
$string['cannotunzipfile'] = 'Chan urrainn am faidhle siopach fhosgladh';
$string['componentisuptodate'] = 'Tha a cho-phàirt ris an là';
$string['dmlexceptiononinstall'] = '<p>Tha mearachd stòir-dàta air tachairt \\[{$a->errorcode}].<br />{$a->debuginfo}</p>';
$string['downloadedfilecheckfailed'] = 'Dhfhàillig sgrùdadh an fhaidhle a chaidh a luchdachadh a-nuas';
$string['missingrequiredfield'] = 'Tha raon deatamach air choireigin a dhìth';
$string['remotedownloaderror'] = '<p>Dhfhàillig luchdachadh na co-phàirte a-nuas chun fhrithealaiche agad. Dearbh na suidheachaidhean procsaidh: thathas a moladh an leudachain PHP cURL gu mòr.</p>
<p>Feumaidh tu am faidhle <a href="{$a->url}">{$a->url}</a> a luchdachadh a-nuas le làimh, lethbhreac dheth a chur ann an "{$a->dest}" san fhrithealaiche agad agus an siopadh fhosgladh an sin.</p>';
$string['wrongdestpath'] = 'Slighe ceann-uidhe ceàrr';
$string['wrongsourcebase'] = 'Bun-thùs URL ceàrr';
$string['wrongzipfilename'] = 'Ainm ceàrr don fhaidhle ZIP';
+3
View File
@@ -39,3 +39,6 @@ $string['cliunknowoption'] = 'अपरिचित विकल्प:
{$a}
कृपया --help विकल्प का उपयोग करें।';
$string['cliyesnoprompt'] = 'टाइप Y (का मतलब है हाँ) या N (का मतलब है नहीं )';
$string['environmentrequireinstall'] = 'स्थापित और सक्षम होना चाहिए';
$string['environmentrequireversion'] = 'version {$a->needed} की आवश्यकता है और आप {$a-> current} चला रहे हैं';
$string['upgradekeyset'] = 'उन्नयन कुंजी (इसे सेट न करने के लिए खाली छोड़ दें)';
+50
View File
@@ -0,0 +1,50 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link https://moodledev.io/general/projects/api/amos}) using the
* list of strings defined in install/stringnames.txt file.
*
* @package installer
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['cannotcreatedboninstall'] = '<p> डेटाबेस नहीं बना सकता है। </p> <p> निर्दिष्ट डेटाबेस मौजूद नहीं है और दिए गए उपयोगकर्ता के पास डेटाबेस बनाने की अनुमति नहीं है। </p> <p> साइट प्रशासक को डेटाबेस विन्यास को सत्यापित करना चाहिए। </p>';
$string['cannotcreatelangdir'] = 'लैंग निर्देशिका नहीं बना सकता है';
$string['cannotcreatetempdir'] = 'अस्थायी निर्देशिका नहीं बना सकता है';
$string['cannotdownloadcomponents'] = 'घटकों को डाउनलोड नहीं कर सकते';
$string['cannotdownloadzipfile'] = 'ज़िप दाखिल करना डाउनलोड नहीं कर सकते';
$string['cannotfindcomponent'] = 'अवयव नहीं मिल रहा है';
$string['cannotsavemd5file'] = 'md5 दाखिल करना को सहेज नहीं सकते';
$string['cannotsavezipfile'] = 'ज़िप दाखिल करना को सहेज नहीं सकते';
$string['cannotunzipfile'] = 'दाखिल करना को अनज़िप नहीं कर सकते';
$string['componentisuptodate'] = 'अवयव अद्यतित है';
$string['dmlexceptiononinstall'] = '<p>डेटाबेस में त्रुटि आ गई है [{$a->errorcode}]।<br />{$a->debuginfo}</p>';
$string['downloadedfilecheckfailed'] = 'डाउनलोड की गई दाखिल करना जाँच विफल रही';
$string['invalidmd5'] = 'चेक चर गलत था-फिर से कोशिश करें';
$string['missingrequiredfield'] = 'कुछ अपेक्षित क्षेत्र गायब है';
$string['remotedownloaderror'] = '<p>आपके सर्वर पर कंपोनेंट डाउनलोड करने में विफलता मिली। कृपया प्रॉक्सी सेटिंग्स की जाँच करें; PHP cURL एक्सटेंशन का उपयोग करने की अत्यधिक अनुशंसा की जाती है।</p>
<p>आपको <a href="{$a->url}">{$a->url}</a> फ़ाइल को मैन्युअल रूप से डाउनलोड करना होगा, इसे अपने सर्वर के "{$a->dest}" फ़ोल्डर में कॉपी करना होगा और वहाँ इसे अनज़िप करना होगा।</p>';
$string['wrongdestpath'] = 'गलत लक्ष्य मार्ग';
$string['wrongsourcebase'] = 'गलत उद्गम URL मूल';
$string['wrongzipfilename'] = 'गलत ज़िप दाखिल करना नाम';
+101
View File
@@ -0,0 +1,101 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link https://moodledev.io/general/projects/api/amos}) using the
* list of strings defined in install/stringnames.txt file.
*
* @package installer
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'प्रशासक निर्देशिका';
$string['availablelangs'] = 'उपलब्ध भाषा पैक';
$string['chooselanguagehead'] = 'एक भाषा चुनें';
$string['chooselanguagesub'] = 'कृपया अधिष्ठापन के लिए एक भाषा चुनें। इस भाषा का उपयोग साइट के लिए चूकना भाषा के रूप में भी किया जाएगा, हालांकि इसे बाद में बदला जा सकता है।';
$string['clialreadyconfigured'] = 'कॉन्फ़िगरेशन फ़ाइल config.php पहले से मौजूद है। कृपया इस साइट के लिए Moodle इंस्टॉल करने के लिए admin/cli/install_database.php का उपयोग करें।';
$string['clialreadyinstalled'] = 'कॉन्फ़िगरेशन फ़ाइल config.php पहले से मौजूद है। कृपया इस साइट के लिए Moodle इंस्टॉल करने के लिए admin/cli/install_database.php का उपयोग करें।';
$string['cliinstallheader'] = 'मूडल {$a} कमांड लाइन अधिष्ठापन कार्यक्रम';
$string['clitablesexist'] = 'डेटाबेस तालिकाएँ पहले से मौजूद हैं; CLI अधिष्ठापन जारी नहीं रह सकती है।';
$string['databasehost'] = 'डेटाबेस मेजबान';
$string['databasename'] = 'डेटाबेस का नाम';
$string['databasetypehead'] = 'डेटाबेस चालक चुनें';
$string['dataroot'] = 'डेटा निर्देशिका';
$string['datarootpermission'] = 'डेटा निर्देशिकाओं की अनुमति';
$string['dbprefix'] = 'तालिका उपसर्ग';
$string['dirroot'] = 'मूडल निर्देशिका';
$string['environmenthead'] = 'अपने पर्यावरण की जाँच करें।....';
$string['environmentsub2'] = 'प्रत्येक मूडल रिलीज में कुछ न्यूनतम, कम से कम PHP संस्करण अपेक्षा, आवश्यकता और कई अनिवार्य पी. एच. पी. विस्तार होते हैं। प्रत्येक स्थापना और उन्नयन करना से पहले पूर्ण पर्अथवावरण जांच की जाती है। यदि आप नहीं जानकारी कि नअथवा संस्करण कैसे स्थापित करना है अथवा PHP विस्तार को सक्षम करना है तो कृपअथवा सर्वर प्रशासक से संपर्क करें।';
$string['errorsinenvironment'] = 'पर्यावरण जांच विफल रही!';
$string['installation'] = 'अधिष्ठापन';
$string['langdownloaderror'] = 'दुर्भाग्य से "{$a}" भाषा डाउनलोड नहीं की जा सकी। अधिष्ठापन प्रक्रम अंग्रेजी में जारी रहेगी।';
$string['memorylimithelp'] = '<p>आपके सर्वर के लिए PHP मेमोरी सीमा वर्तमान में {$a} पर सेट है।</p>
<p>इससे Moodle को बाद में मेमोरी संबंधी समस्याएँ हो सकती हैं, विशेष रूप से
यदि आपने कई मॉड्यूल सक्रिय किए हैं और/या कई उपयोगकर्ता हैं।</p>
<p>हमारा सुझाव है कि यदि संभव हो तो आप PHP को उच्च सीमा, जैसे 40M, पर कॉन्फ़िगर करें।
ऐसा करने के कई तरीके हैं जिन्हें आप आज़मा सकते हैं:</p>
<ol>
<li>यदि संभव हो, तो PHP को <i>--enable-memory-limit</i> के साथ पुनः संकलित करें।
इससे Moodle स्वयं मेमोरी सीमा निर्धारित कर सकेगा।</li>
<li>यदि आपके पास अपनी php.ini फ़ाइल तक पहुँच है, तो आप उसमें <b>memory_limit</b>
सेटिंग को 40M जैसी किसी मान पर बदल सकते हैं।</li> यदि आपके पास पहुँच नहीं है, तो आप
अपने व्यवस्थापक से यह करने के लिए कह सकते हैं।</li>
<li>कुछ PHP सर्वरों पर, आप Moodle निर्देशिका में एक .htaccess फ़ाइल बना सकते हैं,
जिसमें यह पंक्ति हो:
<blockquote><div>php_value memory_limit 40M</div></blockquote>
<p>हालाँकि, कुछ सर्वरों पर यह सभी PHP पृष्ठों को काम करने से रोक देगा
(पृष्ठों को देखते समय आपको त्रुटियाँ दिखाई देंगी), इसलिए आपको .htaccess फ़ाइल को हटाना होगा।</p></li>
</ol>';
$string['paths'] = 'रास्ते';
$string['pathserrcreatedataroot'] = 'डेटा निर्देशिका ({$a-> dataroot}) इंस्टॉलर द्वारा नहीं बनाई जा सकती है।';
$string['pathshead'] = 'रास्तों की पुष्टि';
$string['pathsrodataroot'] = 'डेटारूट निर्देशिका लिखित नहीं है।';
$string['pathsroparentdataroot'] = 'मूल निर्देशिका ({$a-> parent}) लिखने योग्य नहीं है। डेटा निर्देशिका ({$a-> dataroot}) इंस्टॉलर द्वारा नहीं बनाई जा सकती है।';
$string['pathssubadmindir'] = 'बहुत कम वेबहोस्ट नियंत्रण कक्ष अथवा किसी चीज़ तक पहुँचने के लिए आपके लिए एक विशेष URL के रूप में प्रशासक का उपयोग करते हैं। दुर्भाग्य से यह मूडल प्रशासक पृष्ठों के लिए स्तर स्थान के साथ संघर्ष करता है। आप अपने इंस्टॉलेशन में प्रशासक निर्देशिका का नाम बदलकर और उस नए नाम को यहाँ रखकर इसे निश्चित करना सकते हैं। उदाहरण के लिए: <em>moodleadmin</em> यह मूडल में प्रशासक लिंक को निश्चित करनाेगा।';
$string['pathssubdataroot'] = '<p> एक निर्देशिका जहाँ मूडल उपयोगकर्ताओं द्वारा अपलोड की गई सभी दाखिल करना सामग्री को संग्रहीत करेगा। </p> <p> यह निर्देशिका वेब सर्वर उपयोगकर्ता (आमतौर पर \'www-data\', \'कोई नहीं\' अथवा \'अपाचे\') द्वारा पढ़ने योग्य और लिखने योग्य दोनों होनी चाहिए। </p> <p> यह सीधे वेब पर सुलभ नहीं होना चाहिए। </p> <p> यदि निर्देशिका वर्तमान में मौजूद नहीं है, तो अधिष्ठापन प्रक्रम इसे बनाने का प्रअथवास करेगी। </p>';
$string['pathssubdirroot'] = '<p> मॉड्यूल कोड वाली निर्देशिका का पूरा मार्ग। </p>';
$string['pathssubwwwroot'] = '<p> पूरा सम्बोधन जहाँ मूडल तक पहुँचा जाएगा i.e। वह सम्बोधन जिसे उपयोगकर्ता मूडल तक पहुँचने के लिए अपने ब्राउज़र के पते की पट्टी में दर्ज करेंगे। </p> <p> कई पतों का उपयोग करके मूडल तक पहुँचना संभव नहीं है। यदि आपकी साइट कई पतों के माध्यम से सुलभ है तो सबसे आसान का चयन करें और अन्य पतों में से प्रत्येक के लिए एक स्थायी पुनर्निर्देश स्थापित करें। </p> <p> यदि आपकी साइट इंटरनेट और आंतरिक नेटवर्क (जिसे कभी-कभी इंट्रानेट कहा जाता है) दोनों से सुलभ है, तो यहाँ सार्वजनिक पते का उपयोग करें। </p> <p> यदि चालू, प्रचलित सम्बोधन सही नहीं है, तो कृपया अपने ब्राउज़र के पते की पट्टी में यूआरएल बदलें और स्थापना को फिर से शुरू करें। </p>';
$string['pathsunsecuredataroot'] = 'डेटारूट स्थान सुरक्षित नहीं है';
$string['pathswrongadmindir'] = 'प्रशासक निर्देशिका मौजूद नहीं है';
$string['phpextension'] = '{$a} PHP विस्तार';
$string['phpversion'] = 'PHP संस्करण';
$string['phpversionhelp'] = '<p>Moodle के लिए PHP का कम से कम 5.6.5 या 7.1 संस्करण आवश्यक है (7.0.x में कुछ इंजन संबंधी सीमाएँ हैं)</p>
<p>आप वर्तमान में {$a} संस्करण चला रहे हैं।</p>
<p>आपको PHP को अपग्रेड करना होगा या PHP के नए संस्करण वाले होस्ट पर जाना होगा।</p>';
$string['welcomep20'] = 'आप इस पृष्ठ को इसलिए देख रहे हैं क्योंकि आपने अपने कंप्यूटर में <strong>{$a->packname} {$a->packversion}</strong> पैकेज को सफलतापूर्वक स्थापित और लॉन्च कर लिया है।
बधाई हो!';
$string['welcomep30'] = 'इस <strong>{$a->installername}</strong> रिलीज़ में निम्नलिखित एप्लिकेशन शामिल हैं:
एक ऐसा वातावरण बनाने के लिए जिसमें <strong>Moodle</strong> काम करेगा, अर्थात्:';
$string['welcomep40'] = 'इस पैकेज में <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong> भी शामिल है।';
$string['welcomep50'] = 'इस पैकेज में शामिल सभी अनुप्रयोगों का उपयोग उनके संबंधित लाइसेंसों द्वारा नियंत्रित होता है। संपूर्ण <strong>{$a->installername}</strong> पैकेज <a href="https://www.opensource.org/docs/definition_plain.html">ओपन सोर्स</a> है और इसे <a href="https://www.gnu.org/copyleft/gpl.html">GPL</a> लाइसेंस के अंतर्गत वितरित किया जाता है।';
$string['welcomep60'] = 'निम्नलिखित पृष्ठों में दिए गए आसान चरणों का पालन करके आप अपने कंप्यूटर पर <strong>मूडल </strong> को कॉन्फ़िगर और सेट अप कर सकते हैं।
आप डिफ़ॉल्ट सेटिंग्स को स्वीकार कर सकते हैं या चाहें तो उन्हें अपनी आवश्यकताओं के अनुसार बदल सकते हैं।';
$string['welcomep70'] = '<strong>मूडल </strong> के सेटअप के साथ जारी रखने के लिए नीचे दिए गए "अगले" बटन पर क्लिक करें।';
$string['wwwroot'] = 'वेब सम्बोधन';
+1
View File
@@ -41,3 +41,4 @@ Per favor usa le option --help.';
$string['cliyesnoprompt'] = 'typa s (significa si) o n (significa no)';
$string['environmentrequireinstall'] = 'debe esser installate e habilitate';
$string['environmentrequireversion'] = 'version {$a->needed} es requirite e tu es executante {$a->current}';
$string['upgradekeyset'] = 'Clave de actualisation (lassa vacue pro non fixar lo)';
+32
View File
@@ -0,0 +1,32 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link https://moodledev.io/general/projects/api/amos}) using the
* list of strings defined in install/stringnames.txt file.
*
* @package installer
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['thislanguage'] = 'Provençal Occitan';
+7
View File
@@ -43,3 +43,10 @@ $string['cannotunzipfile'] = 'Faylni arxivdan chiqarib bolmadi';
$string['componentisuptodate'] = 'Komponent yangilangan holda';
$string['dmlexceptiononinstall'] = '<p>Malumotlar bazasida xatolik yuz berdi [{$a->errorcode}].<br />{$a->debuginfo}</p>';
$string['downloadedfilecheckfailed'] = 'Yuklab olingan faylni tekshirish muvaffaqiyatsiz tugadi';
$string['invalidmd5'] = 'Tekshiruv ozgaruvchisi notogri — qayta urinib koring';
$string['missingrequiredfield'] = 'Bazi talab qilingan maydonlar mavjud emas';
$string['remotedownloaderror'] = '<p>Komponentni serveringizga yuklab olish muvaffaqiyatsiz boldi. Iltimos, proksi sozlamalarini tekshiring; PHP cURL kengaytmasi tavsiya etiladi.</p>
<p>Siz <a href="{$a->url}">{$a->url}</a> faylini qolda yuklab olishingiz, serveringizdagi "{$a->dest}" ga nusxalashingiz va u yerda arxivdan chiqarishingiz kerak.</p>';
$string['wrongdestpath'] = 'Notogri manzil yoli.';
$string['wrongsourcebase'] = 'Notogri manba URL bazasi.';
$string['wrongzipfilename'] = 'Notogri ZIP fayl nomi.';
+34
View File
@@ -31,10 +31,44 @@ defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'Admin katalogi';
$string['availablelangs'] = 'Mavjud til paketlari';
$string['chooselanguagehead'] = 'Tilni tanlash';
$string['chooselanguagesub'] = 'Iltimos, ornatish uchun tilni tanlang. Ushbu til saytning standart tili sifatida ham ishlatiladi, ammo keyinchalik ozgartirish mumkin.';
$string['clialreadyconfigured'] = 'config.php konfiguratsiya fayli allaqachon mavjud. Iltimos, ushbu sayt uchun Moodleni ornatish uchun admin/cli/install_database.php faylidan foydalaning.';
$string['clialreadyinstalled'] = 'config.php konfiguratsiya fayli allaqachon mavjud. Iltimos, ushbu sayt uchun Moodleni yangilash uchun admin/cli/install_database.php faylidan foydalaning.';
$string['cliinstallheader'] = 'Moodle {$a} buyruq qatori ornatish dasturi';
$string['clitablesexist'] = 'Malumotlar bazasi jadvallari allaqachon mavjud; CLI orqali ornatish davom ettirib bolmaydi.';
$string['databasehost'] = 'Malumotlar bazasi mezboni';
$string['databasename'] = 'Malumotlar bazasi nomi';
$string['databasetypehead'] = 'Malumotlar bazasi drayverini tanlang';
$string['dataroot'] = 'Malumotlar katalogi';
$string['datarootpermission'] = 'Data kataloglari uchun ruxsatlar';
$string['dbprefix'] = 'Jadvallar prefiksi';
$string['dirroot'] = 'Moodle katalogi';
$string['environmenthead'] = 'Muhitingiz tekshirilmoqda ...';
$string['environmentsub2'] = 'Har bir Moodle versiyasi minimal PHP versiyasi talabi va majburiy PHP kengaytmalariga ega.
Toliq muhit tekshiruvi har bir ornatish yoki yangilashdan oldin amalga oshiriladi. Agar yangi versiyani ornatish yoki PHP kengaytmalarini yoqishni bilmasangiz, server administratoriga murojaat qiling.';
$string['errorsinenvironment'] = 'Muhitni tekshirish muvaffaqiyatsiz tugadi!';
$string['installation'] = 'O\'rnatish';
$string['langdownloaderror'] = 'Afsuski, "{$a}" tili yuklab bolmadi. Ornatish jarayoni ingliz tilida davom ettiriladi.';
$string['paths'] = 'Otkazib yuborishlar';
$string['pathserrcreatedataroot'] = 'Malumotlar papkasi ({$a->dataroot}) ornatish dasturi tomonidan yaratilolmayapti.';
$string['pathshead'] = 'Otkazib yuborishlarni tasdiqlash';
$string['pathsrodataroot'] = 'Dataroot papkasiga yozish mumkin emas.';
$string['pathsroparentdataroot'] = 'Ota papkaga ({$a->parent}) yozish mumkin emas. Shu sababli, malumotlar papkasi ({$a->dataroot}) ornatish dasturi tomonidan yaratilolmayapti.';
$string['pathssubadmindir'] = 'Faqat juda kam web-hostlar sizga boshqaruv paneliga kirish uchun /admin URLini maxsus tarzda ishlatadi. Afsuski, bu Moodle administrator sahifalarining standart joylashuvi bilan toqnashadi. Buni tuzatish uchun ornatishingizdagi admin papkasining nomini ozgartiring va yangi nomni bu yerga kiriting. Masalan: moodleadmin. Bu Moodledagi admin havolalarini tuzatadi.';
$string['pathssubdataroot'] = '<p>Moodle foydalanuvchilar tomonidan yuklangan barcha fayl kontentini saqlaydigan papka.</p>
<p>Ushbu papka veb-server foydalanuvchisi (odatda \'www-data\', \'nobody\' yoki \'apache\') tomonidan oqilishi va yozilishi mumkin bolishi kerak.</p>
<p>U togridan-togri veb orqali kirish mumkin bolmasligi lozim.</p>
<p>Agar papka hozir mavjud bolmasa, ornatish jarayoni uni yaratishga harakat qiladi.</p>';
$string['pathssubdirroot'] = '<p>Moodle kodi joylashgan papkaning toliq yoli.</p>';
$string['pathssubwwwroot'] = '<p>Moodlega kirish uchun toliq manzil, yani foydalanuvchilar Moodlega kirish uchun brauzer manzil satriga kiritadigan manzil.</p>
<p>Moodlega bir nechta manzil orqali kirish mumkin emas. Agar saytingiz bir nechta manzil orqali mavjud bolsa, eng osonini tanlang va boshqa manzillar uchun doimiy yonaltirishni sozlang.</p>
<p>Agar saytingiz Internetdan va ichki tarmoqdan (bazan Intranet deb ataladi) mavjud bolsa, bu yerda jamoat manzilini ishlating.</p>
<p>Agar hozirgi manzil notogri bolsa, brauzeringiz manzil satrida URLni ozgartiring va ornatishni qayta boshlang.</p>';
$string['pathsunsecuredataroot'] = 'Dataroot joylashuvi xavfsiz emas.';
$string['pathswrongadmindir'] = 'Administrator papkasi mavjud emas.';
$string['phpextension'] = '{$a} PHP kengaytmasi';
$string['phpversion'] = 'PHP versiyasi';
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
$string['welcomep20'] = 'Siz bu sahifani korayotganingizning sababi, siz kompyuteringizda <strong>{$a->packname} {$a->packversion}</strong> paketini muvaffaqiyatli ornatdingiz va ishga tushirdingiz. Tabriklaymiz!';
$string['welcomep30'] = '<strong>{$a->installername}</strong> ning ushbu relizi <strong>Moodle</strong> ishlaydigan muhitni yaratish uchun ilovalarni oz ichiga oladi, yani:';
+5 -7
View File
@@ -825,16 +825,12 @@ $string['lockoutemailbody'] = 'Hi {$a->firstname},
Your account with username {$a->username} on server \'{$a->sitename}\'
was locked out after multiple invalid login attempts.
To unlock the account immediately go to the following address
To unlock the account immediately, please click the link below:
{$a->link}
<a href="{$a->link}">Unlock account</a>
In most mail programs, this should appear as a blue link
which you can just click on. If that doesn\'t work,
then copy and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
If you need help, please contact the site administrator.
{$a->admin}';
$string['lockoutemailsubject'] = 'Your account on {$a} was locked out';
$string['lockouterrorunlock'] = 'Invalid account unlock information supplied.';
@@ -875,6 +871,7 @@ $string['manageformatsgotosettings'] = 'Default format can be changed in {$a}';
$string['managelang'] = 'Manage';
$string['manageqbehaviours'] = 'Manage question behaviours';
$string['manageqtypes'] = 'Manage question types';
$string['marketplaceavailablenotice'] = 'Browse and download plugins on <a href="{$a}" target="_blank" rel="noopener noreferrer">Moodle Marketplace <i class="fa fa-external-link" role="img" aria-label="Opens in new window" title="Opens in new window"></i></a>.';
$string['maturity50'] = 'Alpha';
$string['maturity100'] = 'Beta';
$string['maturity150'] = 'Release candidate';
@@ -972,6 +969,7 @@ $string['maxtimelimit'] = 'Maximum time limit';
$string['maxtimelimit_desc'] = 'To restrict the maximum PHP execution time that Moodle will allow without any output being displayed, enter a value in seconds here. 0 means that Moodle default restrictions are used. If you have a front-end server with its own time limit, set this value lower to receive PHP errors in logs. Does not apply to CLI scripts.';
$string['moodlebrandedapp'] = 'Branded Moodle app';
$string['moodlebrandedappreference'] = 'Alternatively, get a <a href="https://moodle.com/branded-app/">Branded Moodle app</a> with your own custom branding.';
$string['moodlenetremovalwarning'] = 'The MoodleNet service will be shut down on 20 April 2026. If you wish to continue using MoodleNet on your site, install the MoodleNet plugin from the <a href="https://github.com/moodlehq/moodle-tool_moodlenet" target="_blank">Moodle HQ GitHub repository <i class="fa fa-external-link" role="img" aria-label="Opens in new window" title="Opens in new window"></i></a> and connect it to a self-hosted MoodleNet instance. Following this, the MoodleNet profile ID field will be removed; please migrate that data if you are using it for other purposes.';
$string['noreplyaddress'] = 'No-reply address';
$string['noreplydomain'] = 'No-reply and domain';
$string['noreplydomaindetail'] = 'Settings for No-reply and configured domains';
+7 -3
View File
@@ -79,10 +79,14 @@ $string['emailnowexists'] = 'The email address you tried to assign to your profi
$string['emailupdate'] = 'Email address update';
$string['emailupdatemessage'] = 'Hi {$a->firstname},
You have requested a change of your email address for your account on {$a->site}. To confirm this change, please go to the following web address:
You have requested a change of your email address for your account on {$a->site}.
{$a->url}
The confirmation link will expire in <b>10 minutes</b>.
To confirm this change, please click the link below:
<a href="{$a->url}">Confirm email change</a>
The confirmation link will expire in <strong>10 minutes</strong>.
{$a->supportemail}';
$string['emailupdatesuccess'] = 'Email address of user <em>{$a->fullname}</em> was successfully updated to <em>{$a->email}</em>.';
+4
View File
@@ -40,6 +40,10 @@ $string['activitydate:closed'] = 'Closed:';
$string['activitydate:closes'] = 'Closes:';
$string['activitydate:opened'] = 'Opened:';
$string['activitydate:opens'] = 'Opens:';
$string['activitynavigation'] = 'Activity navigation';
$string['activitynavigation:jumptoactivity'] = 'Jump to activity';
$string['activitynavigation:nexta'] = 'Next activity: {$a}';
$string['activitynavigation:preva'] = 'Previous activity: {$a}';
$string['aria:coursecategory'] = 'Course category';
$string['aria:courseshortname'] = 'Course short name';
$string['aria:coursename'] = 'Course name';
+21 -33
View File
@@ -649,17 +649,12 @@ $string['emailagain'] = 'Email (again)';
$string['emailconfirm'] = 'Confirm your account';
$string['emailconfirmation'] = 'Hi {$a->firstname},
A new account has been requested at \'{$a->sitename}\'
using your email address.
A new account has been requested at \'{$a->sitename}\' using your email address.
To confirm your new account, please go to this web address:
To confirm your new account, please click the link below:
{$a->link}
<a href="{$a->link}">Confirm your account</a>
In most mail programs, this should appear as a blue link
which you can just click on. If that doesn\'t work,
then cut and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
{$a->admin}';
@@ -706,20 +701,14 @@ $string['emailnotfound'] = 'The email address was not found in the database';
$string['emailonlyallowed'] = 'This email cannot be used. Allowed email domains are: {$a}.';
$string['emailpasswordconfirmation'] = 'Hi {$a->firstname},
Someone (probably you) has requested a new password for your
account on \'{$a->sitename}\'.
Someone (probably you) has requested a new password for your account on \'{$a->sitename}\'.
To confirm this and have a new password sent to you via email,
go to the following web address:
To confirm this and have a new password sent to you via email, please click the link below:
{$a->link}
<a href="{$a->link}">Get a new password</a>
In most mail programs, this should appear as a blue link
which you can just click on. If that doesn\'t work,
then cut and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
If you need help, please contact the site administrator.
{$a->admin}';
$string['emailpasswordconfirmationsubject'] = '{$a}: Change password confirmation';
$string['emailpasswordconfirmmaybesent'] = '<p>If you supplied a correct username or unique email address then an email should have been sent to you.</p>
@@ -735,19 +724,14 @@ $string['emailpasswordconfirmsent'] = 'An email should have been sent to your ad
If you continue to have difficulty, contact the site administrator.';
$string['emailpasswordchangeinfo'] = 'Hi {$a->firstname},
Someone (probably you) has requested a new password for your
account \'{$a->username}\' on \'{$a->sitename}\'.
Someone (probably you) has requested a new password for your account \'{$a->username}\' on \'{$a->sitename}\'.
To change your password, please go to the following web address:
To change your password, please click the link below:
{$a->link}
<a href="{$a->link}">Change password</a>
In most mail programs, this should appear as a blue link
which you can just click on. If that doesn\'t work,
then cut and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
If you need help, please contact the site administrator.
{$a->admin}';
$string['emailpasswordchangeinfodisabled'] = 'Hi {$a->firstname},
@@ -764,14 +748,17 @@ $string['emailresetconfirmation'] = 'Hi {$a->firstname},
A password reset was requested for your account \'{$a->username}\' at {$a->sitename}.
To confirm this request, and set a new password for your account, please go to the following web address:
<a href="{$a->link}">{$a->link}</a>
To confirm this request, and set a new password for your account, please click the link below:
<a href="{$a->link}">Reset password</a>
(This link is valid for {$a->resetminutes} minutes from the time this reset was first requested.)
If this password reset was not requested by you, no action is needed.
If you need help, please contact the site administrator, {$a->admin}';
If you need help, please contact the site administrator.
{$a->admin}';
$string['emailresetconfirmationsubject'] = '{$a}: Password reset request';
$string['emailresetconfirmsent'] = 'An email has been sent to your address at <b>{$a}</b>.
<br />It contains easy instructions to confirm and complete this password change.
@@ -1245,6 +1232,7 @@ $string['loginstepsnone'] = '<p>Hi!</p>
<p>If someone else has already chosen your username then you\'ll have to try again using a different username.</p>';
$string['loginto'] = 'Log in to {$a}';
$string['loginagain'] = 'Log in again';
$string['loginrequired'] = 'Login required';
$string['logoof'] = 'Logo of {$a}';
$string['logout'] = 'Log out';
$string['logoutconfirm'] = 'Do you really want to log out?';
@@ -1512,10 +1500,10 @@ An account has been created for you at \'{$a->sitename}\'.
(You will be prompted to change your password when you log in for the first time.)
To start using \'{$a->sitename}\', log in at
{$a->link}
If you need help, contact the site administrator,
<a href="{$a->link}">Click to log in</a> and start using \'{$a->sitename}\'.
If you need help, contact the site administrator.
{$a->signoff}';
$string['newusers'] = 'New users';
$string['newwindow'] = 'New window';
+22
View File
@@ -1,5 +1,27 @@
# core (subsystem) Upgrade notes
## 4.5.10+
### Added
- When creating upgrade notes, the issue number will be inferred from the current Git branch name by default
For more information see [MDL-87100](https://tracker.moodle.org/browse/MDL-87100)
- There is a new Behat `toast_message` named selector to more easily assert the presence of Toast messages on the page
For more information see [MDL-87443](https://tracker.moodle.org/browse/MDL-87443)
- The `core/toast` JS module now accepts a `visuallyHidden` configuration parameter to render visually hidden toast messages for screen reader users.
For more information see [MDL-87993](https://tracker.moodle.org/browse/MDL-87993)
## 4.5.9
### Changed
- `\core\output\core_renderer::confirm()`'s `$displayoptions` parameter now also accepts a `headinglevel` option that developers can use to specify the heading level of the confirmation's heading. If not specified, the confirmation heading will be rendered in an `h4` tag.
For more information see [MDL-87694](https://tracker.moodle.org/browse/MDL-87694)
## 4.5.8
### Changed
+4 -4
View File
@@ -10525,13 +10525,13 @@ class admin_setting_configcolourpicker extends admin_setting {
return $data;
} else if (in_array(strtolower($data), $colornames)) {
return $data;
} else if (preg_match('/rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)/i', $data)) {
} else if (preg_match('/^rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)$/i', $data)) {
return $data;
} else if (preg_match('/rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)/i', $data)) {
} else if (preg_match('/^rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)$/i', $data)) {
return $data;
} else if (preg_match('/hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)/i', $data)) {
} else if (preg_match('/^hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)$/i', $data)) {
return $data;
} else if (preg_match('/hsla\(\d{0,3}\, ?\d{0,3}%,\d{0,3}%\, ?\d(\.\d)?\)/i', $data)) {
} else if (preg_match('/^hsla\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\, ?\d(\.\d)?\)$/i', $data)) {
return $data;
} else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
return $data;

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