Compare commits

..

393 Commits

Author SHA1 Message Date
Jun Pataleta d56aa41e9e Moodle release 4.1.14 2024-10-05 14:46:30 +08:00
AMOS bot 5778558dae Automatically generated installer lang files 2024-10-05 00:07:35 +00:00
Huong Nguyen b7bf868042 weekly release 4.1.13+ 2024-10-04 20:48:25 +07:00
Huong Nguyen 06c20c4675 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-10-04 20:48:23 +07:00
AMOS bot f30a2bb18e Automatically generated installer lang files 2024-10-04 00:07:34 +00:00
Michael Hawkins 92a2dcc96a MDL-83352 message: Update usercantbemessaged string 2024-10-03 17:48:46 +07:00
Shamim Rezaie 3a694fd6bf Merge branch 'MDL-83332-401' of https://github.com/meirzamoodle/moodle into MOODLE_401_STABLE 2024-10-03 02:25:33 +10:00
Paul Holden bb6a65da42 MDL-83180 reportbuilder: correct access check for viewing schedules. 2024-10-02 16:50:08 +08:00
meirzamoodle a11ffea697 MDL-83332 environment: Reverts the slashargument removal on MDL-62640
We are encountering Behat failures on MacOS and possibly Windows.

This reverts commit 3202109780.
2024-10-02 13:32:43 +07:00
Paul Holden 90d7307872 MDL-83181 reportbuilder: ensure audience correctness before delete. 2024-09-30 15:34:03 +07:00
Paul Holden 453dfc4f94 MDL-82386 block_rss_client: user access checks for feed edit/delete. 2024-09-27 11:50:19 +00:00
Huong Nguyen 75bffe9d96 weekly release 4.1.13+ 2024-09-27 18:44:25 +07:00
Huong Nguyen 22dc6301f0 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-09-27 18:44:23 +07:00
AMOS bot 7266fa1524 Automatically generated installer lang files 2024-09-27 00:07:35 +00:00
Jun Pataleta e0a09f31b1 Merge branch 'MDL-79288-401_blockaccessreview_colourcontrastcheck' of https://github.com/brickfield/moodle into MOODLE_401_STABLE 2024-09-26 19:45:12 +08:00
Michael Pound 72526c6f49 MDL-79288 block_accessreview: Improve heatmap colour contrast. 2024-09-26 08:14:55 +00:00
Huong Nguyen 5c0f33209e Merge branch 'MDL-62640-401' of https://github.com/meirzamoodle/moodle into MOODLE_401_STABLE 2024-09-26 11:08:03 +07:00
Sara Arjona 943ee4d484 weekly release 4.1.13+ 2024-09-24 16:52:22 +02:00
meirzamoodle acd31294f4 MDL-62640 environment: Remove the deprecated slash argument check 2024-09-23 13:08:45 +07:00
Sara Arjona b409287479 Merge branch 'MDL-81612_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2024-09-23 07:35:48 +02:00
Sara Arjona 13d98b9f6e weekly release 4.1.13+ 2024-09-20 16:36:32 +02:00
Sara Arjona 2b19e45d49 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-09-20 16:36:25 +02:00
Tim Hunt 7bde04468b MDL-81612 assign: test generation of extensions
Also, some other improvements.

* In Behat you can now use either idnumber of name to identify an
  assignment you are generating things for.
* create_submission generator methods now wants the cmid in $data['cmid']
  not the confusingly named 'assignid'. The old name is accepted, but
  generates a debugging warning. (That will fail the test, but in a way
  that explains how to fix it.)
2024-09-19 16:09:12 +01:00
AMOS bot 80b23a2afd Automatically generated installer lang files 2024-09-19 00:09:22 +00:00
Huong Nguyen da8e201e17 Merge branch 'MDL-81278-401' of https://github.com/mickhawkins/moodle into MOODLE_401_STABLE 2024-09-18 16:09:39 +07:00
AMOS bot d6950db0d9 Automatically generated installer lang files 2024-09-18 00:07:33 +00:00
Sara Arjona 661d66b452 Merge branch 'MDL-83076-401-v2' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-09-17 17:21:40 +02:00
Simey Lameze 74a5cc0965 MDL-83076 behat: workaround Firefox 130.0 failure 2024-09-17 11:08:24 +08:00
Michael Hawkins 768a82b583 MDL-81278 core: Update security.txt expiry 2024-09-16 14:51:06 +08:00
AMOS bot 04d3db34ee Automatically generated installer lang files 2024-09-12 00:07:33 +00:00
AMOS bot c871b4d5f8 Automatically generated installer lang files 2024-09-11 00:07:36 +00:00
Huong Nguyen d3749a82ab weekly release 4.1.13+ 2024-09-10 20:03:11 +07:00
Huong Nguyen d7610a257f Merge branch 'MDL-83047-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-09-10 10:48:21 +07:00
Jun Pataleta 62b840648d MDL-83047 theme_boost: Add upgrade note for theme plugins 2024-09-10 10:34:15 +08:00
Jun Pataleta 5f93e6ab03 MDL-83047 theme_boost: Apply background colour only on select layouts
* Apply the styling of `background-color` for `.page-header-headings`
only in the maintenance and secure layouts.
2024-09-09 17:01:33 +08:00
Jun Pataleta 69beec3d2a weekly release 4.1.13+ 2024-09-07 06:45:53 +08:00
Jun Pataleta d2c9c79c0e Merge branch 'MDL-63399-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-09-06 12:29:34 +08:00
Simey Lameze 1ce15ab9b1 MDL-63399 behat: update existing tests to use the better download steps
Co-authored-by: Tim Hunt <T.J.Hunt@open.ac.uk>
2024-09-06 12:19:56 +08:00
Simey Lameze a24d8a0d2a MDL-63399 behat: new steps to verify downloads
Co-authored-by: Tim Hunt <T.J.Hunt@open.ac.uk>
2024-09-06 12:18:26 +08:00
Huong Nguyen 8835ecef16 Merge branch 'MDL-82948-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-09-06 11:01:41 +07:00
Huong Nguyen 86c79ed091 Merge branch 'MDL-82677-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-09-06 10:51:25 +07:00
Huong Nguyen d4f5e92ee1 Moodle release 4.1.13 2024-08-30 13:50:56 +07:00
Huong Nguyen 2c58f9533b weekly release 4.1.12+ 2024-08-29 18:10:13 +07:00
Jun Pataleta 34077a9190 MDL-82948 theme_boost: Make <blockquote>'s border a bit darker 2024-08-29 16:04:52 +08:00
Paul Holden 42726d645c MDL-82365 mod_lesson: stricter equality checks of activity password. 2024-08-29 12:13:57 +08:00
Jun Pataleta 02d08d2e4d Merge branch 'MDL-82651-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-08-29 11:59:49 +08:00
Jun Pataleta 9b90cf6584 Merge branch 'MDL-82802-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-08-29 11:40:09 +08:00
Huong Nguyen bdbc077efc Merge branch 'MDL-82898-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-08-29 09:50:58 +07:00
Paul Holden fb06894e75 MDL-76962 auth_oauth2: users can only delete their own linked logins. 2024-08-29 10:33:39 +08:00
Simey Lameze 6b4946e0a8 MDL-82651 behat: remove unnecessary steps 2024-08-28 13:10:20 +08:00
Simey Lameze 057f63909f MDL-82651 behat: wait for button to exist before clicking 2024-08-28 13:10:20 +08:00
Angelia Dela Cruz ea84a13152 MDL-82898 enrol_self: Behat to test student enrolment using key 2024-08-28 12:14:27 +08:00
Marina Glancy 9639feb9a3 MDL-82567 core: Add has_capability method
Co-authored-by: David Woloszyn <david.woloszyn@moodle.com>
Co-authored-by: Jun Pataleta <jun@moodle.com>
2024-08-24 06:30:25 +00:00
Shamim Rezaie f009e2621e Merge branch 'MDL-82006-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-08-24 16:21:45 +10:00
Sara Arjona 3449379bca weekly release 4.1.12+ 2024-08-23 16:04:18 +02:00
Sara Arjona c77d6a79ec Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-08-23 16:04:13 +02:00
Jake Dallimore d8558b1d42 Merge branch 'MDL-82787-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-08-23 11:52:07 +08:00
Huong Nguyen b8614dee98 Merge branch 'MDL-82841-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-08-22 08:44:26 +07:00
Sara Arjona b852d34285 Merge branch 'MDL-82655-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-08-21 15:54:42 +02:00
Angelia Dela Cruz 250ef211f0 MDL-82841 mod_workshop: Behat for submissions with set # of attachments 2024-08-21 19:28:12 +08:00
Angelia Dela Cruz 2ebcdda606 MDL-82655 mod_workshop: Behat test for manual and random allocation 2024-08-21 18:21:57 +08:00
Andrew Nicols ce89c63b0b MDL-82787 behat: Correct stop of webdriver on failure 2024-08-21 08:46:34 +08:00
AMOS bot 8acc343fb8 Automatically generated installer lang files 2024-08-19 00:07:37 +00:00
Huong Nguyen 15a54747b9 weekly release 4.1.12+ 2024-08-16 08:40:46 +07:00
Paul Holden 7666131199 MDL-82006 reportbuilder: move filter Behat scenarios inside component. 2024-08-15 13:54:13 +01:00
Paul Holden 8b54213ad0 MDL-82802 tool_xmldb: allow numeric characters in table names. 2024-08-15 12:00:51 +01:00
Jun Pataleta 561a371423 Merge branch 'MDL-82782-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-08-15 11:48:17 +08:00
Jun Pataleta 6cc39f8e94 Merge branch 'MDL-82798-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-08-15 11:12:43 +08:00
Huong Nguyen 58e54a3d33 Merge branch 'MDL-82758-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-08-15 09:38:41 +07:00
Huong Nguyen eb1c1e79a1 MDL-82798 qtype_ddmarker: Fix Behat failure 2024-08-15 09:17:41 +07:00
Jun Pataleta e7c973a195 Merge branch 'MDL-82792-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2024-08-15 10:14:24 +08:00
Andrew Nicols a982ebad0c MDL-82782 behat: Support runtime scaling of Behat windows 2024-08-14 21:51:36 +08:00
Sara Arjona 27692e70b5 MDL-82792 h5p: Fix random behat failures
We've started seeing various failures in Firefox and Classic.
Increasing the default window size fixes them.
2024-08-14 12:18:47 +02:00
Sara Arjona 05db16c24d Merge branch 'MDL-82682-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-08-14 09:13:41 +02:00
Simey Lameze 0aaf7fe953 MDL-82682 behat: increase window size to avoid fails 2024-08-14 10:02:20 +08:00
Ilya Tregubov aea9770cfc Moodle release 4.1.12 2024-08-10 14:54:42 +08:00
Jun Pataleta 05fe39a88f MDL-82758 gha: Remove reference to the master branch 2024-08-09 16:18:06 +08:00
Ilya Tregubov 44305df587 weekly release 4.1.11+ 2024-08-09 09:34:41 +08:00
Huong Nguyen 638caa1355 MDL-82745 filter_tex: Improve PHPUnit test 2024-08-08 21:08:59 +08:00
Michael Hawkins 6d29dc587e MDL-82745 filter_tex: Improve filter deny list 2024-08-08 11:41:11 +07:00
Michael Hawkins 0f03ac03b5 MDL-82576 qtype_calculated: Improve formula regexes 2024-08-08 11:12:28 +07:00
Safat a11b849ebe MDL-82395 tool_xmldb: Table layout update 2024-08-08 10:02:00 +07:00
Jun Pataleta 4cfa5bca7c Merge branch 'MDL-82560-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-08-08 10:44:33 +08:00
Paul Holden 19ad8459d7 MDL-79373 admin: ensure all current password configs are defined.
Ensure the admin preset export tool has an accurate record of each,
to be observed as sensitive settings when creating an export.
2024-08-08 09:23:20 +07:00
Angelia Dela Cruz 3f6919adc7 MDL-82560 mod_forum: Test that students can only start one discussion 2024-08-08 10:18:48 +08:00
Jun Pataleta 5d2ce59559 Merge branch 'MDL-77784-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-08-08 09:47:32 +08:00
Paul Holden e08dd90c32 MDL-81394 restore: format backup file metadata details safely. 2024-08-07 14:05:20 +08:00
raortegar 26d7f16686 MDL-81718 core: Improve localstorage validation 2024-08-06 10:36:01 +07:00
Paul Holden 5b168b055f MDL-82558 h5p: escape template error/exception content. 2024-08-05 11:03:03 +08:00
Jun Pataleta 7ad936dcbf MDL-82677 theme: Set a background colour for page heading
* Set a background colour for the .page-header-headings class.
* Assign the .page-header-headings class to the maintenance mode
template's page header div.
2024-08-02 17:25:12 +08:00
Paul Holden 29ab56c4d2 MDL-82392 blocks: consistent cleaning of block instance names. 2024-08-02 01:40:24 +00:00
Paul Holden 6c640dacf4 MDL-82261 mod_feedback: ensure only non-respondents can be messaged. 2024-08-02 01:40:24 +00:00
Paul Holden 234d24dfea MDL-82390 badges: correct context check when deleting badges. 2024-08-02 01:40:24 +00:00
meirzamoodle 835505681c MDL-82136 curl: Send credentials to redirect URL if allowed
Curl has the option CURLOPT_UNRESTRICTED_AUTH. If true, curl will send
the credentials to a different host. If false, they will not be sent.

CURLOPT_UNRESTRICTED_AUTH can only work if the CURLOPT_FOLLOWLOCATION
option is true. The filelib forces the CURLOPT_FOLLOWLOCATION option
to be false, because all redirects are emulated at the PHP level. So,
in this case, the CURLOPT_UNRESTRICTED_AUTH option is only being used
in our logic and will not work as you might expect it to.

This patch works almost the same as CURLOPT_UNRESTRICTED_AUTH in ideal
conditions. It will check whether the host is different. If so, the
system will check what value CURLOPT_UNRESTRICTED_AUTH has. If it is
not specified, then by default, it will be false. If false, then
credentials will not be sent.
2024-08-02 01:40:24 +00:00
Paul Holden e930abfcf8 MDL-82262 mod_feedback: fix broken bulk message sesskey checks. 2024-08-02 01:40:24 +00:00
Paul Holden cb06a0a271 MDL-64984 mod_glossary: capability check when restoring global type. 2024-08-02 01:40:24 +00:00
Ilya Tregubov f8f19a4d8e MDL-79541 core_grades: Add missing user info to search services.
Seems just fullname is not enough. So added all name fields.
2024-08-02 01:40:24 +00:00
Ilya Tregubov e2a8d063ee MDL-79541 core_grades: Fix singleview and user report selectors. 2024-08-02 01:40:24 +00:00
Ilya Tregubov 1fd2f0364c MDL-79541 core_grades: Hidden user fields check for search.
If some user profile fields are hidden on some grade reports
users without permission were able to see those.
2024-08-02 01:40:24 +00:00
Ilya Tregubov 7f450dcea3 weekly release 4.1.11+ 2024-08-02 09:31:07 +08:00
Ilya Tregubov c39a3357ed Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-08-02 09:31:04 +08:00
Paul Holden cc10212366 MDL-77784 quiz_grading: editor agnostic Behat scenario steps. 2024-08-01 14:19:30 +01:00
Huong Nguyen df2b3113d9 Merge branch 'MDL-82598-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-08-01 10:27:40 +07:00
AMOS bot d4f428311a Automatically generated installer lang files 2024-08-01 00:07:26 +00:00
Sara Arjona e2c677c3e8 Merge branch 'MDL-72876-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-07-31 17:15:38 +02:00
Sara Arjona 80b3cb4951 Merge branch 'MDL-82596-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-07-31 16:46:29 +02:00
Simey Lameze dfa5124909 MDL-82598 behat: improve example submission test 2024-07-31 09:29:01 +08:00
AMOS bot d3a6c97a43 Automatically generated installer lang files 2024-07-31 00:07:24 +00:00
Jun Pataleta 316a50f2b7 MDL-72876 theme_classic: Enclose welcome message in Bootstrap card 2024-07-30 19:40:41 +08:00
Ilya Tregubov 2883fd28e4 Merge branch 'MDL-80345-401' of https://github.com/MartinGauk/moodle into MOODLE_401_STABLE 2024-07-29 11:35:36 +08:00
Paul Holden aa7f8f9e09 MDL-82596 behat: switch manual private files upload to generator. 2024-07-28 19:29:54 +01:00
Paul Holden c4b56bb393 MDL-82596 behat: tidy up existing usage of private files generator.
Filename is no longer required (since 83db25c330), given it never
did anything so can be removed. Requiring the "Private files" block
also isn't necessary since original switch to generator methods.
2024-07-28 19:15:19 +01:00
Jun Pataleta 156d214635 weekly release 4.1.11+ 2024-07-26 13:36:00 +08:00
Jun Pataleta 31b07a6298 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-07-26 13:35:57 +08:00
Jun Pataleta 2c82466444 Merge branch 'MDL-82373-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-07-26 13:18:50 +08:00
Andrew Nicols 91e70280a2 MDL-82373 core_message: Make message drawer router behat-aware
The drawer router was modifying the DOM in a variety of ways and hiding
content, without any pendingJS checks. As a result, there were cases
where a part of the UI was not visible, but had been rendered and was in
the DOM.

This change wraps the router Go method in a pendingJS call that only
resolves when the router transition completes.

Additionally the lazy load manager had a similar issue.
2024-07-26 12:45:30 +08:00
Huong Nguyen 68e93dbfee Merge branch 'MDL-82527-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-07-25 10:50:00 +07:00
Jun Pataleta c12d07d9d4 Merge branch 'MDL-82373-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-07-25 11:30:52 +08:00
Jun Pataleta a29ef48500 Merge branch 'MDL-77706-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-07-25 11:02:16 +08:00
Huong Nguyen 0fae681a7c Merge branch 'MDL-82553-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-07-25 09:18:16 +07:00
Shamim Rezaie 149df44e29 Merge branch 'MDL-82551-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-07-25 11:53:53 +10:00
Huong Nguyen da1fafe59d Merge branch 'MDL-82422-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-07-25 08:37:31 +07:00
AMOS bot 3821c707b8 Automatically generated installer lang files 2024-07-25 00:07:59 +00:00
Jun Pataleta 1c7a5d9972 Merge branch 'MDL-81803-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-07-24 16:57:26 +08:00
Angelia Dela Cruz 0fdbf26fd0 MDL-82422 mod_workshop: Behat for how strict assessment comparison is 2024-07-24 14:35:06 +08:00
Jun Pataleta 4f3c3432cd MDL-82551 block_recentlyaccesseditems: Mark up decorative image properly
This change applies the same change in MDL-70829 to make the
accessibility Behat test for the welcome message to pass.
2024-07-24 12:05:25 +08:00
Jun Pataleta b6715bb1ae MDL-82553 behat: Run accessibility tests using WCAG 2.2 success criteria 2024-07-22 14:38:19 +08:00
Jun Pataleta 40094ddbe0 MDL-82551 core: Set welcome message heading to level 1 2024-07-22 14:21:22 +08:00
AMOS bot 8535c32bf3 Automatically generated installer lang files 2024-07-21 00:07:33 +00:00
Ilya Tregubov 6b90c69bda weekly release 4.1.11+ 2024-07-19 09:30:44 +08:00
Paul Holden 6f3337dcba MDL-82527 h5p: improve editor Behat scenarios for embedding content.
* Move the shared drag fixture out of the Atto plugin;
* Switch to using faster generators for populating private files;
* Clarify required fields for private files generator
2024-07-18 09:45:53 +01:00
Sara Arjona ff33f82ac2 Merge branch 'MDL-82353-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-07-17 17:08:31 +02:00
Andrew Nicols bf6cf572ca MDL-82373 grade_singleview: Run behat tests in large window
For recent versions of Chrome, we must use a bigger window to avoid
Chrome chrome issues.
2024-07-17 22:58:57 +08:00
Andrew Nicols 433109e2ac MDL-82373 contentbank: Pause in Behat before interacting with h5p
It seems that the loading of the h5p content upsets other interactions
with the page in Firefox as it loads. Unfortunately I haven't found a
reliable way to handle this with pendingJS yet.

This is the poor man's fix and we should find a better solution.
2024-07-17 14:45:23 +08:00
Andrew Nicols 07bfca9041 MDL-82373 question: Make window larger in behat tests
I would suggest that this is a stop gap to solve some failing tests. We
really need to look at whether we can improve the usability of this
interface on smaller displays as a longer-term fix.
2024-07-17 14:45:22 +08:00
Andrew Nicols b2320ccd71 MDL-82373 behat: Wait for alerts before accepting/dismissing them 2024-07-17 14:45:22 +08:00
Andrew Nicols 5e4ba1f036 MDL-82373 core: Make CollapsibleRegion more tolerant to behat 2024-07-17 14:45:22 +08:00
Andrew Nicols 7e17945088 MDL-82373 tool_usertours: Ensure that behat waits for tours to show/hide 2024-07-17 14:45:22 +08:00
Andrew Nicols 535e970ad3 MDL-82373 behat: Stop killing the entire Behat run on driver error
If there's a driver error, for example from a step taking too long, then
this kills the entire Behat run and we have to start from scratch.

This change instead throws away the original connection and starts a new
one to try and continue the test.
2024-07-17 14:45:21 +08:00
Andrew Nicols dd8595035f MDL-82373 behat: Stop calling ensure_node_is_visible before click
W3C WebDriver Element::Click, Element::Clear, and Element::SendKeys all
state that the WebDriver implementation (chromedriver, geckodriver,
edgedriver) should scroll the element into view if it is not already
visible.

It is wrong for us to check if the element is visible or not before
calling these as it may not be but will during the click/clear/type
event.
2024-07-17 14:45:21 +08:00
Andrew Nicols 06ae387ca5 MDL-82373 mod_lesson: Fix failing behat tests for Selenium 4
Recent versions of Chrome do not re-render the page but return previous
page state from cache. The page state had the Submit button disabled
before navigating away.

Refreshing the page addresses this.
2024-07-17 14:45:21 +08:00
Angelia Dela Cruz df48a73c5c MDL-82353 gradingform: Behat to verify marking guide can be set to draft 2024-07-16 17:30:44 +08:00
Andrew Nicols 70bbb44030 MDL-77706 atto_link: Work around Mozilla bug 1906559
This upstream bug prevents creation of an anchor with a hyperlink where
the content has a block-like display.

The workaround is to wrap the content in a span, set the display to
inline, call the `createLink` command on the span, move the content out
of the span, and then remove it.

This is only done for Firefox-based browsers.
2024-07-16 15:38:21 +08:00
Andrew Nicols 70ab78478f MDL-66903 core: Check PHPUNIT_TEST defined before using 2024-07-15 13:42:10 +07:00
Huong Nguyen 8a66b2b99a Merge branch 'MDL-66903-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-07-15 13:22:51 +07:00
Andrew Nicols ee5ac3f1f3 MDL-66903 core: Introduce \core_component::reset to replace hacks
Traditionally we have done this using reflection and it would be better
to do this in a 'proper' fashion in case we ever need to change how it
works.
2024-07-15 12:22:20 +08:00
Andrew Nicols 9e71503ab3 MDL-66903 core: Add option to get core in list of components
Note: This change is backported from MDL-81063.
2024-07-15 12:22:19 +08:00
Andrew Nicols 651ecb52d9 MDL-66903 core: Add helper to get component name from classname
Note: This change is backported from MDL-81063.
2024-07-15 12:22:19 +08:00
Andrew Nicols 85845b13d7 MDL-66903 testing: Helper to load fixture files 2024-07-15 12:22:17 +08:00
Andrew Nicols 7b0946129c MDL-66903 testing: Add support for a \tests\ namespace during tests
This commit:
- introduces a \tests\ sub-namespace for use in unit tests only
- the path to this the tests/classes directory of the owning parent
- files here are excluded from unit test runs

This is agreed per policy in MDL-80855.
2024-07-15 12:21:54 +08:00
Paul Holden 7d7cfee0b7 MDL-81803 tool_dataprivacy: observe disabled expiration time.
A non-positive integer value should indicate "No time limit".
2024-07-12 11:05:19 +01:00
Huong Nguyen 6751ba6500 weekly release 4.1.11+ 2024-07-12 08:42:21 +07:00
Jun Pataleta 6d387e4b2b Merge branch 'MDL-81265-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-07-11 12:08:30 +08:00
Jun Pataleta ed3b73d9fc MDL-81265 mod_workshop: Verify "Setup phase" heading with "should exist" 2024-07-11 11:54:39 +08:00
Laurent David f95b010ec5 MDL-81265 mod_workshop: Fix accessibility issues
* Page title should be h2
* Contrasts in the table headers (for submission, assessment and grading)
should be 4:1 at least
2024-07-11 11:54:39 +08:00
Andrew Nicols 97060da478 Merge branch 'MDL-82431-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-07-11 11:47:18 +08:00
Huong Nguyen 9715fd56da Merge branch 'MDL-79717-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-07-11 09:36:07 +07:00
Huong Nguyen 7e3d7fb02d Merge branch 'MDL-76654-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-07-11 09:20:23 +07:00
Paul Holden a9cc2ebb6e MDL-82431 phpunit: correct unit test filename and classname.
Standardise the same test cases, e.g. make final, covers notation,
static data providers, namespaces, etc.

Once the tests run, a couple of them failed. They required changes
to assertions to make them pass.
2024-07-10 19:06:11 +01:00
Paul Holden 1a7ec4cf3d MDL-79717 phpunit: ensure unique data provider keys in tests.
Duplicate data provider keys were overwriting and/or duplicating
one another, leading to some cases being skipped.

Other "duplicate array key" errors were picked up by `phpcs` in
this dragnet across all tests, which have also been fixed.
2024-07-10 14:09:00 +01:00
Huong Nguyen 7715c626ad Merge branch 'MDL-82236-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-07-10 09:05:01 +07:00
Angelia Dela Cruz 1c9098aeb8 MDL-82236 mod_data: Behat to display DB activity entry rating 2024-07-09 16:28:28 +08:00
Sara Arjona 5d18431087 Merge branch 'MDL-81296-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-07-09 08:50:54 +02:00
Angelia Dela Cruz 0d27c76e34 MDL-81296 mod_data: Behat test for data activity readonly availability 2024-07-09 13:06:35 +08:00
Simey Lameze 8deeed9771 MDL-76654 behat: fixes and improvements to self assessment test 2024-07-08 11:22:39 +08:00
Angelia Dela Cruz 7893a34171 MDL-76654 behat: Coverage for workshop self-assessment 2024-07-08 11:22:39 +08:00
Andrew Nicols e6966b7ae7 MDL-66903 testing: Fix whitespace 2024-07-05 22:06:09 +08:00
Andrew Nicols 3e81ebecb6 MDL-66903 testing: Reset CFG and component after test
This change moves the reset of global test state to the finally section
rather than doing it only if the test passes.

Previously if a test which modifies the `core_component` internals
failed, it would not reset the internal state and impact subsequent
tests.
2024-07-05 22:06:09 +08:00
Ilya Tregubov f9c5802f4e weekly release 4.1.11+ 2024-07-05 09:02:18 +08:00
Sara Arjona e6ee7a5b1c Merge branch 'MDL-80907-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-07-04 11:19:14 +02:00
Huong Nguyen 17081ee902 MDL-80907 behat: Fix incorrect column selectors 2024-07-04 16:01:53 +07:00
Huong Nguyen e817dd70db Merge branch 'MDL-80907-401' of https://github.com/marinaglancy/moodle into MOODLE_401_STABLE 2024-07-04 11:14:56 +07:00
Ilya Tregubov bc30a904f4 Merge branch 'MDL-63670-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-07-04 10:58:57 +08:00
Sara Arjona b2d9c6ff81 Merge branch 'MDL-82363-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-07-03 08:24:03 +02:00
Huong Nguyen 18dae18e20 MDL-82363 Calendar: Responsiveness improvement for calendar block
Hide the month name if the screen is too small
2024-07-03 11:03:02 +07:00
Sara Arjona 18c7f3cdf3 weekly release 4.1.11+ 2024-06-27 16:37:06 +02:00
Sara Arjona f0b462209a Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-06-27 16:37:02 +02:00
Jun Pataleta 974db25816 Merge branch 'MDL-79809-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-06-27 11:00:52 +08:00
Andrew Nicols 7a02ae5644 MDL-63670 core_message: Tests should not use another testcase as base 2024-06-27 11:00:49 +08:00
Huong Nguyen a9f24ce125 MDL-79809 environment: Moodle 4.5 requirements 2024-06-24 09:37:38 +07:00
Huong Nguyen a68fcabc65 MDL-79809 environment: Add missing Moodle 4.4 requirements 2024-06-24 09:37:38 +07:00
Huong Nguyen 2978ffc4a5 MDL-79809 environment: Add recommended custom check
Add support for custom environment checks to have a recommended
option in addition to required an optional.
2024-06-24 09:37:38 +07:00
AMOS bot a694661d5c Automatically generated installer lang files 2024-06-22 00:13:59 +00:00
Martin Gauk 7d77854fd6 MDL-80345 core_lock: deal with hash collisions in postgres_lock_factory 2024-06-20 17:44:24 +02:00
Sara Arjona 17124e62a9 weekly release 4.1.11+ 2024-06-20 16:31:16 +02:00
Sara Arjona d9ae012d15 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-06-20 16:31:12 +02:00
AMOS bot c03706fafb Automatically generated installer lang files 2024-06-20 00:16:13 +00:00
Huong Nguyen f77aa8c4a7 Merge branch 'MDL-81249-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-06-19 09:19:35 +07:00
Angelia Dela Cruz 4044abd156 MDL-81249 gradingform: Behat test that grades are retained 2024-06-18 20:11:33 +08:00
Angelia Dela Cruz 6164556c85 MDL-81249 gradingform: Enhance Behat test for deleting marking guide 2024-06-18 17:47:06 +08:00
Huong Nguyen 69a8f920d4 Merge branch 'MDL-82148_401' of https://github.com/stronk7/moodle into MOODLE_401_STABLE 2024-06-18 16:28:34 +07:00
Eloy Lafuente (stronk7) ff263b6dbc MDL-82148 phpunit: Remove all cases of multiple @coversDefaultClass
Only one by class is allowed (to be used as shortcut exclusively).
2024-06-14 16:05:48 +02:00
Ilya Tregubov 763c71194b weekly release 4.1.11+ 2024-06-14 08:57:21 +08:00
Huong Nguyen 19ac0728f2 Merge branch 'MDL-81332-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-06-12 13:16:45 +07:00
Angelia Dela Cruz 39cb2a03e5 MDL-81332 core_course: Behat test for grade and activity reports display 2024-06-11 16:57:53 +08:00
Andrew Nicols c8c84b4af1 Moodle release 4.1.11 2024-06-07 14:55:32 +08:00
Jun Pataleta 5ec01a7226 weekly release 4.1.10+ 2024-06-06 22:30:50 +08:00
Jun Pataleta f878e05d62 NOBUG: Fixed file access permissions 2024-06-06 22:30:49 +08:00
Huong Nguyen aa0e2cffdd Merge branch 'MDL-81989-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-06-06 22:23:26 +08:00
meirzamoodle c18b59808c MDL-81890 assign: confirm_sesskey fixed in view_fix_rescaled_null_grades 2024-06-06 22:23:26 +08:00
meirzamoodle a0d8c025f7 MDL-81890 enrol_lti: Fix sesskey checks 2024-06-06 22:23:25 +08:00
meirzamoodle 137d311fd1 MDL-81890 course: Fix sesskey checks 2024-06-06 22:23:25 +08:00
meirzamoodle 57f20b6cb3 MDL-81890 tool_moodlenet: Fix sesskey checks 2024-06-06 22:23:25 +08:00
Paul Holden a10506b8d7 MDL-81778 mod_bigbluebuttonbn: access checks when getting meeting URL. 2024-06-06 22:23:25 +08:00
Stevani Andolo 28b8fc9896 MDL-81412 calendar: Sanitise calendar event names 2024-06-06 22:22:49 +08:00
lameze f55b829f44 MDL-81989 install: remove unnecessary closing div tags 2024-06-05 14:06:23 +08:00
Cameron Ball 836b2c23a2 MDL-81774 curl: Strip auth headers when redirecting to different host 2024-06-05 02:00:21 +00:00
Huong Nguyen 0068701bc6 Merge branch 'MDL-81751-MOODLE_401_STABLE' of https://github.com/durzo/moodle into MOODLE_401_STABLE 2024-06-05 08:57:27 +07:00
Huong Nguyen 83a978d1cd Merge branch 'MDL-81955-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-06-04 10:34:35 +07:00
Juan Leyva d05795db8e MDL-80959 tool_mobile: Use different user keys for QR and auto login 2024-06-04 10:34:50 +08:00
Paul Holden 629a7066be MDL-81955 assignfeedback_editpdf: ensure ghostscript present in tests. 2024-05-31 07:14:17 +01:00
Ilya Tregubov 058b11e57b Merge branch 'MDL-77685-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-05-31 09:06:09 +08:00
Sara Arjona aa230649d9 weekly release 4.1.10+ 2024-05-30 17:00:42 +02:00
Paul Holden f8cce391b2 MDL-77685 grade: remove Behat specific report rendering hacks.
These changes follow the same approach as that taken elsewhere in
the Gradebook in 3643f48e0f.
2024-05-30 09:43:11 +01:00
Mihail Geshoski a7d476c35f MDL-77685 core_grades: Fix row_column_of_table_should_contain()
Backport from MDL-77632.
2024-05-30 09:42:26 +01:00
Andrew Nicols 5425fb3d79 MDL-81897 tool_mobile: Coding style fix 2024-05-28 11:45:05 +08:00
Andrew Nicols 587d737690 Merge branch 'MDL-81897-401' of https://github.com/jleyva/moodle into MOODLE_401_STABLE 2024-05-28 11:44:49 +08:00
Juan Leyva 31b68a2b5d MDL-81897 tool_mobile: Force partitioned cookies on inapp browser 2024-05-17 10:23:24 +02:00
Jordan Tomkinson a48f00d940 MDL-81751 ddl: new reserved keywords in Aurora MySQL database engine 2024-05-16 13:41:25 +01:00
Huong Nguyen f7da739ea0 weekly release 4.1.10+ 2024-05-16 18:25:49 +07:00
Huong Nguyen ebb346cd8e Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-05-16 18:25:48 +07:00
Huong Nguyen b39d8d5027 Merge branch 'MDL-79838-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-05-16 09:22:50 +07:00
Angelia Dela Cruz cf48f359ad MDL-79838 mod_h5pactivity: Behat for H5P activity grade settings control 2024-05-15 17:00:50 +08:00
Sara Arjona 8ac29c9422 Merge branch 'MDL-81805-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-05-14 17:05:57 +02:00
AMOS bot 7f3617860c Automatically generated installer lang files 2024-05-12 00:14:21 +00:00
Ilya Tregubov b35f70ce14 weekly release 4.1.10+ 2024-05-10 09:09:52 +08:00
Jun Pataleta 77d0c0caa9 Merge branch 'MDL-80972-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-05-09 11:42:40 +08:00
info@eWallah.net c2193308a6 MDL-80972 behat: Behat check_server_status can provide better info. 2024-05-09 11:08:04 +08:00
Huong Nguyen 9456911fe9 Merge branch 'MDL-80453-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-05-09 09:49:19 +07:00
Huong Nguyen bd2c8fdd54 Merge branch 'MDL-79702-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-05-09 09:34:41 +07:00
Angelia Dela Cruz 234d81e39b MDL-80453 mod_glossary: Behat for different glossary format display 2024-05-08 17:41:08 +08:00
Marina Glancy 9747b5b26b MDL-80907 various: fixes to incorrect column selectors in behat 2024-05-07 19:33:47 +01:00
Marina Glancy 508d462fa7 MDL-80907 behat: be more precise in selecting table rows 2024-05-07 19:33:35 +01:00
Simey Lameze f687e02dec MDL-81805 behat: remove unnecessary @javascript from tests 2024-05-07 09:45:17 +08:00
Simey Lameze 72c3038925 MDL-79702 behat: improvements and fixes to new tests
This commit adds the handling of tags as string separated by comma
to forum and glossary modules.
2024-05-06 20:02:57 +08:00
Angelia Dela Cruz d393030c58 MDL-79702 core_tag: Behat for activity tags deletion during course reset 2024-05-06 20:02:57 +08:00
Huong Nguyen d68ea84924 Merge branch 'MDL-80589-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-05-06 09:37:52 +07:00
Ilya Tregubov 85825dcc55 weekly release 4.1.10+ 2024-05-03 08:22:54 +08:00
Ilya Tregubov 80f152296c Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-05-03 08:22:51 +08:00
AMOS bot 48aa9aae56 Automatically generated installer lang files 2024-05-03 00:10:35 +00:00
Simey Lameze 025065daf5 MDL-80589 behat: fixes and improvements to new tests 2024-05-02 13:34:16 +08:00
Angelia Dela Cruz 49be0febdc MDL-80589 qtype_numerical: Behat to add, edit, preview numeric question 2024-05-02 13:34:16 +08:00
Huong Nguyen 73f57aab98 Merge branch 'MDL-79851-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-05-01 16:12:51 +07:00
Angelia Dela Cruz 457f0e9384 MDL-79851 mod_h5pactivity: Behat for h5p activity access control 2024-05-01 14:26:40 +08:00
Sara Arjona 4b15c803e2 Merge branch 'MDL-81632-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-04-30 15:09:54 +02:00
Huong Nguyen 50aff515b7 Merge branch 'MDL-79149-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-04-30 11:49:03 +07:00
Paul Holden 5b33398f07 MDL-79149 report_participation: more resilient activity access test.
Use the "Forum" module rather than the "Book" module, because the latter
emits two events when a student views it - if these two events happened
either side of a single second, then the report  would count two distinct
views (when it was trying to assert only one).
2024-04-26 16:10:22 +01:00
Sara Arjona 47568f31c5 Merge branch 'MDL-77445-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-04-26 10:40:50 +02:00
Sara Arjona eba83406da weekly release 4.1.10+ 2024-04-25 15:43:45 +02:00
Sara Arjona 137c6f36bb Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-04-25 15:43:41 +02:00
Huong Nguyen d9043b324d MDL-77998 Behat: Fix Behat failure 2024-04-25 10:38:18 +07:00
Paul Holden e57e560385 MDL-81632 block_recentlyaccesseditems: deterministic item ordering.
Behat tests of this functionality could trigger the unlikely (in real
world usage) scenario where a user has an identical "timeaccess" value
for multiple course activities.

This led to random failures in said tests when the DB ordered items in
apparently random order, where the "timeaccess" value was equal.
2024-04-24 20:57:46 +01:00
Sara Arjona eebd5742b6 Merge branch 'MDL-77998-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-04-24 14:48:16 +02:00
Angelia Dela Cruz 0837a24c05 MDL-77998 Behat: Add behat coverage for preferences navigation 2024-04-24 19:49:26 +08:00
Angelia Dela Cruz f2e48b39f6 MDL-77445 Behat: Coverage for activity chooser navigation 2024-04-24 18:09:22 +08:00
Huong Nguyen 5060c53176 Merge branch 'MDL-80107-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-04-24 08:36:46 +07:00
Sara Arjona 3f392e934e Merge branch 'MDL-81209-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-04-23 15:32:42 +02:00
Sara Arjona c1e4679ed5 Merge branch 'MDL-81457-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-04-23 14:57:51 +02:00
Pol Torrent i Soler 9254b7e75a MDL-81622 core: fix str_contains usage in cookie_helper 2024-04-23 12:52:58 +02:00
Angelia Dela Cruz 1d609ca0ee MDL-80107 gradingform: Behat test for defining marking guide 2024-04-23 15:40:26 +08:00
AMOS bot 61fabd3e73 Automatically generated installer lang files 2024-04-22 00:10:24 +00:00
Jun Pataleta e8d03fdae4 Moodle release 4.1.10 2024-04-20 15:57:26 +08:00
Huong Nguyen c8922328b2 weekly release 4.1.9+ 2024-04-19 17:05:41 +07:00
Laurent David 19635ef720 MDL-81267 mod_data: Additional tests 2024-04-17 10:33:42 +02:00
Laurent David 9d5a4746ab MDL-81267 mod_data: Clean field type before inclusion
* Filter out wrong field types when restoring or creating a field.
2024-04-17 10:33:42 +02:00
Stevani Andolo e4406472f0 MDL-81354 core: Escape string of fullname 2024-04-12 11:30:23 +00:00
Paul Holden cf2f67272a MDL-81062 mod_lesson: escape identity fields in overview report. 2024-04-12 11:30:23 +00:00
Paul Holden 2ebac5604c MDL-80712 mod_workshop: stricter cleaning of sub-plugin values. 2024-04-12 11:30:23 +00:00
Paul Holden c5f48698da MDL-81058 admin: fix sesskey checks for admin preset actions. 2024-04-12 11:30:23 +00:00
Paul Holden c2a043f50d MDL-81059 tool_analytics: fix sesskey checks for model actions. 2024-04-12 11:30:23 +00:00
Laurent David 001c78285f MDL-81284 mod_wiki: Add stricter cleaning of contentformat
* When restoring backup we clean the contentformat and defaultformat
to avoid issue with invalid value.
* Add tests for format and defaultformat so to check that we
 filter out invalid formats
* Remove private $basepath variable as it was used one and not always
initialised correctly.
2024-04-12 11:30:23 +00:00
meirzamoodle af90bb572e MDL-81352 filter: Ensure equation safety prior to filtering 2024-04-12 11:30:23 +00:00
Paul Holden 882a660f7e MDL-81135 mod_feedback: stricter cleaning of item type parameter. 2024-04-12 11:30:23 +00:00
Huong Nguyen adc1de569d MDL-81247 calendar: Extra validation for webservices 2024-04-12 11:30:23 +00:00
Paul Holden 607716f206 MDL-81247 calendar: freeze event type if "user" only allowed. 2024-04-12 11:30:23 +00:00
Leon Stringer 5358ce28ee MDL-80585 report_log: sanitize descriptions
Strip any HTML tags from values in the "Description" column.
2024-04-12 11:30:23 +00:00
Sara Arjona fef8dda150 weekly release 4.1.9+ 2024-04-12 13:23:37 +02:00
Ilya Tregubov b049731dfb Merge branch 'MDL-80385-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2024-04-11 15:43:31 +08:00
Huong Nguyen 5277dca108 Merge branch 'MDL-81362-401' of https://github.com/ssj365/moodle into MOODLE_401_STABLE 2024-04-11 11:00:21 +07:00
Ilya Tregubov 4be4d0d489 Merge branch 'MDL-81491-401' of https://github.com/snake/moodle into MOODLE_401_STABLE 2024-04-11 09:51:19 +08:00
Shamiso.Jaravaza 3d0bcb7172 MDL-81362 mod_bigbluebuttonbn: Fix content type 2024-04-10 11:49:39 -06:00
Sara Arjona 7dac3707fd Merge branch 'MDL-74977-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-04-10 15:51:10 +02:00
Huong Nguyen 037a7d5d6c MDL-74977 core_courseformat: add expanded section update actions
This also fix the Random "Course content preferences" Behat failure
2024-04-10 18:12:45 +07:00
Jake Dallimore f5587cf881 MDL-81491 enrol_lti: fix incorrect login_hint parameter type validation
This was always bad, but since we only used it to verify the existence
of the param, and let library code take $_REQUEST for the real
validation, it was ok. Now, since we're redirecting to self during
necessary cookie checks added by MDL-80835, we lose the real value
originally stored in $_REQUEST. This patch just fixes the param type,
setting it to raw, which is what it should have used originally. The
raw value won't be cast from a string to an int as part of the
require_param call, so the value won't be lost any more.
2024-04-10 17:45:07 +08:00
Sara Arjona a18c0b7c8b weekly release 4.1.9+ 2024-04-09 12:07:33 +02:00
Jake Dallimore 097a879e1a Merge branch 'MDL-81060-401-fix' of https://github.com/meirzamoodle/moodle into MOODLE_401_STABLE 2024-04-09 10:14:07 +08:00
Andrew Nicols 58acb96ee6 MDL-81209 core: Unit test should not care about plugin details 2024-04-08 20:54:25 +08:00
Jake Dallimore 1901cd3fdf Merge branch 'MDL-81405-401' of https://github.com/jleyva/moodle into MOODLE_401_STABLE 2024-04-08 15:35:46 +08:00
Jake Dallimore 2c9a5c009f MDL-81060 core: fix incorrect userquota usage in zip_packer
This change reverts much of MDL-74641, removing the erroneous userquota
checks from zip_packer. Now, during extraction, the zip_packer will exit
with an error status when it encounters a file which is larger than its
advertised size, and NOT when it determines the size of the extracted
files have exceeded the userquota (which only applies to private files
and which was used incorrectly here). It also reverts changes to the
draftfiles_ajax.php file, restoring the original (pre-MDL-74641) error
handling.
2024-04-08 09:11:26 +07:00
Andrew Nicols b564a40d74 MDL-81457 core: Do not mark tests as Incomplete when they are wrong
This check was marking a test as Incomplete if there was a bug in any
part of the test, rather than failing the test in such a way that the
test bug may be found.

https://docs.phpunit.de/en/9.6/incomplete-and-skipped-tests.html
2024-04-08 08:47:29 +08:00
Juan Leyva 179bfdf5c5 MDL-81405 tool_mobile: Set Partitioned cookie when required 2024-04-06 12:31:50 +02:00
Juan Leyva eb975467bd MDL-81405 session: New core cookie helper utility class 2024-04-06 12:31:50 +02:00
Sara Arjona 5efbd6e0e1 weekly release 4.1.9+ 2024-04-02 14:46:54 +02:00
Andrew Nicols fa20b1790f Merge branch 'MDL-79712-401' of https://github.com/snake/moodle into MOODLE_401_STABLE 2024-04-02 11:57:12 +08:00
Jake Dallimore 7d579dd4bb MDL-79712 auth_lti: ensure MoodleSession uses SameSite=None Secure
The MoodleSession cookie is already set this way when using Chrome, but
not for Firefox. This is a decision made in \core\session\manager. When
Firefox eventually defaults to SameSite=Lax when the SameSite attribute
is omitted from the Set-Cookie headers, the MoodleSession cookie won't
be sent. This change ensures the cookies used during LTI launches will
continue to work if/when this happens, and despite any changes which may
be made to core's sessionlib in future.
2024-04-02 11:44:28 +08:00
Ilya Tregubov 6b0661b9ea Merge branch 'MDL-80036-401' of https://github.com/mickhawkins/moodle into MOODLE_401_STABLE 2024-03-27 11:08:55 +08:00
Sara Arjona afd57ca9a0 weekly release 4.1.9+ 2024-03-26 12:31:21 +01:00
Huong Nguyen c14e75a017 Merge branch 'MDL-81353-401' of https://github.com/snake/moodle into MOODLE_401_STABLE 2024-03-26 10:34:46 +07:00
Jake Dallimore 318e2344c0 MDL-81353 auth_lti: fix str_contains usage in cookie_helper
This failure was being masked by Symfony\Polyfill\Php80 during unit
tests.
2024-03-26 09:28:01 +08:00
Sara Arjona 17ed404ccc weekly release 4.1.9+ 2024-03-22 14:03:06 +01:00
Huong Nguyen 5b54a87858 Merge branch 'MDL-80835-401' of https://github.com/snake/moodle into MOODLE_401_STABLE 2024-03-21 14:54:20 +07:00
Jake Dallimore ea49be1e61 MDL-80835 auth_lti: fix bad cast breaking samesite LTI usage 2024-03-21 15:41:16 +08:00
Jake Dallimore 0f3d3b2d77 MDL-80835 enrol_lti: add partitioning support for OIDC state cookie
Adds the property that is required by Chrome to opt-in to its 3rd party
cookie partitioning solution, CHIPS. This specific change ensures the
'state' cookie, used in the OIDC handshake, has partitioning support.
This cookie can be partitioned unconditionally, since it's a cookie
controlled by the library and one we don't expect to be set without
partitioning elsewhere.
2024-03-21 15:41:16 +08:00
Jake Dallimore dee41e0648 MDL-80835 auth_lti: add partitioning to post-auth MoodleSession cookie
Adds the property that is required by Chrome to opt-in to its 3rd party
cookie partitioning solution, CHIPS. This specific change to auth_lti is
to ensure the MoodleSession Set-Cookie header resulting from
complete_user_login() calls (in auth.php) have this property set.
2024-03-21 15:41:16 +08:00
Jake Dallimore c11b1c6b3f MDL-80835 enrol_lti: add cookies required notice to auth login endpoint
This will be displayed if the cookie checks fail, which currently occurs
in Safari only.
2024-03-21 15:41:16 +08:00
Jake Dallimore e2362b0a7a MDL-80835 enrol_lti: add partitioning support to MoodleSession cookie
Adds the property that is required by Chrome to opt-in to its 3rd party
cookie partitioning solution, CHIPS. This specific change deals with the
cookie that is set when the user is not yet auth'd with the site and is
necessary to facilitate OIDC nonce retrieval and validation.
2024-03-21 15:41:15 +08:00
Jake Dallimore d63721cb15 MDL-80835 auth_lti: add cookie helper facilitating CHIPS opt-in
To opt a cookie in to Chrome's 3rd party cookie partitioning solution,
CHIPS, the property 'Partitioned;' needs to be set. This adds a helper
class supporting this, for a given cookie(s).

Note also, PHP's native
cookie APIs (setcookie, etc) don't support this cookie property yet -
(https://github.com/php/php-src/issues/12646).

Since this class is intended to allow existing Set-Cookie headers to be
modified before being sent (e.g. allowing clients to set a property on a
cookie set elsewhere in code), it deals with the headers directly anyway
but it means that new cookies must also use this helper to opt-in,
instead of relying on setcookie(). E.g. where the intent is to add
partitioning support to a new cookie, that cookie must first be set
(setcookie) and then it may opt-in to partitioning via this helper;
partitioning support cannot be achieved directly through setcookie and
friends yet.
2024-03-21 15:41:14 +08:00
Ilya Tregubov 66b1733bda weekly release 4.1.9+ 2024-03-20 08:54:22 +08:00
Ilya Tregubov 81821e6833 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-03-20 08:54:18 +08:00
Michael Hawkins 965a5fdb27 MDL-80036 core: Update security.txt expiry 2024-03-19 15:31:33 +08:00
Huong Nguyen 6aa2828161 Merge branch 'MDL-81156-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-03-19 10:56:15 +07:00
Andrew Nicols d46a37ebd4 Merge branch 'MDL-77255-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-03-19 11:40:21 +08:00
Huong Nguyen 8c358aefd9 MDL-77255 block_tag_flickr: Update to match with the new API
Flickr dropped php_serial format, we need to change to JSON format
2024-03-19 10:14:58 +07:00
AMOS bot 40aceb0256 Automatically generated installer lang files 2024-03-19 00:08:44 +00:00
Huong Nguyen f64240ce94 weekly release 4.1.9+ 2024-03-15 21:48:28 +07:00
Huong Nguyen 10c7926c0f Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-03-15 21:48:28 +07:00
Jake Dallimore 8a26315686 Merge branch 'MDL-80167-401-int' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-03-15 12:55:15 +08:00
Huong Nguyen 0e8d4d8e2a MDL-80167 core: Skip Oracle for Oracle environment in unit tests 2024-03-15 08:46:44 +07:00
Sara Arjona 5ac11701ac Merge branch 'MDL-80167-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2024-03-14 11:55:12 +01:00
Sara Arjona dc65317ba6 Merge branch 'MDL-80479-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-03-14 11:48:14 +01:00
Huong Nguyen f0ae7c4343 MDL-80167 admin: Add environment check for Oracle database 2024-03-14 17:42:51 +07:00
Simey Lameze b52c1855fa MDL-80479 behat: improve new test for quiz timer 2024-03-14 10:21:29 +08:00
Angelia Dela Cruz 6e1e63cc6b MDL-80479 mod_quiz: Behat to set quiz opening and closing time 2024-03-14 10:21:29 +08:00
Simey Lameze 50ae525be9 MDL-81156 behat: coverage for force course language 2024-03-14 09:04:49 +08:00
AMOS bot a700d119df Merge branch 'MOODLE_401_STABLE' into install_401_STABLE 2024-03-14 00:13:07 +00:00
Dale Davies 9ccd794b36 MDL-80385 core: Check lock state before attempting to acquire a lock
This is a cherry-pick of the same change from MDL-76865, which was
accidentally reverted in MDL-79428.
2024-03-13 13:56:49 +00:00
AMOS bot 49673645ea Automatically generated installer lang files 2024-03-13 00:11:01 +00:00
Ilya Tregubov e6316e6b9b weekly release 4.1.9+ 2024-03-13 08:10:00 +08:00
Huong Nguyen 5003afbe48 Merge branch 'MDL-76805-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-03-12 10:33:02 +07:00
Shamiso.Jaravaza 7e1bbaaf20 MDL-76805 mod_bigbluebuttonbn: Fix up behat tests 2024-03-12 11:08:28 +08:00
Sara Arjona d1111bc3e8 weekly release 4.1.9+ 2024-03-08 11:12:04 +01:00
Huong Nguyen 8de03684ac Merge branch 'MDL-79990-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-03-07 10:05:09 +07:00
Simey Lameze 2db07c4d0a MDL-79990 behat: custom step to verify assign grading data
This commit also optimise the new behat test.
2024-03-07 09:44:34 +08:00
Angelia Dela Cruz 3ac3d942dd MDL-79990 gradingform: Behat test for marking guide information display 2024-03-07 09:44:34 +08:00
Huong Nguyen 84308c7dfc Merge branch 'MDL-79278-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-03-06 09:13:39 +07:00
Angelia Dela Cruz 24a0e76ebf MDL-79278 mod_quiz: Teacher can set how quiz questions behave behat
Co-authored-by: Simey Lameze <simey@moodle.com>
2024-03-06 09:48:46 +08:00
Sara Arjona 2444b2b787 weekly release 4.1.9+ 2024-03-05 11:22:31 +01:00
Jun Pataleta b704fa7dda Merge branch 'MDL-80819_401' of https://github.com/stronk7/moodle into MOODLE_401_STABLE 2024-03-05 11:48:39 +08:00
Huong Nguyen 9d3d193f29 Merge branch 'MDL-80836-401' of https://github.com/snake/moodle into MOODLE_401_STABLE 2024-03-05 10:24:39 +07:00
Jun Pataleta 43c7557e84 weekly release 4.1.9+ 2024-03-01 12:24:39 +08:00
Jun Pataleta da74ac4987 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-03-01 12:24:37 +08:00
AMOS bot 1e5bc3a393 Automatically generated installer lang files 2024-02-28 00:10:59 +00:00
Sara Arjona 7c20cd1b81 Merge branch 'MDL-81036-401' of https://github.com/marinaglancy/moodle into MOODLE_401_STABLE 2024-02-27 13:04:19 +01:00
Marina Glancy edf1a1ac8c MDL-81036 behat: fix the action menu clickable area selector 2024-02-23 11:23:21 +00:00
Eloy Lafuente (stronk7) 5a2acac1ed MDL-80819 install: halt any install if php min version is not met
Apart from environmental stuff, the moodle_minimum_php_version_is_met()
function is executed by all installs (web, cli, database, ...)
and halts any installation (early) not meeting the PHP constraints.
2024-02-23 11:08:40 +01:00
Huong Nguyen 719c95009e weekly release 4.1.9+ 2024-02-23 08:37:56 +07:00
Huong Nguyen 860b4b941a Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-02-23 08:37:54 +07:00
Huong Nguyen 26ed48ec40 Merge branch 'MDL-80844_401' of https://github.com/stronk7/moodle into MOODLE_401_STABLE 2024-02-22 11:24:26 +07:00
Huong Nguyen 58ba1a9448 Merge branch 'MDL-80862-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-02-22 10:38:16 +07:00
Sara Arjona 40d2b4de39 Merge branch 'MDL-76441_401' of https://github.com/stronk7/moodle into MOODLE_401_STABLE 2024-02-21 17:50:08 +01:00
Sara Arjona bce1d7ff85 Merge branch 'MDL-80404-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-02-21 13:23:48 +01:00
Angelia Dela Cruz 3f439ff9d4 MDL-80404 mod_data: Behat for url autolinking and opening in new window 2024-02-21 16:03:46 +08:00
Huong Nguyen 0c34355c85 Merge branch 'MDL-79792-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-02-21 14:25:55 +07:00
Angelia Dela Cruz ab118d48f5 MDL-79792 core_badges: Behat test for awarding badge to group members 2024-02-21 14:06:09 +08:00
Jun Pataleta 6df3213c9b Merge branch 'MDL-79003-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2024-02-21 10:35:31 +08:00
Andrew Nicols 6dbd0e938f MDL-80862 testing: Reset task manager between behat tests 2024-02-20 10:55:04 +08:00
AMOS bot 8889f6f26f Automatically generated installer lang files 2024-02-20 00:13:48 +00:00
Sara Arjona 36aa09726b Merge branch 'MDL-79674-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2024-02-19 10:20:59 +01:00
Sara Arjona 196c2ae7a5 Merge branch 'MDL-80613-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2024-02-19 09:14:39 +01:00
Sara Arjona 4d217e3a1a weekly release 4.1.9+ 2024-02-15 15:39:56 +01:00
Sara Arjona 09e3db1aa6 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2024-02-15 15:39:53 +01:00
Simey Lameze 8815ecb3fd MDL-80613 behat: convert manual completion steps to use generators 2024-02-15 11:02:57 +08:00
Jake Dallimore 8482210209 MDL-80836 auth_lti: take user through login instead of sesspiggyback
Browsers are phasing out 3rd party cookies. Those which can be set are
partitioned to the top level embedding site, so piggybacking is
prevented. This will break the account linking process. This fix swaps
the piggyback for a login round trip, as originally intended, which
resolves the issue.
2024-02-15 10:40:05 +08:00
Sara Arjona 281ef7ef8d Merge branch 'MDL-79321-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-02-14 09:35:38 +01:00
Angelia Dela Cruz 6970580b4c MDL-79321 mod_quiz: Behat to set quiz as interactive with multiple tries 2024-02-13 17:09:00 +08:00
Jun Pataleta 652633b467 Merge branch 'MDL-80402-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2024-02-13 15:51:52 +08:00
Angelia Dela Cruz 5a7963d9f5 MDL-80402 mod_quiz: Behat for interactive quiz question dependency 2024-02-13 15:23:17 +08:00
AMOS bot 56ff5e78f1 Automatically generated installer lang files 2024-02-13 00:10:13 +00:00
Eloy Lafuente (stronk7) e31db928b5 MDL-80844 phpunit: Only some tests can use the debugging sink
The only tests that have COMPLETE* support for the debugging sink
are the advanced_testcase and the database_driver ones (store and
report). So we must ensure that the rest of tests don't use the
debugging sink at all.

Right now we are using it for storing, but later there is not
reporting, so any debugging happening within non advanced tests
is not detected.

This commit just ensures that we stop making that storing for
non advanced/database_driver tests. Nothing more, nothing less.

* Note that we have had to add a few missing bits to the
database_driver testcase because it was not 100% complete. Now
it behaves 100% the same than the advanced_testcase one regarding
the debugging sink.
2024-02-12 13:03:06 +01:00
AMOS bot 03fcd81892 Automatically generated installer lang files 2024-02-12 00:11:13 +00:00
Eloy Lafuente (stronk7) 05a41b6e32 MDL-76441 env: Moodle 4.2.x and 4.3.x do not support PHP 8.3 2024-02-10 12:15:11 +01:00
Jun Pataleta b3867e7422 MDL-79674 behat: Use proper tags for testing WCAG 2.1 criteria
`wcag2a` and `wcag2aa` refer to WCAG 2.0 standards. We should be using
`wcag21a` and `wcag21aa` to test against WCAG 2.1 standards.

See:
https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#axe-core-tags
2024-01-30 16:19:50 +08:00
Jun Pataleta 8c298f14bb MDL-79674 behat: axe-core version fixes
* Update \behat_accessibility::run_axe_validation_for_tags()'s
  PHPDoc block to reflect the current version.
* Fix incorrect copyright tag for the library
2024-01-30 16:19:48 +08:00
Andrew Nicols 58bd2ed8e4 MDL-79003 js: Bump stylelint and components
Stylelint 15 deprecates a heap of rules which will be removed in 16.

We can't use 16 because it's ESM and requires a big restructure of our
build process.

We can temporarily silence the deprecations and then when we update our
build tooling we can make use of @stylistic/stylelint-plugin to add the
rules back.

This commit:
- bumps stylelint and related dependencies accordingly
- fixes issues identified with newer sniffs
- temporarily silences deprecation warnings
2024-01-30 14:08:51 +08:00
Andrew Nicols 36ca488c06 MDL-79003 js: Move rollup ratelimit to generateBundle
There are two phases of a build: Building, and then Outputting.

We were previously listening on the final event for the build phase, but
we should be listening to the final event of the output phase.
2024-01-30 14:08:51 +08:00
Andrew Nicols bca28b03a9 MDL-79003 js: Move browserlist config to package.json 2024-01-30 14:08:51 +08:00
Andrew Nicols a6216bb890 MDL-79003 js: Standard package bump - glob 2024-01-30 14:08:20 +08:00
Andrew Nicols 94b14df11d MDL-79003 js: Standard package bump - async 2024-01-30 14:07:09 +08:00
Andrew Nicols 0dc60026c8 MDL-79003 js: Remove grunt-contrib-uglify
We stopped using this when we updated our processes to support ESMs in
MDL-62497. Removal of the dependency was missed at the time.
2024-01-30 14:07:08 +08:00
Andrew Nicols 63a1ec81c4 MDL-79003 js: Standard package bump - grunt 2024-01-30 14:07:08 +08:00
Andrew Nicols 150fc0837a MDL-79003 js: Standard package bump - watchman 2024-01-30 14:07:08 +08:00
Andrew Nicols 72958879c6 MDL-79003 js: Standard package bump - ajv 2024-01-30 14:07:08 +08:00
Andrew Nicols d8d560b7fe MDL-79003 js: Use our own jsdoc wrapper
grunt-jsdoc is abandoned and only works with an older version of jsdoc.

This is a very simple wrapper around jsdoc itself.

This commit also includes a fix for broken docs.
2024-01-30 14:07:06 +08:00
Andrew Nicols 917dd49db2 MDL-79003 core: Bump eslint 2024-01-30 14:04:40 +08:00
Ruslan Kabalin 80691cf5a3 MDL-79003 eslint: Enable 'reportUnusedDisableDirectives' eslint setting
This commit is a backport of MDL-74301 which should have been backported
at the time.

This will warn on unused /* eslint-disable */ statements.
2024-01-30 14:04:38 +08:00
Ruslan Kabalin e20720ce0e MDL-79003 eslint: Remove rules included in eslint:recommended
This commit is a backport of MDL-74301 which should have been backported
at the time.

All rules included in "eslint:recommended" set containing matching
values are removed from the list. Recommended set is defined in
node_modules/eslint/conf/eslint-recommended.js
2024-01-30 14:03:10 +08:00
Ruslan Kabalin f976349b95 MDL-79003 eslint: Remove deprecated rules.
This commit is a backport of MDL-74301 which should have been backported
at the time.

To identify deprecated rules, temporary install sarbbottam/eslint-find-rules, then run
`npx eslint-find-rules -d .eslintrc`, it will return a list of rules
defined in .eslintrc that were deprecated.
2024-01-30 14:03:09 +08:00
Ruslan Kabalin 312a166f1e MDL-79003 eslint: Refactor jsdoc rules.
This commit is a backport of MDL-74301 which should have been backported
at the time.

Adding 'plugin:promise/recommended' results in 5k+ jsdoc warnings most
of which includes missing return and naming preferences (e.g. 'returns'
over 'return' or 'string' over 'String'). This needs to be audited and
addressed seprately.
2024-01-30 14:03:09 +08:00
Ruslan Kabalin f16ca8fa7a MDL-79003 eslint: Use 'plugin:promise/recommended'
This commit is a backport of MDL-74301 which should have been backported
at the time.

This enables recommended rules and brings back related rules. Without
reducing level to warn for some of them, this gives numerous (about 88) issues mainly
catch-or-return. Recommended set is defined in
node_modules/eslint-plugin-promise/index.js
2024-01-30 14:03:09 +08:00
Ruslan Kabalin 846a8b6568 MDL-79003 eslint: Address issues reported by eslint
This commit is a backport of MDL-74301 which should have been backported
at the time.
2024-01-30 14:03:05 +08:00
Ruslan Kabalin 692d3090fa MDL-79003 eslint: Change configuration and use 'eslint:recommended'
This commit is a backport of MDL-74301 which should have been backported
at the time.
2024-01-30 13:33:40 +08:00
Ruslan Kabalin 8092eb95c4 MDL-79003 eslint: Upgrade eslint and babel
This commit is a backport of MDL-74301 which should have been backported
at the time.

This patch includes changes:
* Upgrade "@babel/core" to latest point release
* Remove plugins already included into current "@babel/preset-env":
  - "@babel/plugin-proposal-class-properties"
  - "@babel/plugin-proposal-json-strings"
  - "@babel/plugin-syntax-dynamic-import"
  - "@babel/plugin-syntax-import-meta"
* Upgrade "eslint" to latest version
* Replace "eslint-plugin-babel" (depreacted in 2019) with "@babel/eslint-parser" and "@babel/eslint-plugin"
2024-01-30 13:33:40 +08:00
Andrew Nicols 2b864ceec3 MDL-79003 core: Bump NodeJS to lts/iron (NodeJS 20) 2024-01-30 13:33:40 +08:00
448 changed files with 10974 additions and 5217 deletions
+30 -93
View File
@@ -1,8 +1,12 @@
{
'plugins': [
'babel',
'@babel',
'promise',
'jsdoc',
'jsdoc'
],
'extends': [
'eslint:recommended',
'plugin:promise/recommended'
],
'env': {
'browser': true,
@@ -16,42 +20,19 @@
'M': true,
'Y': true
},
// Warn about unused eslint-disable statements.
'reportUnusedDisableDirectives': true,
'rules': {
// See http://eslint.org/docs/rules/ for all rules and explanations of all
// rules.
// === Possible Errors ===
'comma-dangle': 'off',
'no-compare-neg-zero': 'error',
'no-cond-assign': 'error',
'no-console': 'error',
'no-constant-condition': 'error',
'no-control-regex': 'error',
'no-debugger': 'error',
'no-dupe-args': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-empty': 'warn',
'no-empty-character-class': 'error',
'no-ex-assign': 'error',
'no-extra-boolean-cast': 'error',
'no-extra-parens': 'off',
'no-extra-semi': 'error',
'no-func-assign': 'error',
'no-inner-declarations': 'error',
'no-invalid-regexp': 'error',
'no-irregular-whitespace': 'error',
'no-obj-calls': 'error',
'no-prototype-builtins': 'off',
'no-regex-spaces': 'error',
'no-sparse-arrays': 'error',
'no-unexpected-multiline': 'error',
'no-unreachable': 'warn',
'no-unsafe-finally': 'error',
'no-unsafe-negation': 'error',
'use-isnan': 'error',
'valid-jsdoc': ['warn', { 'requireReturn': false, 'requireParamDescription': false, 'requireReturnDescription': false }],
'valid-typeof': 'error',
// === Best Practices ===
// (these mostly match our jshint config)
@@ -63,15 +44,12 @@
'dot-notation': 'warn',
'no-alert': 'warn',
'no-caller': 'error',
'no-case-declarations': 'error',
'no-div-regex': 'error',
'no-empty-pattern': 'error',
'no-empty-function': 'warn',
'no-eq-null': 'error',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'warn',
'no-fallthrough': 'error',
'no-floating-decimal': 'warn',
'no-global-assign': 'warn',
'no-implied-eval': 'error',
@@ -83,28 +61,22 @@
'no-multi-str': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-octal': 'error',
'no-octal-escape': 'error',
'no-proto': 'error',
'no-redeclare': 'warn',
'no-restricted-globals': ['error', { 'name': 'Notification' }],
'no-return-assign': 'error',
'no-script-url': 'error',
'no-self-assign': 'error',
'no-self-compare': 'error',
'no-sequences': 'warn',
'no-throw-literal': 'warn',
'no-unmodified-loop-condition': 'error',
'no-unused-expressions': 'error',
'no-unused-labels': 'error',
'no-useless-call': 'warn',
'no-useless-escape': 'warn',
'no-with': 'error',
'wrap-iife': ['error', 'any'],
// === Variables ===
'no-delete-var': 'error',
'no-undef': 'error',
'no-undef-init': 'error',
'no-unused-vars': ['error', { 'caughtErrors': 'none' }],
@@ -137,8 +109,6 @@
'max-statements-per-line': ['warn', { max: 2 }],
'new-cap': ['warn', { 'properties': false }],
'new-parens': 'warn',
'newline-after-var': 'off',
'newline-before-return': 'off',
'newline-per-chained-call': 'off',
'no-array-constructor': 'off',
'no-bitwise': 'error',
@@ -146,7 +116,6 @@
'no-inline-comments': 'off',
'no-lonely-if': 'off',
'no-mixed-operators': 'off',
'no-mixed-spaces-and-tabs': 'error',
'no-multiple-empty-lines': 'warn',
'no-negated-condition': 'off',
'no-nested-ternary': 'warn',
@@ -168,7 +137,6 @@
'padded-blocks': 'off',
'quote-props': ['warn', 'as-needed', {'unnecessary': false, 'keywords': true, 'numbers': true}],
'quotes': 'off',
'require-jsdoc': 'warn',
'semi': 'error',
'semi-spacing': ['warn', {'before': false, 'after': true}],
'sort-vars': 'off',
@@ -181,21 +149,19 @@
'unicode-bom': 'error',
'wrap-regex': 'off',
// === Promises ===
// === Promises (override plugin:promise/recommended) ===
'promise/always-return': 'warn',
'promise/no-return-wrap': 'warn',
'promise/param-names': 'warn',
'promise/catch-or-return': ['warn', {terminationMethod: ['catch', 'fail']}],
'promise/no-native': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
// === Deprecations ===
"no-restricted-properties": ['warn', {
'object': 'M',
'property': 'str',
'message': 'Use AMD module "core/str" or M.util.get_string()'
'message': 'Use "core/str" module or M.util.get_string()'
}],
},
overrides: [
@@ -235,7 +201,6 @@
parser: '@babel/eslint-parser',
// Check AMD with some slightly stricter rules.
rules: {
'no-unused-vars': 'error',
'no-implicit-globals': 'error',
// Disable all of the rules that have babel versions.
'new-cap': 'off',
@@ -247,65 +212,37 @@
'semi': 'off',
'no-unused-expressions': 'off',
// Enable all of the babel version of these rules.
'babel/new-cap': ['warn', { 'properties': false }],
'@babel/new-cap': ['warn', { 'properties': false }],
// Not using this rule for the time being because it isn't
// compatible with jQuery and ES6.
'babel/no-invalid-this': 'off',
'babel/object-curly-spacing': 'warn',
// This is off in the original style int.
'babel/quotes': 'off',
'babel/semi': 'error',
'babel/no-unused-expressions': 'error',
'@babel/no-invalid-this': 'off',
'@babel/object-curly-spacing': 'warn',
'@babel/semi': 'error',
'@babel/no-unused-expressions': 'error',
// === Promises ===
// We have Promise now that we're using ES6.
'promise/no-native': 'off',
'promise/avoid-new': 'off',
// === JSDocs ===
"jsdoc/check-access": [
'error',
],
"jsdoc/check-alignment": 1, // Recommended.
"jsdoc/check-param-names": [
'error',
],
"jsdoc/check-property-names": [
'error',
],
"jsdoc/empty-tags": [
'error',
],
"jsdoc/implements-on-classes": [
'error',
],
"jsdoc/multiline-blocks": [
'error',
],
"jsdoc/require-jsdoc": [
'error',
],
"jsdoc/require-param": [
'error',
],
"jsdoc/require-param-name": [
'error',
],
"jsdoc/require-param-type": [
'error',
],
"jsdoc/require-property": [
'error',
],
"jsdoc/require-property-name": [
'error',
],
"jsdoc/require-property-type": [
'error',
],
'jsdoc/check-access': 'error',
'jsdoc/check-alignment': 1, // Recommended.
'jsdoc/check-param-names': 'error',
'jsdoc/check-property-names': 'error',
'jsdoc/empty-tags': 'error',
'jsdoc/implements-on-classes': 'error',
'jsdoc/multiline-blocks': 'error',
'jsdoc/require-jsdoc': 'error',
'jsdoc/require-param': 'error',
'jsdoc/require-param-name': 'error',
'jsdoc/require-param-type': 'error',
'jsdoc/require-property': 'error',
'jsdoc/require-property-name': 'error',
'jsdoc/require-property-type': 'error'
},
parserOptions: {
'sourceType': 'module',
'requireConfigFile': false,
'requireConfigFile': false
}
}
]
+1 -1
View File
@@ -3,7 +3,7 @@ name: Core
on:
push:
branches-ignore:
- master
- main
- MOODLE_[0-9]+_STABLE
tags-ignore:
- v[0-9]+.[0-9]+.[0-9]+*
+7 -19
View File
@@ -21,7 +21,7 @@
*/
/**
* Function to generate the destination for the uglify task
* Function to generate the destination for the minification task
* (e.g. build/file.min.js). This function will be passed to
* the rename property of files array when building dynamically:
* http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
@@ -60,7 +60,6 @@ module.exports = grunt => {
grunt.registerTask('js', ['amd', 'yui']);
// Register NPM tasks.
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-rollup');
@@ -104,6 +103,7 @@ module.exports = grunt => {
// The queue runner will run the next `size` items in the queue.
const runQueue = (size = 1) => {
queue.splice(0, size).forEach(resolve => {
grunt.log.debug(`Item resolved. Kicking off next one.`);
resolve();
});
};
@@ -113,15 +113,17 @@ module.exports = grunt => {
// The options hook is run in parallel.
// We can return an unresolved Promise which is queued for later resolution.
options: async() => {
options: async(options) => {
return new Promise(resolve => {
queue.push(resolve);
startQueue();
return options;
});
},
// When an item in the queue completes, start the next item in the queue.
buildEnd: () => {
generateBundle: (options, bundle) => {
grunt.log.debug(`Finished output phase for ${Object.keys(bundle).join(', ')}`);
runQueue();
},
};
@@ -151,24 +153,10 @@ module.exports = grunt => {
//
// It also adds the Moodle plugin name to the AMD module definition
// so that it can be imported as expected in other modules.
path.resolve('.grunt/babel-plugin-add-module-to-define.js'),
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-import-meta',
['@babel/plugin-proposal-class-properties', {'loose': false}],
'@babel/plugin-proposal-json-strings'
path.resolve('.grunt/babel-plugin-add-module-to-define.js')
],
presets: [
['@babel/preset-env', {
targets: {
browsers: [
">0.3%",
"last 2 versions",
"not ie >= 0",
"not op_mini all",
"not Opera > 0",
"not dead"
]
},
modules: false,
useBuiltIns: false
}]
+28 -12
View File
@@ -20,17 +20,33 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
// Project configuration.
grunt.config.merge({
jsdoc: {
dist: {
options: {
configure: ".grunt/jsdoc/jsdoc.conf.js",
},
},
},
});
module.exports = (grunt) => {
const path = require('path');
grunt.loadNpmTasks('grunt-jsdoc');
grunt.registerTask('jsdoc', 'Generate JavaScript documentation using jsdoc', function() {
const done = this.async();
const configuration = path.resolve('.grunt/jsdoc/jsdoc.conf.js');
grunt.util.spawn({
cmd: 'jsdoc',
args: [
'--configure',
configuration,
]
}, function(error, result, code) {
if (result.stdout) {
grunt.log.write(result.stdout);
}
if (result.stderr) {
grunt.log.error(result.stderr);
}
if (error) {
grunt.fail.fatal(`JSDoc failed with error code ${code}`);
} else {
grunt.log.write('JSDoc completed successfully'.green);
}
done();
});
});
};
+5 -1
View File
@@ -29,6 +29,7 @@ module.exports = grunt => {
// Use a fully-qualified path.
src: files,
options: {
quietDeprecationWarnings: true,
configOverrides: {
rules: {
// These rules have to be disabled in .stylelintrc for scss compat.
@@ -45,7 +46,10 @@ module.exports = grunt => {
return {
stylelint: {
scss: {
options: {syntax: 'scss'},
options: {
quietDeprecationWarnings: true,
customSyntax: 'postcss-scss',
},
src: files,
},
},
+1 -1
View File
@@ -1 +1 @@
lts/gallium
lts/iron
+16 -4
View File
@@ -1,9 +1,14 @@
{
"customSyntax": "postcss-scss",
"plugins": [
"stylelint-csstree-validator"
],
"rules": {
"csstree/validator": true,
"csstree/validator": {
"syntaxExtensions": [
"sass"
]
},
"at-rule-empty-line-before": [ "always",
{"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
],
@@ -45,7 +50,7 @@
"function-name-case": "lower",
"function-parentheses-newline-inside": "always-multi-line",
"function-parentheses-space-inside": "never-single-line",
"function-url-scheme-blacklist": ["data"],
"function-url-scheme-disallowed-list": ["data"],
"function-whitespace-after": "always",
"indentation": 4,
"keyframe-declaration-no-important": true,
@@ -84,12 +89,19 @@
"selector-type-no-unknown": true,
"string-no-newline": true,
"time-min-milliseconds": 100,
"unit-blacklist": ["pt"],
"unit-disallowed-list": ["pt"],
"unit-case": "lower",
"unit-no-unknown": true,
"value-keyword-case": ["lower", {"ignoreKeywords": ["/(@|$)/"]}],
"value-list-comma-newline-after": "always-multi-line",
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
}
},
"overrides": [
{
"files": ["**/yui/**/*.css"],
"rules": {
}
}
]
}
+207
View File
@@ -3923,6 +3923,8 @@
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="4.2" requires="3.11.8">
@@ -3939,6 +3941,7 @@
<VENDOR name="oracle" version="19" />
</DATABASE>
<PHP version="8.0.0" level="required">
<RESTRICT function="restrict_php_version_83" message="unsupportedphpversion83" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
@@ -4112,6 +4115,8 @@
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="4.3" requires="3.11.8">
@@ -4128,6 +4133,7 @@
<VENDOR name="oracle" version="19" />
</DATABASE>
<PHP version="8.0.0" level="required">
<RESTRICT function="restrict_php_version_83" message="unsupportedphpversion83" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
@@ -4303,6 +4309,8 @@
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="4.4" requires="4.1.2">
@@ -4494,6 +4502,205 @@
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="4.5" requires="4.1.2">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="10.6.7" />
<VENDOR name="mysql" version="8.0" />
<VENDOR name="postgres" version="13" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="oracle" version="19" />
</DATABASE>
<PHP version="8.1.0" level="required">
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<ON_CHECK message="pcreunicodewarning" />
</FEEDBACK>
</PCREUNICODE>
<PHP_EXTENSIONS>
<PHP_EXTENSION name="iconv" level="required">
<FEEDBACK>
<ON_ERROR message="iconvrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="mbstring" level="required">
<FEEDBACK>
<ON_ERROR message="mbstringrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="curl" level="required">
<FEEDBACK>
<ON_ERROR message="curlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="openssl" level="required">
<FEEDBACK>
<ON_ERROR message="opensslrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="tokenizer" level="optional">
<FEEDBACK>
<ON_CHECK message="tokenizerrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="soap" level="optional">
<FEEDBACK>
<ON_CHECK message="soaprecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="ctype" level="required">
<FEEDBACK>
<ON_ERROR message="ctyperequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zip" level="required">
<FEEDBACK>
<ON_ERROR message="ziprequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zlib" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="gd" level="required">
<FEEDBACK>
<ON_ERROR message="gdrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="simplexml" level="required">
<FEEDBACK>
<ON_ERROR message="simplexmlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="spl" level="required">
<FEEDBACK>
<ON_ERROR message="splrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="pcre" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="dom" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xml" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlreader" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="intl" level="required">
<FEEDBACK>
<ON_ERROR message="intlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="json" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="hash" level="required"/>
<PHP_EXTENSION name="fileinfo" level="required"/>
<PHP_EXTENSION name="sodium" level="required"/>
<PHP_EXTENSION name="exif" level="optional"/>
</PHP_EXTENSIONS>
<PHP_SETTINGS>
<PHP_SETTING name="memory_limit" value="96M" level="required">
<FEEDBACK>
<ON_ERROR message="settingmemorylimit" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="file_uploads" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="settingfileuploads" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="opcache.enable" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="opcacherecommended" />
</FEEDBACK>
</PHP_SETTING>
</PHP_SETTINGS>
<CUSTOM_CHECKS>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbstorageengine" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
<FEEDBACK>
<ON_ERROR message="quizattemptsupgradedmessage" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
<FEEDBACK>
<ON_CHECK message="slashargumentswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
<FEEDBACK>
<ON_CHECK message="unsupporteddbtablerowformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
<FEEDBACK>
<ON_CHECK message="unoconvwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
<FEEDBACK>
<ON_CHECK message="libcurlwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfileformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfilepertable" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddblargeprefix" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
<FEEDBACK>
<ON_CHECK message="ishttpswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
<FEEDBACK>
<ON_CHECK message="incompleteunicodesupport" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="required">
<FEEDBACK>
<ON_ERROR message="sixtyfourbitsrequired" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
<FEEDBACK>
<ON_CHECK message="iscustomadminwarnings" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+5 -2
View File
@@ -2052,8 +2052,10 @@ class core_admin_renderer extends plugin_renderer_base {
// We are checking installed & enabled things
if ($environment_result->getLevel() == 'required') {
$stringtouse = 'environmentrequirecustomcheck';
} else {
} else if ($environment_result->getLevel() == 'optional') {
$stringtouse = 'environmentrecommendcustomcheck';
} else {
$stringtouse = 'environmentshouldfixcustomcheck';
}
} else if ($environment_result->getPart() == 'php_setting') {
@@ -2084,7 +2086,8 @@ class core_admin_renderer extends plugin_renderer_base {
if ($status) { //Handle ok result (ok)
$status = get_string('statusok');
} else {
if ($environment_result->getLevel() == 'optional') {//Handle check result (warning)
// Handle check result (warning).
if (in_array($environment_result->getLevel(), ['optional', 'recommended'])) {
$status = get_string('check');
$warningline = true;
} else { //Handle error result (error)
+2 -1
View File
@@ -152,7 +152,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$sensiblesettingsdefault .= 'server_password@@search_solr, ssl_keypassword@@search_solr, ';
$sensiblesettingsdefault .= 'alternateserver_password@@search_solr, alternatessl_keypassword@@search_solr, ';
$sensiblesettingsdefault .= 'test_password@@cachestore_redis, password@@mlbackend_python, ';
$sensiblesettingsdefault .= 'badges_badgesalt@@none, calendar_exportsalt@@none';
$sensiblesettingsdefault .= 'badges_badgesalt@@none, calendar_exportsalt@@none, ';
$sensiblesettingsdefault .= 'bigbluebuttonbn_shared_secret@@none';
$temp->add(new admin_setting_configtextarea('adminpresets/sensiblesettings',
get_string('sensiblesettings', 'core_adminpresets'),
get_string('sensiblesettingstext', 'core_adminpresets'),
+3 -3
View File
@@ -18,7 +18,7 @@ Feature: An administrator can browse user accounts
When I navigate to "Users > Accounts > Browse list of users" in site administration
# Name field always present, email field is default for showidentity.
Then the following should exist in the "users" table:
| First name / Last name | Email address |
| First name | Email address |
| User One | one@example.com |
| User Two | two@example.com |
# Should not see other identity fields or non-default name fields.
@@ -34,7 +34,7 @@ Feature: An administrator can browse user accounts
| alternativefullnameformat | firstnamephonetic lastname |
When I navigate to "Users > Accounts > Browse list of users" in site administration
Then the following should exist in the "users" table:
| First name - phonetic / Last name | Email address |
| First name - phonetic | Email address |
| Yewzer One | one@example.com |
| Yoozare Two | two@example.com |
@@ -43,7 +43,7 @@ Feature: An administrator can browse user accounts
| showuseridentity | department,profile_field_frog |
When I navigate to "Users > Accounts > Browse list of users" in site administration
Then the following should exist in the "users" table:
| First name / Last name | Favourite frog | Department |
| First name | Favourite frog | Department |
| User One | Kermit | Attack |
| User Two | Tree | Defence |
And I should not see "Email address" in the "table" "css_element"
-22
View File
@@ -48,25 +48,3 @@ Feature: View task logs report and use its filters
| operator | shouldornotsee |
| Less than | should not see |
| Greater than | should see |
@javascript
Scenario: Reset task log filters
Given I log in as "admin"
And I change window size to "large"
And I navigate to "Server > Tasks > Task logs" in site administration
When I click on "Filters" "button"
And I set the following fields in the "Result" "core_reportbuilder > Filter" to these values:
| Result operator | Is equal to |
| Result value | Fail |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
Then I should see "Filters applied"
And I should see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
And I should see "Nothing to display"
And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element"
And I should see "Filters reset"
And I should not see "Filters (1)" in the "#dropdownFiltersButton" "css_element"
And I should see "Filters" in the "#dropdownFiltersButton" "css_element"
And "[data-region='report-filters']" "css_element" should be visible
And the following fields in the "Result" "core_reportbuilder > Filter" match these values:
| Result operator | Is any value |
And I should not see "Nothing to display"
@@ -64,7 +64,7 @@ class delete extends base {
* Delete the DB preset
*/
public function execute(): void {
confirm_sesskey();
require_sesskey();
$this->manager->delete_preset($this->id);
@@ -52,8 +52,6 @@ class export extends base {
* Stores a preset into the DB.
*/
public function execute(): void {
confirm_sesskey();
$url = new \moodle_url('/admin/tool/admin_presets/index.php', ['action' => 'export', 'mode' => 'execute']);
$this->moodleform = new export_form($url);
@@ -87,7 +85,7 @@ class export extends base {
* @throws xml_writer_exception
*/
public function download_xml(): void {
confirm_sesskey();
require_sesskey();
list($xmlstr, $filename) = $this->manager->download_preset($this->id);
@@ -40,8 +40,6 @@ class import extends base {
* Imports the xmlfile into DB
*/
public function execute(): void {
confirm_sesskey();
$url = new \moodle_url('/admin/tool/admin_presets/index.php', ['action' => 'import', 'mode' => 'execute']);
$this->moodleform = new import_form($url);
@@ -38,8 +38,6 @@ class load extends base {
public function execute(): void {
global $OUTPUT;
confirm_sesskey();
$url = new \moodle_url('/admin/tool/admin_presets/index.php', ['action' => 'load', 'mode' => 'execute']);
$this->moodleform = new load_form($url);
@@ -73,7 +73,7 @@ class rollback extends base {
public function execute(): void {
global $OUTPUT;
confirm_sesskey();
require_sesskey();
list($presetapp, $rollback, $failures) = $this->manager->revert_preset($this->id);
@@ -5,16 +5,18 @@ Feature: I can download a preset
| name |
| Custom preset |
@javascript
Scenario: Custom preset settings can be downloaded
Given I log in as "admin"
And I navigate to "Site admin presets" in site administration
When I open the action menu in "Custom preset" "table_row"
Then following "Download" "link" in the "Custom preset" "table_row" should download between "0" and "5000" bytes
Then following "Download" in the "Custom preset" "table_row" should download a file that:
| Has mimetype | text/xml |
| Contains text in xml element | Custom preset |
@javascript
Scenario: Core preset settings can be downloaded
Given I log in as "admin"
And I navigate to "Site admin presets" in site administration
When I open the action menu in "Starter" "table_row"
Then following "Download" "link" in the "Starter" "table_row" should download between "0" and "5000" bytes
Then following "Download" in the "Starter" "table_row" should download a file that:
| Has mimetype | text/xml |
| Contains text in xml element | Starter |
+7 -7
View File
@@ -97,28 +97,28 @@ if ($onlycli === false) {
switch ($action) {
case 'enable':
confirm_sesskey();
require_sesskey();
$model->enable();
redirect($returnurl);
break;
case 'disable':
confirm_sesskey();
require_sesskey();
$model->update(0, false, false);
redirect($returnurl);
break;
case 'delete':
confirm_sesskey();
require_sesskey();
$model->delete();
redirect($returnurl);
break;
case 'edit':
confirm_sesskey();
require_sesskey();
$invalidcurrenttimesplitting = $model->invalid_timesplitting_selected();
$potentialtimesplittings = $model->get_potential_timesplittings();
@@ -183,7 +183,7 @@ switch ($action) {
break;
case 'evaluate':
confirm_sesskey();
require_sesskey();
if ($model->is_static()) {
throw new moodle_exception('errornostaticevaluate', 'tool_analytics');
@@ -226,7 +226,7 @@ switch ($action) {
break;
case 'scheduledanalysis':
confirm_sesskey();
require_sesskey();
if ($onlycli) {
throw new moodle_exception('erroronlycli', 'tool_analytics');
@@ -292,7 +292,7 @@ switch ($action) {
break;
case 'clear':
confirm_sesskey();
require_sesskey();
$model->clear();
redirect($returnurl);
@@ -155,9 +155,9 @@ class data_request extends persistent {
case api::DATAREQUEST_STATUS_EXPIRED:
$result = true;
break;
// Complete requests are expired if the expiry time has elapsed.
// Complete requests are expired if the expiry time is a positive value, and has elapsed.
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
$expiryseconds = (int) get_config('tool_dataprivacy', 'privacyrequestexpiry');
if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
$result = true;
}
@@ -178,7 +178,12 @@ class data_request extends persistent {
public static function get_expired_requests($userid = 0) {
global $DB;
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
// Complete requests are expired if the expiry time is a positive value, and has elapsed.
$expiryseconds = (int) get_config('tool_dataprivacy', 'privacyrequestexpiry');
if ($expiryseconds <= 0) {
return [];
}
$expirytime = strtotime("-{$expiryseconds} second");
$table = self::TABLE;
$sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
@@ -54,6 +54,7 @@ Feature: Data delete from the privacy API
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
And I open the action menu in "Victim User 1" "table_row"
And I follow "Approve request"
And I wait until "Approve request" "button" exists
And I press "Approve request"
And I should see "Approved" in the "Victim User 1" "table_row"
And I run all adhoc tasks
@@ -80,6 +81,7 @@ Feature: Data delete from the privacy API
And I navigate to "Users > Privacy and policies > Data requests" in site administration
And I open the action menu in "Victim User 1" "table_row"
And I follow "Approve request"
And I wait until "Approve request" "button" exists
And I press "Approve request"
And I log out
@@ -115,6 +117,7 @@ Feature: Data delete from the privacy API
And I navigate to "Users > Privacy and policies > Data requests" in site administration
And I open the action menu in "Victim User 1" "table_row"
And I follow "Approve request"
And I wait until "Approve request" "button" exists
And I press "Approve request"
And I log out
@@ -48,13 +48,15 @@ Feature: Data export from the privacy API
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
And I open the action menu in "Victim User 1" "table_row"
And I follow "Approve request"
And I wait until "Approve request" "button" exists
And I press "Approve request"
And I should see "Approved" in the "Victim User 1" "table_row"
And I run all adhoc tasks
And I reload the page
And I should see "Download ready" in the "Victim User 1" "table_row"
And I open the action menu in "Victim User 1" "table_row"
And following "Download" should download between "1" and "150000" bytes
And following "Download" should download a file that:
| Contains file in zip | index.html |
And the following config values are set as admin:
| privacyrequestexpiry | 1 | tool_dataprivacy |
And I wait "1" seconds
@@ -89,7 +91,8 @@ Feature: Data export from the privacy API
And I reload the page
And I should see "Download ready" in the "Export all of my personal data" "table_row"
And I open the action menu in "Victim User 1" "table_row"
And following "Download" should download between "1" and "155000" bytes
And following "Download" should download a file that:
| Contains file in zip | index.html |
And the following config values are set as admin:
| privacyrequestexpiry | 1 | tool_dataprivacy |
@@ -115,6 +118,7 @@ Feature: Data export from the privacy API
And I navigate to "Users > Privacy and policies > Data requests" in site administration
And I open the action menu in "Victim User 1" "table_row"
And I follow "Approve request"
And I wait until "Approve request" "button" exists
And I press "Approve request"
And I log out
@@ -126,7 +130,8 @@ Feature: Data export from the privacy API
And I reload the page
And I should see "Download ready" in the "Victim User 1" "table_row"
And I open the action menu in "Victim User 1" "table_row"
And following "Download" should download between "1" and "150000" bytes
And following "Download" should download a file that:
| Contains file in zip | index.html |
And the following config values are set as admin:
| privacyrequestexpiry | 1 | tool_dataprivacy |
@@ -26,10 +26,11 @@ require_once('data_privacy_testcase.php');
* Expired data requests tests.
*
* @package tool_dataprivacy
* @covers \tool_dataprivacy\data_request
* @copyright 2018 Michael Hawkins
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_data_requests_test extends data_privacy_testcase {
final class expired_data_requests_test extends data_privacy_testcase {
/**
* Test tearDown.
@@ -107,6 +108,52 @@ class expired_data_requests_test extends data_privacy_testcase {
$this->assertEquals(0, $DB->count_records('files', $fileconditions));
}
/**
* Test that data requests are not expired when expiration is disabled (set to zero)
*/
public function test_data_request_expiry_never(): void {
global $DB;
$this->resetAfterTest();
\core_privacy\local\request\writer::setup_real_writer_instance();
// Disable request expiry.
set_config('privacyrequestexpiry', 0, 'tool_dataprivacy');
// Create and approve data request.
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
$this->setUser($user->id);
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
$requestid = $datarequest->get('id');
$this->setAdminUser();
api::approve_data_request($requestid);
ob_start();
$this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
ob_end_clean();
// Run expiry deletion - should not affect test export.
$expiredrequests = data_request::get_expired_requests();
$this->assertEmpty($expiredrequests);
data_request::expire($expiredrequests);
// Confirm approved and exported.
$request = new data_request($requestid);
$this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $request->get('status'));
$fileconditions = [
'userid' => $user->id,
'component' => 'tool_dataprivacy',
'filearea' => 'export',
'itemid' => $requestid,
'contextid' => $usercontext->id,
];
$this->assertEquals(2, $DB->count_records('files', $fileconditions));
}
/**
* Test for \tool_dataprivacy\data_request::is_expired()
* Tests for the expected request status to protect from false positive/negative,
@@ -14,6 +14,6 @@ Feature: In a report, admin can see logstore visibility changes
And I click on "Enable" "icon" in the "External database log" "table_row"
And I navigate to "Reports > Config changes" in site administration
Then the following should exist in the "reportbuilder-table" table:
| User | Plugin | Setting | New value | Original value |
| First name | Plugin | Setting | New value | Original value |
| Admin User | logstore_standard | tool_logstore_visibility | 0 | 1 |
| Admin User | logstore_database | tool_logstore_visibility | 1 | 0 |
File diff suppressed because one or more lines are too long
-2
View File
@@ -71,14 +71,12 @@ define(['jquery'], function($) {
return null;
};
// eslint-disable-line valid-jsdoc
/**
* Return the type of the module.
*
* @return {String}
* @method getType
*/
// eslint-enable-line valid-jsdoc
Rule.prototype.getType = function() {
throw new Error('Not implemented');
};
+2 -2
View File
@@ -419,13 +419,13 @@ class api {
public static function get_qrlogin_key(stdClass $mobilesettings) {
global $USER;
// Delete previous keys.
delete_user_key('tool_mobile', $USER->id);
delete_user_key('tool_mobile/qrlogin', $USER->id);
// Create a new key.
$iprestriction = !empty($mobilesettings->qrsameipcheck) ? getremoteaddr(null) : null;
$qrkeyttl = !empty($mobilesettings->qrkeyttl) ? $mobilesettings->qrkeyttl : self::LOGIN_QR_KEY_TTL;
$validuntil = time() + $qrkeyttl;
return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil);
return create_user_key('tool_mobile/qrlogin', $USER->id, null, $iprestriction, $validuntil);
}
/**
@@ -0,0 +1,45 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_mobile;
use core\session\utility\cookie_helper;
use core\event\user_loggedin;
/**
* Event handler for tool_mobile.
*
* @package tool_mobile
* @copyright 2024 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_handler {
/**
* Allows the plugin to augment Set-Cookie headers when the user_loggedin event is fired as part of complete_user_login() calls.
*
* @param user_loggedin $event the event
* @return void
*/
public static function handle_user_loggedin(user_loggedin $event): void {
global $CFG;
// Set Partitioned and Secure attributes to the MoodleSession cookie if the user is using the Moodle app.
if (\core_useragent::is_moodle_app()) {
cookie_helper::add_attributes_to_cookie_response_header('MoodleSession'.$CFG->sessioncookie, ['Secure', 'Partitioned']);
}
}
}
+2 -2
View File
@@ -649,8 +649,8 @@ class external extends external_api {
api::check_autologin_prerequisites($params['userid']); // Checks https, avoid site admins using this...
// Validate and delete the key.
$key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
delete_user_key('tool_mobile', $params['userid']);
$key = validate_user_key($params['qrloginkey'], 'tool_mobile/qrlogin', null);
delete_user_key('tool_mobile/qrlogin', $params['userid']);
// Double check key belong to user.
if ($key->userid != $params['userid']) {
@@ -66,7 +66,7 @@ class provider implements
FROM {user_private_key} k
JOIN {user} u ON k.userid = u.id
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
WHERE k.userid = :userid AND k.script = 'tool_mobile'";
WHERE k.userid = :userid AND (k.script = 'tool_mobile' OR k.script = 'tool_mobile/qrlogin')";
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
@@ -88,6 +88,7 @@ class provider implements
// Add users based on userkey.
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile');
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile/qrlogin');
}
/**
@@ -108,6 +109,7 @@ class provider implements
}
// Export associated userkeys.
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile');
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile/qrlogin');
}
/**
* Export all user preferences for the plugin.
@@ -138,6 +140,7 @@ class provider implements
$userid = $context->instanceid;
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
}
/**
* Delete all user data for the specified user, in the specified contexts.
@@ -158,6 +161,7 @@ class provider implements
$userid = $context->instanceid;
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
}
/**
@@ -178,5 +182,6 @@ class provider implements
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
/**
* tool_mobile plugin event handler definition.
*
* @package tool_mobile
* @category event
* @copyright 2024 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = [
[
'eventname' => '\core\event\user_loggedin',
'callback' => '\tool_mobile\event_handler::handle_user_loggedin',
],
];
+8
View File
@@ -69,6 +69,14 @@ if (empty($service)) {
throw new moodle_exception('servicenotavailable', 'webservice');
}
// If the user is using the inapp (embedded) browser, we need to set the Secure and Partitioned attributes to the session cookie.
if (\core_useragent::is_moodle_app()) {
\core\session\utility\cookie_helper::add_attributes_to_cookie_response_header(
"MoodleSession{$CFG->sessioncookie}",
['Secure', 'Partitioned'],
);
}
require_login(0, false);
// Require an active user: not guest, not suspended.
+13
View File
@@ -265,3 +265,16 @@ function tool_mobile_pre_processor_message_send($procname, $data) {
$data->fullmessagehtml .= html_writer::tag('p', get_string('readingthisemailgettheapp', 'tool_mobile', $url->out()));
}
}
/**
* Callback to add headers before the HTTP headers are sent.
*
*/
function tool_mobile_before_http_headers() {
global $CFG;
// Set Partitioned and Secure attributes to the MoodleSession cookie if the user is using the Moodle app.
if (\core_useragent::is_moodle_app()) {
\core\session\utility\cookie_helper::add_attributes_to_cookie_response_header('MoodleSession'.$CFG->sessioncookie, ['Secure', 'Partitioned']);
}
}
@@ -87,7 +87,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
$context1 = \context_user::instance($user1->id);
$context2 = \context_user::instance($user2->id);
$key1 = get_user_key('tool_mobile', $user1->id);
$key2 = get_user_key('tool_mobile', $user2->id);
$key2 = get_user_key('tool_mobile/qrlogin', $user1->id);
$key3 = get_user_key('tool_mobile', $user2->id);
// Ensure only user1 is found in context1.
$userlist = new \core_privacy\local\request\userlist($context1, $component);
@@ -174,12 +175,15 @@ class provider_test extends \core_privacy\tests\provider_testcase {
$context1 = \context_user::instance($user1->id);
$context2 = \context_user::instance($user2->id);
$keyvalue1 = get_user_key('tool_mobile', $user1->id);
$keyvalue2 = get_user_key('tool_mobile', $user2->id);
$keyvalue2 = get_user_key('tool_mobile/qrlogin', $user1->id);
$keyvalue3 = get_user_key('tool_mobile', $user2->id);
$key1 = $DB->get_record('user_private_key', ['value' => $keyvalue1]);
// Before deletion, we should have 2 user_private_keys.
// Before deletion, we should have 2 user_private_keys for tool_mobile and one for tool_mobile/qrlogin.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(2, $count);
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
$this->assertEquals(1, $count);
// Ensure deleting wrong user in the user context does nothing.
$approveduserids = [$user2->id];
@@ -197,6 +201,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
// Ensure only user1's data is deleted, user2's remains.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(1, $count);
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
$this->assertEquals(0, $count);
$params = ['script' => $component];
$userid = $DB->get_field_select('user_private_key', 'userid', 'script = :script', $params);
+1 -1
View File
@@ -23,7 +23,7 @@
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2022112801; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2022111800; // Requires this Moodle version.
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
File diff suppressed because one or more lines are too long
@@ -121,7 +121,6 @@ define(['tool_moodlenet/validator',
});
// Trigger the transition between 'pages'.
carousel.carousel(2);
// eslint-disable-next-line max-len
modal.setFooter(Templates.render('tool_moodlenet/chooser_footer_close_mnet', {}));
};
+1 -1
View File
@@ -63,7 +63,7 @@ if ($cancel) {
}
redirect($url);
} else if ($continue) {
confirm_sesskey();
require_sesskey();
// Handle backups.
if (strtolower($importinfo->get_resource()->get_extension()) == 'mbz') {
+1 -1
View File
@@ -81,7 +81,7 @@ switch ($config->type) {
}
if ($import && $module) {
confirm_sesskey();
require_sesskey();
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($importinfo->get_resource(), $module, $strategy);
if (is_null($handlerinfo)) {
@@ -170,6 +170,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
And I wait until "Activate" "button" exists
And I press "Activate"
And I log out
When I log in as "manager"
@@ -260,6 +261,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
And I wait until "Activate" "button" exists
And I press "Activate"
And I log out
When I log in as "manager"
@@ -62,8 +62,8 @@ Feature: Manage scheduled tasks
And I press "Save changes"
Then I should see "Changes saved"
And the following should not exist in the "admintable" table:
| Name | Component | Minute | Hour | Day | Day of week | Month |
| Log table cleanup | Standard log | */5 | 1 | 2 | 4 | 3 |
| Name | Minute | Hour | Day | Day of week | Month |
| Log table cleanup | */5 | 1 | 2 | 4 | 3 |
And I should see "Log table cleanup" in the "tr.table-primary" "css_element"
Scenario: Disabled plugin's tasks are labelled as disabled too
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+33 -11
View File
@@ -36,6 +36,7 @@ import {dispatchEvent} from 'core/event_dispatcher';
import {eventTypes} from './events';
import {get_string as getString} from 'core/str';
import {prefetchStrings} from 'core/prefetch';
import PendingPromise from 'core/pending';
/**
* The minimum spacing for tour step to display.
@@ -485,14 +486,22 @@ const Tour = class {
return this.endTour();
}
const pendingPromise = new PendingPromise(`tool_usertours/tour:_gotoStep-${stepConfig.stepNumber}`);
if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay && !stepConfig.delayed) {
stepConfig.delayed = true;
window.setTimeout(this._gotoStep.bind(this), stepConfig.delay, stepConfig, direction);
window.setTimeout(function(stepConfig, direction) {
this._gotoStep(stepConfig, direction);
pendingPromise.resolve();
}, stepConfig.delay, stepConfig, direction);
return this;
} else if (!stepConfig.orphan && !this.isStepActuallyVisible(stepConfig)) {
let fn = direction == -1 ? 'getPreviousStepNumber' : 'getNextStepNumber';
return this.gotoStep(this[fn](stepConfig.stepNumber), direction);
const fn = direction == -1 ? 'getPreviousStepNumber' : 'getNextStepNumber';
this.gotoStep(this[fn](stepConfig.stepNumber), direction);
pendingPromise.resolve();
return this;
}
this.hide();
@@ -503,6 +512,7 @@ const Tour = class {
this.dispatchEvent(eventTypes.stepRendered, {stepConfig});
}
pendingPromise.resolve();
return this;
}
@@ -820,12 +830,14 @@ const Tour = class {
left: 0,
});
const pendingPromise = new PendingPromise(`tool_usertours/tour:addStepToPage-${stepConfig.stepNumber}`);
animationTarget
.animate({
scrollTop: this.calculateScrollTop(stepConfig),
}).promise().then(function() {
this.positionStep(stepConfig);
this.revealStep(stepConfig);
pendingPromise.resolve();
return;
}.bind(this))
.catch(function() {
@@ -897,6 +909,7 @@ const Tour = class {
*/
revealStep(stepConfig) {
// Fade the step in.
const pendingPromise = new PendingPromise(`tool_usertours/tour:revealStep-${stepConfig.stepNumber}`);
this.currentStepNode.fadeIn('', $.proxy(function() {
// Announce via ARIA.
this.announceStep(stepConfig);
@@ -910,6 +923,7 @@ const Tour = class {
if (this.currentStepNode) {
this.currentStepNode.focus();
}
pendingPromise.resolve();
}, this), 100);
}, this));
@@ -1157,6 +1171,7 @@ const Tour = class {
return this;
}
const pendingPromise = new PendingPromise('tool_usertours/tour:hide');
if (this.currentStepNode && this.currentStepNode.length) {
this.currentStepNode.hide();
if (this.currentStepPopper) {
@@ -1191,17 +1206,22 @@ const Tour = class {
this.currentStepConfig = null;
}
let fadeTime = 0;
if (transition) {
fadeTime = 400;
}
// Remove the backdrop features.
$('[data-flexitour="step-background"]').remove();
$('[data-flexitour="step-backdrop"]').removeAttr('data-flexitour');
$('[data-flexitour="backdrop"]').fadeOut(fadeTime, function() {
$(this).remove();
});
const backdrop = $('[data-flexitour="backdrop"]');
if (backdrop.length) {
if (transition) {
const backdropRemovalPromise = new PendingPromise('tool_usertours/tour:hide:backdrop');
backdrop.fadeOut(400, function() {
$(this).remove();
backdropRemovalPromise.resolve();
});
} else {
backdrop.remove();
}
}
// Remove aria-describedby and tabindex attributes.
if (this.currentStepNode && this.currentStepNode.length) {
@@ -1222,6 +1242,8 @@ const Tour = class {
this.currentStepNode = null;
this.currentStepPopper = null;
pendingPromise.resolve();
return this;
}
@@ -36,6 +36,11 @@ class new_table_from_mysql extends XMLDBAction {
function init() {
parent::init();
global $DB;
if ($DB->get_dbfamily() !== 'mysql') {
throw new moodle_exception('DB family not supported');
}
// Set own custom attributes
// Get needed strings
@@ -55,8 +60,6 @@ class new_table_from_mysql extends XMLDBAction {
function invoke() {
parent::invoke();
$result = true;
// Set own core attributes
$this->does_generate = ACTION_GENERATE_HTML;
@@ -125,11 +128,15 @@ class new_table_from_mysql extends XMLDBAction {
// If table, retrofit information and, if everything works,
// go to the table edit action
} else {
// Get some params (table is mandatory here)
$tableparam = required_param('table', PARAM_CLEAN);
$afterparam = required_param('after', PARAM_CLEAN);
// Get some params (table is mandatory here).
$tableparam = required_param('table', PARAM_ALPHANUMEXT);
$afterparam = required_param('after', PARAM_ALPHANUMEXT);
// Create one new xmldb_table
if (empty($tableparam) || empty($afterparam)) {
throw new moodle_exception('Invalid param value detected.');
}
// Create one new xmldb_table.
$table = new xmldb_table(strtolower(trim($tableparam)));
$table->setComment($table->getName() . ' table retrofitted from MySQL');
// Get fields info from ADODb
@@ -147,7 +154,6 @@ class new_table_from_mysql extends XMLDBAction {
// Get PK, UK and indexes info from ADODb
$dbindexes = $DB->get_indexes($tableparam);
if ($dbindexes) {
$lastkey = NULL; //To temp store the last key processed
foreach ($dbindexes as $indexname => $dbindex) {
// Add the indexname to the array
$dbindex['name'] = $indexname;
@@ -156,9 +162,6 @@ class new_table_from_mysql extends XMLDBAction {
$key = new xmldb_key(strtolower($dbindex['name']));
// Set key with info retrofitted
$key->setFromADOKey($dbindex);
// Set default comment to PKs
if ($key->getType() == XMLDB_KEY_PRIMARY) {
}
// Add key to the table
$table->addKey($key);
@@ -172,18 +175,17 @@ class new_table_from_mysql extends XMLDBAction {
}
}
}
// Finally, add the whole retroffited table to the structure
// in the place specified
// Finally, add the whole retrofitted table to the structure in the place specified.
$structure->addTable($table, $afterparam);
}
// Launch postaction if exists (leave this here!)
if ($this->getPostAction() && $result) {
if ($this->getPostAction()) {
return $this->launch($this->getPostAction());
}
// Return ok if arrived here
return $result;
return true;
}
}
+3 -1
View File
@@ -114,6 +114,7 @@ Feature: Manage analytics models
And I should see "Read actions amount"
And I click on "Select Student 6 for bulk action" "checkbox" in the "Student 6" "table_row"
And I click on "Accept" "button"
And I wait until "Confirm" "button" exists
And I click on "Confirm" "button" in the "Accept" "dialogue"
And I click on "View prediction details" "icon" in the "Student 5" "table_row"
And I click on "Select Student 5 for bulk action" "checkbox" in the "Student 5" "table_row"
@@ -145,7 +146,8 @@ Feature: Manage analytics models
When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
And I choose "Export" in the open action menu
And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
And following "Export" should download between "100" and "500" bytes
And following "Export" should download a file that:
| Contains file in zip | model-config.json |
Scenario: Check invalid site elements
When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
@@ -14,31 +14,29 @@
// 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 activities completed by classification.
*
* @package core_analytics
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_analytics;
defined('MOODLE_INTERNAL') || die();
use advanced_testcase;
use ReflectionClass;
use ReflectionMethod;
use stdClass;
/**
* Unit tests for activities completed by classification.
*
* @package core_analytics
* @covers \core_analytics\local\indicator\community_of_inquiry_activity
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class community_of_inquiry_activities_completed_by_testcase extends advanced_testcase {
final class community_of_inquiry_activities_completed_by_test extends advanced_testcase {
/**
* availability_levels
*
* @return array
*/
public function availability_levels() {
public static function availability_levels(): array {
return array(
'activity' => array('activity'),
'section' => array('section'),
@@ -148,7 +146,7 @@ class community_of_inquiry_activities_completed_by_testcase extends advanced_tes
$second = $startdate + WEEKSECS;
$third = $startdate + (WEEKSECS * 2);
$forth = $startdate + (WEEKSECS * 3);
$this->assertCount(1, $method->invoke($indicator, $first, $first + WEEKSECS, $stu1));
$this->assertCount(2, $method->invoke($indicator, $first, $first + WEEKSECS, $stu1));
$this->assertCount(1, $method->invoke($indicator, $second, $second + WEEKSECS, $stu1));
$this->assertCount(0, $method->invoke($indicator, $third, $third + WEEKSECS, $stu1));
$this->assertCount(2, $method->invoke($indicator, $forth, $forth + WEEKSECS, $stu1));
+1 -1
View File
@@ -116,7 +116,7 @@ class auth_plugin_lti extends \auth_plugin_base {
if (isloggedin()) {
// If a different user is currently logged in, authenticate the linked user instead.
global $USER;
if ((int) $USER->id !== $user->id) {
if ($USER->id !== $user->id) {
complete_user_login($user);
}
// If the linked user is already logged in, skip the call to complete_user_login() because this affects deep linking
@@ -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/>.
namespace auth_lti\local\ltiadvantage\event;
use auth_lti\local\ltiadvantage\utility\cookie_helper;
use core\event\user_loggedin;
/**
* Event handler for auth_lti.
*
* @package auth_lti
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_handler {
/**
* Allows the plugin to augment Set-Cookie headers when the user_loggedin event is fired as part of complete_user_login() calls.
*
* @param user_loggedin $event the event
* @return void
*/
public static function handle_user_loggedin(user_loggedin $event): void {
// The event data isn't important here. The intent of this listener is to ensure that the MoodleSession cookie is set up
// properly during LTI launches + login. This means two things:
// i) it's set with SameSite=None; Secure; where possible (since OIDC needs HTTPS this will almost always be possible).
// ii) it set with the 'Partitioned' attribute, when required.
// The former ensures cross-site cookies are sent for embedded launches. The latter is an opt-in flag needed to use Chrome's
// partitioning mechanism, CHIPS.
if (cookie_helper::cookies_supported()) {
cookie_helper::setup_session_cookie();
}
}
}
@@ -0,0 +1,173 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_lti\local\ltiadvantage\utility;
use core\session\utility\cookie_helper as core_cookie_helper;
/**
* Helper class providing utils dealing with cookies in LTI, particularly 3rd party cookies.
*
* @package auth_lti
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class cookie_helper {
/** @var int Cookies are not supported. */
public const COOKIE_METHOD_NOT_SUPPORTED = 0;
/** @var int Cookies are supported without explicit partitioning. */
public const COOKIE_METHOD_NO_PARTITIONING = 1;
/** @var int Cookies are supported via explicit partitioning. */
public const COOKIE_METHOD_EXPLICIT_PARTITIONING = 2;
/**
* Check whether cookies can be used with the current user agent and, if so, via what method they are set.
*
* Currently, this tries 2 modes of setting a test cookie:
* 1. Setting a SameSite=None, Secure cookie. This will work in any first party context, and in 3rd party contexts for
* any browsers supporting automatic partitioning of 3rd party cookies (E.g. Firefox, Brave).
* 2. If 1 fails, setting a cookie with the Chrome 'Partitioned' attribute included, opting that cookie into CHIPS. This will
* work for Chrome.
*
* Upon completion of the cookie check, the check sets a SESSION flag indicating the method used to set the cookie, and upgrades
* the session cookie ('MoodleSession') using the respective method. This ensure the session cookie will continue to be sent.
*
* Then, the following methods can be used by client code to query whether the UA supports cookies, and how:
* @see self::cookies_supported() - whether it could be set at all.
* @see self::get_cookies_supported_mode() - if a cookie could be set, what mode was used to set it.
*
* This permits client code to make sure it's setting its cookies appropriately (via the advertised method), and allows it to
* present notices - such as in the case where a given UA is found to be lacking the requisite cookie support.
* E.g.
* cookie_helper::do_cookie_check($mypageurl);
* if (!cookie_helper::cookies_supported()) {
* // Print a notice stating that cookie support is required.
* }
* // Elsewhere in other client code...
* if (cookie_helper::get_cookies_supported_mode() === cookie_helper::COOKIE_METHOD_EXPLICIT_PARTITIONING) {
* // Set a cookie, making sure to use the helper to also opt-in to partitioning.
* setcookie('myauthcookie', 'myauthcookievalue', ['samesite' => 'None', 'secure' => true]);
* cookie_helper::add_partitioning_to_cookie('myauthcookie');
* }
*
* @param \moodle_url $pageurl the URL of the page making the check, used to redirect back to after setting test cookies.
* @return void
*/
public static function do_cookie_check(\moodle_url $pageurl): void {
global $_COOKIE, $SESSION, $CFG;
$cookiecheck1 = optional_param('cookiecheck1', null, PARAM_INT);
$cookiecheck2 = optional_param('cookiecheck2', null, PARAM_INT);
if (empty($cookiecheck1)) {
// Start the cookie check. Set two test cookies - one samesite none, and one partitioned - and redirect.
// Set cookiecheck to show the check has started.
self::set_test_cookie('cookiecheck1', self::COOKIE_METHOD_NO_PARTITIONING);
self::set_test_cookie('cookiecheck2', self::COOKIE_METHOD_EXPLICIT_PARTITIONING, true);
$pageurl->params([
'cookiecheck1' => self::COOKIE_METHOD_NO_PARTITIONING,
'cookiecheck2' => self::COOKIE_METHOD_EXPLICIT_PARTITIONING,
]);
// LTI needs to guarantee the 'SameSite=None', 'Secure' (and sometimes 'Partitioned') attributes are set on the
// MoodleSession cookie. This is done via manipulation of the outgoing headers after the cookie check redirect. To
// guarantee these outgoing Set-Cookie headers will be created after the redirect, expire the current cookie.
core_cookie_helper::expire_moodlesession();
redirect($pageurl);
} else {
// Have already started a cookie check, so check the result.
$cookie1received = isset($_COOKIE['cookiecheck1']) && $_COOKIE['cookiecheck1'] == $cookiecheck1;
$cookie2received = isset($_COOKIE['cookiecheck2']) && $_COOKIE['cookiecheck2'] == $cookiecheck2;
if ($cookie1received || $cookie2received) {
// The test cookie could be set and received.
// Set a session flag storing the method used to set it, and make sure the session cookie uses this method.
$cookiemethod = $cookie1received ? self::COOKIE_METHOD_NO_PARTITIONING : self::COOKIE_METHOD_EXPLICIT_PARTITIONING;
$SESSION->auth_lti_cookie_method = $cookiemethod;
self::setup_session_cookie();
}
}
}
/**
* If a cookie check has been made, returns whether cookies could be set or not.
*
* @return bool whether cookies are supported or not.
*/
public static function cookies_supported(): bool {
return self::get_cookies_supported_method() !== self::COOKIE_METHOD_NOT_SUPPORTED;
}
/**
* If a cookie check has been made, gets the method used to set a cookie, or self::COOKIE_METHOD_NOT_SUPPORTED if not supported.
*
* For cookie methods:
* @see self::COOKIE_METHOD_NOT_SUPPORTED
* @see self::COOKIE_METHOD_NO_PARTITIONING
* @see self::COOKIE_METHOD_EXPLICIT_PARTITIONING
*
* @return int the constant representing the method by which the cookie was set, or not.
*/
public static function get_cookies_supported_method(): int {
global $SESSION;
return $SESSION->auth_lti_cookie_method ?? self::COOKIE_METHOD_NOT_SUPPORTED;
}
/**
* Sets up the session cookie according to the method used in the cookie check, and with SameSite=None; Secure attributes.
*
* @return void
*/
public static function setup_session_cookie(): void {
global $CFG;
require_once($CFG->libdir . '/sessionlib.php');
if (is_moodle_cookie_secure()) {
$atts = ['SameSite=None', 'Secure'];
if (self::get_cookies_supported_method() == self::COOKIE_METHOD_EXPLICIT_PARTITIONING) {
$atts[] = 'Partitioned';
}
core_cookie_helper::add_attributes_to_cookie_response_header('MoodleSession' . $CFG->sessioncookie, $atts);
}
}
/**
* Set a test cookie, using SameSite=None; Secure; attributes if possible, and with or without partitioning opt-in.
*
* @param string $name cookie name
* @param string $value cookie value
* @param bool $partitioned whether to try to add partitioning opt-in, which requires secure cookies (https sites).
* @return void
*/
private static function set_test_cookie(string $name, string $value, bool $partitioned = false): void {
global $CFG;
require_once($CFG->libdir . '/sessionlib.php');
$atts = ['expires' => time() + 30];
if (is_moodle_cookie_secure()) {
$atts['samesite'] = 'none';
$atts['secure'] = true;
}
setcookie($name, $value, $atts);
if (is_moodle_cookie_secure() && $partitioned) {
core_cookie_helper::add_attributes_to_cookie_response_header($name, ['Partitioned']);
}
}
}
+5 -6
View File
@@ -33,33 +33,32 @@ class renderer extends \plugin_renderer_base {
* @return string the html.
*/
public function render_account_binding_options_page(int $provisioningmode): string {
$formaction = new \moodle_url('/auth/lti/login.php');
$notification = new notification(get_string('firstlaunchnotice', 'auth_lti'), \core\notification::INFO, false);
$noauthnotice = new notification(get_string('firstlaunchnoauthnotice', 'auth_lti', get_docs_url('Publish_as_LTI_tool')),
\core\notification::WARNING, false);
$cancreateaccounts = !get_config('moodle', 'authpreventaccountcreation');
if ($provisioningmode == \auth_plugin_lti::PROVISIONING_MODE_PROMPT_EXISTING_ONLY) {
$cancreateaccounts = false;
}
$accountinfo = ['isloggedin' => isloggedin()];
$accountinfo = [];
if (isloggedin()) {
global $USER;
$accountinfo = array_merge($accountinfo, [
$accountinfo = [
'firstname' => $USER->firstname,
'lastname' => $USER->lastname,
'email' => $USER->email,
'picturehtml' => $this->output->user_picture($USER, ['size' => 35, 'class' => 'round']),
]);
];
}
$context = [
'isloggedin' => isloggedin(),
'info' => $notification->export_for_template($this),
'formaction' => $formaction->out(),
'sesskey' => sesskey(),
'accountinfo' => $accountinfo,
'cancreateaccounts' => $cancreateaccounts,
'noauthnotice' => $noauthnotice->export_for_template($this)
];
return parent::render_from_template('auth_lti/local/ltiadvantage/login', $context);
}
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
/**
* LTI Auth plugin event handler definition.
*
* @package auth_lti
* @category event
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = [
[
'eventname' => '\core\event\user_loggedin',
'callback' => '\auth_lti\local\ltiadvantage\event\event_handler::handle_user_loggedin',
],
];
+1 -1
View File
@@ -35,7 +35,7 @@ $string['firstlaunchnoauthnotice'] = 'To link your existing account you must be
$string['getstartedwithnewaccount'] = 'Get started with a new account';
$string['haveexistingaccount'] = 'I have an existing account';
$string['linkthisaccount'] = 'Link this account';
$string['mustbeloggedin'] = 'You need to be logged in to your existing account';
$string['mustbeloggedin'] = 'Sign in to link your existing account';
$string['pluginname'] = 'LTI';
$string['privacy:metadata:auth_lti'] = 'LTI authentication';
$string['privacy:metadata:auth_lti:authsubsystem'] = 'This plugin is connected to the authentication subsystem.';
@@ -32,7 +32,6 @@
* info - a notification describing the first launch options
* cancreateaccounts - whether or not the user is allowed to create auth_lti accounts
* accountinfo - information about the user, importantly whether they are logged in or not.
* noauthnotice - a notification telling the user they must be authenticated to link accounts. Only relevant when not logged in.
Example context (json):
{
@@ -46,19 +45,12 @@
"issuccess": true
},
"cancreateaccounts": true,
"isloggedin": true,
"accountinfo": {
"isloggedin": true,
"firstname": "John",
"lastname": "Smith",
"email": "john@example.com",
"picturehtml": "<img src=\"http://site.example.com/pluginfile.php/5/user/icon/boost/f2?rev=99\" class=\"round\" alt=\"\" width=\"35\" height=\"35\">"
},
"noauthnotice": {
"message": "To link your existing account you must be logged in to the site...",
"extraclasses": "",
"announce": false,
"closebutton": false,
"iswarning": true
}
}
}}
@@ -79,8 +71,8 @@
<div class="card-body text-center d-flex flex-column">
<i class="fa fa-user-circle-o fa-2x link"></i>
<h4 class="card-title">{{#str}} useexistingaccount, auth_lti {{/str}}</h4>
{{#accountinfo}}
{{#isloggedin}}
{{#accountinfo}}
<p class="card-text mt-2">
<span class="text-muted">
{{#str}} currentlyloggedinas, auth_lti {{/str}}
@@ -90,14 +82,12 @@
{{firstname}} {{lastname}} ({{email}})
</p>
<input type="submit" class="btn btn-primary mt-auto" name="existing_account" value="{{#str}} linkthisaccount, auth_lti {{/str}}">
{{/accountinfo}}
{{/isloggedin}}
{{^isloggedin}}
<p class="card-text text-muted">{{#str}} mustbeloggedin, auth_lti {{/str}}</p>
{{#noauthnotice}}
{{> core/notification}}
{{/noauthnotice}}
<input type="submit" class="btn btn-primary mt-auto" name="existing_account" value="{{#str}} login, moodle {{/str}}">
{{/isloggedin}}
{{/accountinfo}}
</div>
</div>
</div>
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2022112801; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2022111800; // Requires this Moodle version.
$plugin->component = 'auth_lti'; // Full name of the plugin (used for diagnostics).
+9 -4
View File
@@ -345,7 +345,7 @@ class api {
}
/**
* Delete linked login
* Delete a users own linked login
*
* Requires auth/oauth2:managelinkedlogins capability at the user context.
*
@@ -353,14 +353,19 @@ class api {
* @return boolean
*/
public static function delete_linked_login($linkedloginid) {
$login = new linked_login($linkedloginid);
$userid = $login->get('userid');
global $USER;
if (\core\session\manager::is_loggedinas()) {
throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
}
$context = context_user::instance($userid);
$login = linked_login::get_record([
'id' => $linkedloginid,
'userid' => $USER->id,
'confirmtoken' => '',
], MUST_EXIST);
$context = context_user::instance($login->get('userid'));
require_capability('auth/oauth2:managelinkedlogins', $context);
$login->delete();
+25
View File
@@ -137,6 +137,7 @@ class api_test extends \advanced_testcase {
$issuer = \core\oauth2\api::create_standard_issuer('google');
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$info = [];
$info['username'] = 'banana';
@@ -169,6 +170,30 @@ class api_test extends \advanced_testcase {
$this->assertEquals($newuser->id, $match->get('userid'));
}
/**
* Test that we cannot deleted a linked login for another user
*/
public function test_delete_linked_login_other_user(): void {
$this->resetAfterTest();
$this->setAdminUser();
$issuer = \core\oauth2\api::create_standard_issuer('google');
$user = $this->getDataGenerator()->create_user();
api::link_login([
'username' => 'banana',
'email' => 'banana@example.com',
], $issuer, $user->id);
/** @var linked_login $linkedlogin */
$linkedlogin = api::get_linked_logins($user->id)[0];
// We are logged in as a different user, so cannot delete this.
$this->expectException(\dml_missing_record_exception::class);
api::delete_linked_login($linkedlogin->get('id'));
}
/**
* Test that is_enabled correctly identifies when the plugin is enabled.
*/
+1 -1
View File
@@ -52,7 +52,7 @@ abstract class backup_block_task extends backup_task {
}
$this->blockid = $blockid;
$this->blockname = $block->blockname;
$this->blockname = clean_param($block->blockname, PARAM_PLUGIN);
$this->contextid = context_block::instance($this->blockid)->id;
$this->moduleid = $moduleid;
$this->modulename = null;
+10 -10
View File
@@ -134,14 +134,14 @@ class core_backup_renderer extends plugin_renderer_base {
$html .= $this->backup_detail_pair(get_string('backupmode', 'backup'), get_string('backupmode'.$details->mode, 'backup'));
$html .= $this->backup_detail_pair(get_string('backupdate', 'backup'), userdate($details->backup_date));
$html .= $this->backup_detail_pair(get_string('moodleversion', 'backup'),
html_writer::tag('span', $details->moodle_release, array('class' => 'moodle_release')).
html_writer::tag('span', '['.$details->moodle_version.']', array('class' => 'moodle_version sub-detail')));
html_writer::tag('span', s($details->moodle_release), array('class' => 'moodle_release')).
html_writer::tag('span', '[' . s($details->moodle_version) .']', array('class' => 'moodle_version sub-detail')));
$html .= $this->backup_detail_pair(get_string('backupversion', 'backup'),
html_writer::tag('span', $details->backup_release, array('class' => 'moodle_release')).
html_writer::tag('span', '['.$details->backup_version.']', array('class' => 'moodle_version sub-detail')));
html_writer::tag('span', s($details->backup_release), array('class' => 'moodle_release')).
html_writer::tag('span', '[' . s($details->backup_version) . ']', array('class' => 'moodle_version sub-detail')));
$html .= $this->backup_detail_pair(get_string('originalwwwroot', 'backup'),
html_writer::tag('span', $details->original_wwwroot, array('class' => 'originalwwwroot')).
html_writer::tag('span', '['.$details->original_site_identifier_hash.']', array('class' => 'sitehash sub-detail')));
html_writer::tag('span', s($details->original_wwwroot), array('class' => 'originalwwwroot')).
html_writer::tag('span', '[' . s($details->original_site_identifier_hash) . ']', array('class' => 'sitehash sub-detail')));
if (!empty($details->include_file_references_to_external_content)) {
$message = '';
if (backup_general_helper::backup_is_samesite($details)) {
@@ -169,8 +169,8 @@ class core_backup_renderer extends plugin_renderer_base {
$html .= html_writer::start_tag('div', ['class' => 'backup-section',
'role' => 'table', 'aria-labelledby' => 'backupcoursedetailsheader']);
$html .= $this->output->heading(get_string('backupcoursedetails', 'backup'), 2, 'header', 'backupcoursedetailsheader');
$html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), $details->course->title);
$html .= $this->backup_detail_pair(get_string('courseid', 'backup'), $details->course->courseid);
$html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), format_string($details->course->title));
$html .= $this->backup_detail_pair(get_string('courseid', 'backup'), clean_param($details->course->courseid, PARAM_INT));
// Warning users about front page backups.
if ($details->original_course_format === 'site') {
@@ -188,7 +188,7 @@ class core_backup_renderer extends plugin_renderer_base {
} else {
continue;
}
$html .= $this->backup_detail_pair(get_string('backupcoursesection', 'backup', $section->title), $value);
$html .= $this->backup_detail_pair(get_string('backupcoursesection', 'backup', format_string($section->title)), $value);
$table = null;
foreach ($details->activities as $activitykey => $activity) {
if ($activity->sectionid != $section->sectionid) {
@@ -206,7 +206,7 @@ class core_backup_renderer extends plugin_renderer_base {
$icon = new image_icon('monologo', '', $activity->modulename, ['class' => 'iconlarge icon-pre']);
$table->data[] = array(
$this->output->render($icon).$name,
$activity->title,
format_string($activity->title),
($activity->settings[$activitykey.'_userinfo']) ? $yestick : $notick,
);
}
+2 -1
View File
@@ -107,9 +107,10 @@ if (!has_any_capability(array(
$PAGE->set_title($hdr);
$output = $PAGE->get_renderer('core', 'badges');
if (($delete || $archive) && has_capability('moodle/badges:deletebadge', $PAGE->context)) {
if ($delete || $archive) {
$badgeid = ($archive != 0) ? $archive : $delete;
$badge = new badge($badgeid);
require_capability('moodle/badges:deletebadge', $badge->get_context());
if (!$confirm) {
echo $output->header();
// Archive this badge?
@@ -119,3 +119,97 @@ Feature: Award badges with separate groups
And I press "Award badge"
# Teacher 2 shouldn't be able to go further
Then I should see "Sorry, but you need to be part of a group to see this page."
@javascript
Scenario: Editing teacher can award badge to members of separate groups
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Badges > Manage badges" in current page administration
And I follow "Course Badge"
And I press "Award badge"
When I set the field "role" to "Teacher"
# Confirm that editing teacher sees a separate groups dropdown menu.
Then "Separate groups" "select" should exist
And I should see "All participants" in the "Separate groups" "select"
# Confirm that all participants are displayed when All participants is selected.
And I should see "Student 1" in the "potentialrecipients[]" "select"
And I should see "Student 2" in the "potentialrecipients[]" "select"
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
And I set the field "Separate groups" to "Class A"
# Confirm that only members of selected group are displayed
And I should not see "Student 1" in the "potentialrecipients[]" "select"
And I should not see "Teacher 1" in the "potentialrecipients[]" "select"
And I should see "Student 2" in the "potentialrecipients[]" "select"
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
And I set the field "Separate groups" to "Class B"
And I should not see "Student 2" in the "potentialrecipients[]" "select"
And I should not see "Teacher 2" in the "potentialrecipients[]" "select"
And I should see "Student 1" in the "potentialrecipients[]" "select"
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
@javascript
Scenario Outline: Teacher can award badge to members of visible groups
Given I am on the "Course 1" "course editing" page logged in as teacher1
And I expand all fieldsets
# Set the group mode to visible groups.
And I set the field "Group mode" to "Visible groups"
And I press "Save and display"
When I am on the "Course 1" course page logged in as <loggedinuser>
And I navigate to "Badges > Manage badges" in current page administration
And I follow "Course Badge"
And I press "Award badge"
And I set the field "role" to "<awarder>"
# Confirm that teachers see a visible groups dropdown menu.
Then "Visible groups" "select" should exist
# Confirm that My groups option group exists.
And "optgroup[label='My groups']" "css_element" should exist in the "select[name='group']" "css_element"
# Confirm that Other groups option group exists.
And "optgroup[label='Other groups']" "css_element" should exist in the "select[name='group']" "css_element"
# Confirm that all participants are displayed when All participants is selected.
And I set the field "Visible groups" to "All participants"
And I should see "Student 1" in the "potentialrecipients[]" "select"
And I should see "Student 2" in the "potentialrecipients[]" "select"
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
# Confirm that only members of selected group are displayed.
And I set the field "Visible groups" to "Class A"
And I should not see "Student 1" in the "potentialrecipients[]" "select"
And I should not see "Teacher 1" in the "potentialrecipients[]" "select"
And I should see "Student 2" in the "potentialrecipients[]" "select"
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
And I set the field "Visible groups" to "Class B"
And I should not see "Student 2" in the "potentialrecipients[]" "select"
And I should not see "Teacher 2" in the "potentialrecipients[]" "select"
And I should see "Student 1" in the "potentialrecipients[]" "select"
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
Examples:
| loggedinuser | awarder |
| teacher1 | Teacher |
| teacher2 | Non-editing teacher |
@javascript
Scenario Outline: Teacher can award badge to members when group mode is set to no groups
Given I am on the "Course 1" "course editing" page logged in as teacher1
And I expand all fieldsets
# Set the group mode to no groups.
And I set the field "Group mode" to "No groups"
And I press "Save and display"
When I am on the "Course 1" course page logged in as <loggedinuser>
And I navigate to "Badges > Manage badges" in current page administration
And I follow "Course Badge"
And I press "Award badge"
And I set the field "role" to "<awarder>"
# Confirm that no group dropdowns don't exist.
Then "Separate groups" "select" should not exist
And "Visible groups" "select" should not exist
# Confirm all participants are displayed.
And I should see "Student 1" in the "potentialrecipients[]" "select"
And I should see "Student 2" in the "potentialrecipients[]" "select"
And I should see "Teacher 1" in the "potentialrecipients[]" "select"
And I should see "Teacher 2" in the "potentialrecipients[]" "select"
Examples:
| loggedinuser | awarder |
| teacher1 | Teacher |
| teacher2 | Non-editing teacher |
+7 -7
View File
@@ -1,22 +1,22 @@
.block_accessreview_success,
.block_accessreview.block_accessreview_success.hasinfo {
color: #1e451e;
background: #d7e6d7;
border-color: #c8ddc8;
background: #eff5ef;
box-shadow: 0 0 2px 2px #619a61;
}
.block_accessreview_danger,
.block_accessreview.block_accessreview_danger.hasinfo {
color: #6e211e;
background: #f6d9d8;
border-color: #f3c9c8;
background: #fdf7f7;
box-shadow: 0 0 2px 2px #da6960;
}
.block_accessreview_warning,
.block_accessreview.block_accessreview_warning.hasinfo {
color: #7d5a29;
background: #fcefdc;
border-color: #fbe8cd;
color: #694b21;
background: #fdf2e3;
box-shadow: 0 0 2px 2px #c97a0e;
}
.block_accessreview_table {
@@ -58,7 +58,7 @@ class helper {
FROM {block_recentlyaccesseditems} rai
JOIN {course} c ON c.id = rai.courseid
WHERE userid = :userid
ORDER BY rai.timeaccess DESC";
ORDER BY rai.timeaccess DESC, rai.id DESC";
$records = $DB->get_records_sql($sql, $paramsql);
$order = 0;
@@ -27,9 +27,8 @@
<div class="text-xs-center text-center mt-4" data-region="empty-message">
<img
src="{{noitemsimgurl}}"
alt="{{#str}} noitems, block_recentlyaccesseditems {{/str}}"
role="presentation"
alt=""
style="height: 70px; width: 70px;"
>
<p class="text-muted mt-3 mb-0">{{#str}} noitems, block_recentlyaccesseditems {{/str}}</p>
</div>
</div>
+12 -1
View File
@@ -180,7 +180,18 @@ $PAGE->set_pagelayout('admin');
if ($rssid) {
$isadding = false;
$rssrecord = $DB->get_record('block_rss_client', array('id' => $rssid), '*', MUST_EXIST);
if ($managesharedfeeds) {
$select = 'id = :id AND (userid = :userid OR shared = 1)';
} else {
$select = 'id = :id AND userid = :userid';
}
$rssrecord = $DB->get_record_select('block_rss_client', $select, [
'id' => $rssid,
'userid' => $USER->id,
], '*', MUST_EXIST);
} else {
$isadding = true;
$rssrecord = new stdClass;
+14 -7
View File
@@ -61,20 +61,27 @@ if ($returnurl) {
$baseurl = new moodle_url('/blocks/rss_client/managefeeds.php', $urlparams);
$PAGE->set_url($baseurl);
if ($managesharedfeeds) {
$select = '(userid = :userid OR shared = 1)';
} else {
$select = 'userid = :userid';
}
// Process any actions
if ($deleterssid && confirm_sesskey()) {
$DB->delete_records('block_rss_client', array('id'=>$deleterssid));
$deleterssid = $DB->get_field_select('block_rss_client', 'id', "id = :id AND {$select}", [
'id' => $deleterssid,
'userid' => $USER->id
], MUST_EXIST);
$DB->delete_records('block_rss_client', ['id' => $deleterssid]);
redirect($PAGE->url, get_string('feeddeleted', 'block_rss_client'));
}
// Display the list of feeds.
if ($managesharedfeeds) {
$select = '(userid = ' . $USER->id . ' OR shared = 1)';
} else {
$select = 'userid = ' . $USER->id;
}
$feeds = $DB->get_records_select('block_rss_client', $select, null, $DB->sql_order_by_text('title'));
$feeds = $DB->get_records_select('block_rss_client', $select, ['userid' => $USER->id], $DB->sql_order_by_text('title'));
$strmanage = get_string('managefeeds', 'block_rss_client');
@@ -5,7 +5,7 @@ Feature: Edit activities in main menu block
I need to add and edit activities there
@javascript
Scenario: Edit name of acitivity in-place in site main menu block
Scenario: Edit name of activity in-place in site main menu block
Given the following "activity" exists:
| activity | forum |
| course | Acceptance test site |
@@ -36,6 +36,7 @@ Feature: Edit activities in main menu block
| forum | Acceptance test site | 0 | Visible forum |
| forum | Acceptance test site | 0 | My forum name |
And I log in as "admin"
And I change window size to "large"
And I am on site homepage
And I turn editing mode on
And "My forum name" activity in site main menu block should have "Hide" editing icon
+12 -10
View File
@@ -101,13 +101,14 @@ class block_tag_flickr extends block_base {
$request .= '&api_key='.FLICKR_DEV_KEY;
$request .= '&photoset_id='.$this->config->photoset;
$request .= '&per_page='.$numberofphotos;
$request .= '&format=php_serial';
$request .= '&format=json';
// We need to add nojsoncallback=? here, otherwise, Flickr will return the jsonFlickrApi object.
$request .= '&nojsoncallback=?';
$response = $this->fetch_request($request);
$search = @unserialize($response);
if ($search === false && $search != serialize(false)) {
// The response didn't appear to be anything serialized, exit...
$search = @json_decode($response, true);
if (!is_array($search) || json_last_error() !== JSON_ERROR_NONE) {
// The response didn't appear to be in correct format.
return;
}
@@ -126,13 +127,14 @@ class block_tag_flickr extends block_base {
$request .= '&tags='.$tagscsv;
$request .= '&per_page='.$numberofphotos;
$request .= '&sort='.$sortby;
$request .= '&format=php_serial';
$request .= '&format=json';
// We need to add nojsoncallback=? here, otherwise, Flickr will return the jsonFlickrApi object.
$request .= '&nojsoncallback=?';
$response = $this->fetch_request($request);
$search = @unserialize($response);
if ($search === false && $search != serialize(false)) {
// The response didn't appear to be anything serialized, exit...
$search = @json_decode($response, true);
if (!is_array($search) || json_last_error() !== JSON_ERROR_NONE) {
// The response didn't appear to be in correct format.
return;
}
$photos = array_values($search['photos']['photo']);
+2 -1
View File
@@ -608,7 +608,8 @@ class cache implements cache_loader {
// store; parent method will have set it to all stores if needed.
if ($setaftervalidation) {
$lock = null;
if (!empty($this->requirelockingbeforewrite)) {
// Only try to acquire a lock for this cache if we do not already have one.
if (!empty($this->requirelockingbeforewrite) && !$this->check_lock_state($key)) {
$lock = $this->acquire_lock($key);
}
if ($requiredversion === self::VERSION_NONE) {
@@ -78,7 +78,8 @@ trait eventtype {
if (!empty($eventtypes['user']) && count($options) == 1) {
$mform->addElement('hidden', 'eventtype');
$mform->setType('eventtype', PARAM_TEXT);
$mform->setDefault('eventtype', 'user');
$mform->hardFreeze('eventtype');
$mform->setConstant('eventtype', 'user');
return;
} else {
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
@@ -106,7 +106,7 @@ class managesubscriptions extends \moodleform {
$courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
$eventtypes = calendar_get_allowed_event_types($courseid);
if (empty($eventtype) || !isset($eventtypes[$eventtype])) {
if (empty($eventtype) || !isset($eventtypes[$eventtype]) || $eventtypes[$eventtype] == false) {
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
}
+11 -1
View File
@@ -898,7 +898,17 @@ class core_calendar_external extends external_api {
$courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
$editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context);
$formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid];
$formoptions['eventtypes'] = calendar_get_allowed_event_types($courseid);
$allowedeeventtypes = calendar_get_allowed_event_types($courseid);
// Event type validation.
if (in_array(true, $allowedeeventtypes, true) === false) {
throw new \moodle_exception('nopermissiontoupdatecalendar');
}
if (empty($eventtype) || !isset($allowedeeventtypes[$eventtype]) || $allowedeeventtypes[$eventtype] == false) {
return ['validationerror' => true];
}
$formoptions['eventtypes'] = $allowedeeventtypes;
if ($courseid) {
require_once($CFG->libdir . '/grouplib.php');
$groupcoursedata = groups_get_course_data($courseid);
+12
View File
@@ -3555,6 +3555,18 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
}
}
// Check if $data has events.
if (isset($data->events)) {
// Let's check and sanitize all "name" in $data->events before it's sent to front end.
foreach ($data->events as $d) {
$name = $d->name ?? null;
// Encode special characters if our decoded name does not match the original name.
if ($name && (html_entity_decode($name) !== $name)) {
$d->name = htmlspecialchars(html_entity_decode($name), ENT_QUOTES, 'utf-8');
}
}
}
return [$data, $template];
}
@@ -5,8 +5,8 @@ Feature: Allow to mark course as completed without cron for activity completion
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Completion course | CC1 | 0 |
| fullname | shortname | category | enablecompletion |
| Completion course | CC1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | First | student1@example.com |
@@ -25,11 +25,7 @@ Feature: Allow to mark course as completed without cron for activity completion
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| completionstatus | Course | CC1 | course-view-* | side-pre |
And I am on the "Completion course" "course editing" page logged in as admin
And I expand all fieldsets
And I set the field "Enable completion tracking" to "Yes"
And I click on "Save and display" "button"
And I am on the "Test assignment name" "assign activity editing" page
And I am on the "Test assignment name" "assign activity editing" page logged in as admin
And I follow "Expand all"
And I set the field "Completion tracking" to "Show activity as complete when conditions are met"
And I set the field "completionusegrade" to "1"
@@ -6,8 +6,8 @@ Feature: Show activity completion status or activity completion configuration on
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
@@ -18,12 +18,6 @@ Feature: Show activity completion status or activity completion configuration on
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | teacher |
| student1 | C1 | student |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Enable completion tracking | Yes |
And I press "Save and display"
And the following "activities" exist:
| activity | course | idnumber | name | intro | completion | completionview | completionexpected |
| assign | C1 | assign1 | Test assignment name | Test assignment description | 2 | 1 | 0 |
@@ -7,8 +7,8 @@ Feature: Allow students to manually mark an activity as complete
@javascript
Scenario: Mark an activity as completed
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
@@ -18,18 +18,11 @@ Feature: Allow students to manually mark an activity as complete
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | forum |
| course | C1 |
| name | Test forum name |
| activity | forum |
| course | C1 |
| name | Test forum name |
| completion | 1 |
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Enable completion tracking | Yes |
And I press "Save and display"
And I am on the "Test forum name" "forum activity editing" page
And I set the following fields to these values:
| completion | 1 |
And I press "Save and return to course"
And "Student First" user has not completed "Test forum name" activity
And I am on the "Course 1" course page logged in as student1
When I toggle the manual completion state of "Test forum name"
@@ -6,8 +6,8 @@ Feature: Restrict sections availability through completion or grade conditions
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
@@ -23,15 +23,11 @@ Feature: Restrict sections availability through completion or grade conditions
@javascript
Scenario: Show section greyed-out to student when completion condition is not satisfied
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Enable completion tracking | Yes |
And I press "Save and display"
And the following "activities" exist:
Given the following "activities" exist:
| activity | course | section | intro | completion | idnumber |
| label | C1 | 1 | Test label | 1 | 1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I edit the section "2"
And I expand all fieldsets
And I click on "Add restriction..." "button"
@@ -1,17 +1,14 @@
@core @core_contentbank @core_h5p @contenttype_h5p @_file_upload @_switch_iframe @javascript
@core @core_contentbank @core_h5p @contenttype_h5p @_switch_iframe @javascript
Feature: H5P file upload to content bank for admins
In order import new H5P content to content bank
As an admin
I need to be able to upload a new .h5p file to content bank
Background:
Given the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
Given the following "user private file" exists:
| user | admin |
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
And I log in as "admin"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I turn editing mode on
And the following config values are set as admin:
| unaddableblocks | | theme_boost|
@@ -19,13 +19,10 @@ Feature: H5P file upload to content bank for non admins
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | editingteacher |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
And the following "user private file" exists:
| user | teacher1 |
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
And I log in as "teacher1"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
Scenario: Teachers can not access system level content bank
Given I turn editing mode on
@@ -128,11 +125,10 @@ Feature: H5P file upload to content bank for non admins
And I should not see "filltheblanks.h5p"
Scenario: Teachers can not see existing contents when libraries are not installed
Given I log out
Given the following "user private file" exists:
| user | admin |
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
And I log in as "admin"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I navigate to "H5P > Manage H5P content types" in site administration
And I should not see "Fill in the Blanks"
When I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
+22 -36
View File
@@ -1,28 +1,14 @@
@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @javascript
@core @core_contentbank @core_h5p @contentbank_h5p @javascript
Feature: Delete H5P file from the content bank
In order remove H5P content from the content bank
As an admin
I need to be able to delete any H5P content from the content bank
Background:
Given the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
And I log in as "admin"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on site homepage
And I turn editing mode on
And the following config values are set as admin:
| unaddableblocks | | theme_boost|
And I add the "Navigation" block if not present
And I configure the "Navigation" block
And I set the following fields to these values:
| Page contexts | Display throughout the entire site |
And I press "Save changes"
And I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link" in the "Navigation" "block"
Given the following "user private file" exists:
| user | admin |
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
And I am on the "Content bank" page logged in as "admin"
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
@@ -32,14 +18,16 @@ Feature: Delete H5P file from the content bank
And I click on "Save changes" "button"
Scenario: Admins can delete content from the content bank
Given I click on "More" "button"
Given I wait "2" seconds
And I click on "More" "button"
And I should see "Delete"
And I click on "Delete" "link" in the ".cb-toolbar-container" "css_element"
And I click on "Delete" "link"
And I should see "Are you sure you want to delete the content 'content2delete.h5p'"
And I should not see "The content will only be deleted from the content bank"
And I click on "Cancel" "button" in the "Delete content" "dialogue"
Then I should see "content2delete.h5p"
And I click on "More" "button"
And I wait "2" seconds
And I click on "More" "button"
And I click on "Delete" "link" in the ".cb-toolbar-container" "css_element"
And I click on "Delete" "button" in the "Delete content" "dialogue"
And I wait until the page is ready
@@ -56,40 +44,38 @@ Feature: Delete H5P file from the content bank
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| manager | manager | System | |
And I log out
And I log in as "manager"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/find-the-words.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
When I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link" in the "Navigation" "block"
And the following "user private file" exists:
| user | manager |
| filepath | h5p/tests/fixtures/find-the-words.h5p |
When I am on the "Content bank" page logged in as "manager"
And I should see "content2delete.h5p"
And I follow "content2delete.h5p"
And I wait "2" seconds
And I click on "More" "button"
Then I should not see "Delete"
And I click on "Content bank" "link"
And I am on the "Content bank" page
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "find-the-words.h5p" "link"
And I click on "Select this file" "button"
And I click on "Save changes" "button"
And I click on "More" "button"
And I wait "2" seconds
And I click on "More" "button"
And I should see "Delete"
Scenario: The number of times a content is used is displayed before removing it
Given I follow "Dashboard"
And I follow "Manage private files..."
Given I am on the "My private files" page
And I click on "Add..." "button"
And I select "Content bank" repository in file picker
And I click on "content2delete.h5p" "file" in repository content area
And I click on "Link to the file" "radio"
And I click on "Select this file" "button"
And I click on "Save changes" "button"
When I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link" in the "Navigation" "block"
And I am on the "Content bank" page
And I follow "content2delete.h5p"
And I click on "More" "button"
And I wait "2" seconds
And I click on "More" "button"
And I click on "Delete" "link" in the ".cb-toolbar-container" "css_element"
Then I should see "Are you sure you want to delete the content 'content2delete.h5p'"
And I should see "The content will only be deleted from the content bank"
+7 -12
View File
@@ -5,10 +5,7 @@ Feature: Content bank use editor feature
I need to be able to access the edition options
Background:
Given the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
And I log in as "admin"
Given I log in as "admin"
And I am on site homepage
And I turn editing mode on
And the following config values are set as admin:
@@ -25,10 +22,9 @@ Feature: Content bank use editor feature
Then the "[data-action=Add-content]" "css_element" should be disabled
Scenario: Users can see the Add button if there is content type available for creation
Given I follow "Dashboard"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
Given the following "user private file" exists:
| user | admin |
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
And I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link" in the "Navigation" "block"
And I click on "Upload" "link"
@@ -44,10 +40,9 @@ Feature: Content bank use editor feature
And I should see "Fill in the Blanks"
Scenario: Users can edit content if they have the required permission
Given I follow "Dashboard"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
Given the following "user private file" exists:
| user | admin |
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
And I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link" in the "Navigation" "block"
And I click on "Upload" "link"
+7 -14
View File
@@ -1,30 +1,23 @@
@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @_switch_iframe @javascript
@core @core_contentbank @core_h5p @contentbank_h5p @_switch_iframe @javascript
Feature: Confirm content bank events are triggered
In order to log content bank actions
As an admin
I need to be able to check triggered events
Background:
Given the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
And I log in as "admin"
And I navigate to "H5P > Manage H5P content types" in site administration
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
And the following "courses" exist:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "contentbank content" exist:
| contextlevel | reference | contenttype | user | contentname | filepath |
| Course | C1 | contenttype_h5p | admin | Existing | /h5p/tests/fixtures/filltheblanks.h5p |
And I follow "Dashboard"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on "Course 1" course homepage with editing mode on
And the following "user private file" exists:
| user | admin |
| filepath | h5p/tests/fixtures/filltheblanks.h5p |
And the following config values are set as admin:
| unaddableblocks | | theme_boost|
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I add the "Navigation" block if not present
Scenario: Content created and uploaded events when uploading a content file
@@ -1,18 +1,15 @@
@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @javascript
@core @core_contentbank @core_h5p @contentbank_h5p @javascript
Feature: Store the content bank view preference
In order to consistantly view the content bank in icons or details view
As an admin
I need to be able to store my view preference
Background:
Given the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
Given the following "user private files" exist:
| user | filepath |
| admin | h5p/tests/fixtures/filltheblanks.h5p |
| admin | h5p/tests/fixtures/greeting-card.h5p |
And I log in as "admin"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I upload "h5p/tests/fixtures/greeting-card.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on site homepage
And I turn editing mode on
And the following config values are set as admin:
@@ -55,8 +52,7 @@ Feature: Store the content bank view preference
And I should not see "Last modified"
Scenario: Display the number of times a content is used in file details view
Given I follow "Dashboard"
And I follow "Manage private files..."
Given I am on the "My private files" page
And I click on "Add..." "button"
And I select "Content bank" repository in file picker
And I click on "fib.h5p" "file" in repository content area
+1 -1
View File
@@ -46,7 +46,7 @@ $filename = str_replace('/', '', str_replace(' ', '_', $courseinfo->shortname))
// If download confirmed, prepare and start the zipstream of the course download content.
if ($isdownload) {
confirm_sesskey();
require_sesskey();
$exportoptions = null;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/cm",["exports","core_courseformat/lo
* @class core_courseformat/local/courseindex/cm
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new Component({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");(window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element);try{const{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}catch(error){throw error}}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"&section=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new Component({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");(window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element),{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"&section=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default}));
//# sourceMappingURL=cm.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6 -9
View File
@@ -173,15 +173,12 @@ export default class Component extends BaseComponent {
const toggler = section.querySelector(this.selectors.COLLAPSE);
const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;
if (isChevron || isCollapsed) {
// Update the state.
const sectionId = section.getAttribute('data-id');
this.reactive.dispatch(
'sectionContentCollapsed',
[sectionId],
!isCollapsed
);
}
const sectionId = section.getAttribute('data-id');
this.reactive.dispatch(
'sectionContentCollapsed',
[sectionId],
!isCollapsed,
);
}
}
@@ -0,0 +1,549 @@
// 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/>.
/**
* The course file uploader.
*
* This module is used to upload files directly into the course.
*
* @module core_courseformat/local/courseeditor/fileuploader
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @typedef {Object} Handler
* @property {String} extension the handled extension or * for any
* @property {String} message the handler message
* @property {String} module the module name
*/
import Config from 'core/config';
import ModalFactory from 'core/modal_factory';
import ModalEvents from 'core/modal_events';
import Templates from 'core/templates';
import {getFirst} from 'core/normalise';
import {prefetchStrings} from 'core/prefetch';
import {get_string as getString, get_strings as getStrings} from 'core/str';
import {getCourseEditor} from 'core_courseformat/courseeditor';
import {processMonitor} from 'core/process_monitor';
import {debounce} from 'core/utils';
// Uploading url.
const UPLOADURL = Config.wwwroot + '/course/dndupload.php';
const DEBOUNCETIMER = 500;
const USERCANIGNOREFILESIZELIMITS = -1;
/** @var {ProcessQueue} uploadQueue the internal uploadQueue instance. */
let uploadQueue = null;
/** @var {Object} handlerManagers the courseId indexed loaded handler managers. */
let handlerManagers = {};
/** @var {Map} courseUpdates the pending course sections updates. */
let courseUpdates = new Map();
/** @var {Object} errors the error messages. */
let errors = null;
// Load global strings.
prefetchStrings('moodle', ['addresourceoractivity', 'upload']);
prefetchStrings('core_error', ['dndmaxbytes', 'dndread', 'dndupload', 'dndunkownfile']);
/**
* Class to upload a file into the course.
* @private
*/
class FileUploader {
/**
* Class constructor.
*
* @param {number} courseId the course id
* @param {number} sectionId the section id
* @param {number} sectionNum the section number
* @param {File} fileInfo the file information object
* @param {Handler} handler the file selected file handler
*/
constructor(courseId, sectionId, sectionNum, fileInfo, handler) {
this.courseId = courseId;
this.sectionId = sectionId;
this.sectionNum = sectionNum;
this.fileInfo = fileInfo;
this.handler = handler;
}
/**
* Execute the file upload and update the state in the given process.
*
* @param {LoadingProcess} process the process to store the upload result
*/
execute(process) {
const fileInfo = this.fileInfo;
const xhr = this._createXhrRequest(process);
const formData = this._createUploadFormData();
// Try reading the file to check it is not a folder, before sending it to the server.
const reader = new FileReader();
reader.onload = function() {
// File was read OK - send it to the server.
xhr.open("POST", UPLOADURL, true);
xhr.send(formData);
};
reader.onerror = function() {
// Unable to read the file (it is probably a folder) - display an error message.
process.setError(errors.dndread);
};
if (fileInfo.size > 0) {
// If this is a non-empty file, try reading the first few bytes.
// This will trigger reader.onerror() for folders and reader.onload() for ordinary, readable files.
reader.readAsText(fileInfo.slice(0, 5));
} else {
// If you call slice() on a 0-byte folder, before calling readAsText, then Firefox triggers reader.onload(),
// instead of reader.onerror().
// So, for 0-byte files, just call readAsText on the whole file (and it will trigger load/error functions as expected).
reader.readAsText(fileInfo);
}
}
/**
* Returns the bind version of execute function.
*
* This method is used to queue the process into a ProcessQueue instance.
*
* @returns {Function} the bind function to execute the process
*/
getExecutionFunction() {
return this.execute.bind(this);
}
/**
* Generate a upload XHR file request.
*
* @param {LoadingProcess} process the current process
* @return {XMLHttpRequest} the XHR request
*/
_createXhrRequest(process) {
const xhr = new XMLHttpRequest();
// Update the progress bar as the file is uploaded.
xhr.upload.addEventListener(
'progress',
(event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded * 100) / event.total);
process.setPercentage(percent);
}
},
false
);
// Wait for the AJAX call to complete.
xhr.onreadystatechange = () => {
if (xhr.readyState == 1) {
// Add a 1% just to indicate that it is uploading.
process.setPercentage(1);
}
// State 4 is DONE. Otherwise the connection is still ongoing.
if (xhr.readyState != 4) {
return;
}
if (xhr.status == 200) {
var result = JSON.parse(xhr.responseText);
if (result && result.error == 0) {
// All OK.
this._finishProcess(process);
} else {
process.setError(result.error);
}
} else {
process.setError(errors.dndupload);
}
};
return xhr;
}
/**
* Upload a file into the course.
*
* @return {FormData|null} the new form data object
*/
_createUploadFormData() {
const formData = new FormData();
try {
formData.append('repo_upload_file', this.fileInfo);
} catch (error) {
throw Error(error.dndread);
}
formData.append('sesskey', Config.sesskey);
formData.append('course', this.courseId);
formData.append('section', this.sectionNum);
formData.append('module', this.handler.module);
formData.append('type', 'Files');
return formData;
}
/**
* Finishes the current process.
* @param {LoadingProcess} process the process
*/
_finishProcess(process) {
addRefreshSection(this.courseId, this.sectionId);
process.setPercentage(100);
process.finish();
}
}
/**
* The file handler manager class.
*
* @private
*/
class HandlerManager {
/** @var {Object} lastHandlers the last handlers selected per each file extension. */
lastHandlers = {};
/** @var {Handler[]|null} allHandlers all the available handlers. */
allHandlers = null;
/**
* Class constructor.
*
* @param {Number} courseId
*/
constructor(courseId) {
this.courseId = courseId;
this.lastUploadId = 0;
this.courseEditor = getCourseEditor(courseId);
if (!this.courseEditor) {
throw Error('Unkown course editor');
}
this.maxbytes = this.courseEditor.get('course')?.maxbytes ?? 0;
}
/**
* Load the course file handlers.
*/
async loadHandlers() {
this.allHandlers = await this.courseEditor.getFileHandlersPromise();
}
/**
* Extract the file extension from a fileInfo.
*
* @param {File} fileInfo
* @returns {String} the file extension or an empty string.
*/
getFileExtension(fileInfo) {
let extension = '';
const dotpos = fileInfo.name.lastIndexOf('.');
if (dotpos != -1) {
extension = fileInfo.name.substring(dotpos + 1, fileInfo.name.length).toLowerCase();
}
return extension;
}
/**
* Check if the file is valid.
*
* @param {File} fileInfo the file info
*/
validateFile(fileInfo) {
if (this.maxbytes !== USERCANIGNOREFILESIZELIMITS && fileInfo.size > this.maxbytes) {
throw Error(errors.dndmaxbytes);
}
}
/**
* Get the file handlers of an specific file.
*
* @param {File} fileInfo the file indo
* @return {Array} Array of handlers
*/
filterHandlers(fileInfo) {
const extension = this.getFileExtension(fileInfo);
return this.allHandlers.filter(handler => handler.extension == '*' || handler.extension == extension);
}
/**
* Get the Handler to upload a specific file.
*
* It will ask the used if more than one handler is available.
*
* @param {File} fileInfo the file info
* @returns {Promise<Handler|null>} the selected handler or null if the user cancel
*/
async getFileHandler(fileInfo) {
const fileHandlers = this.filterHandlers(fileInfo);
if (fileHandlers.length == 0) {
throw Error(errors.dndunkownfile);
}
let fileHandler = null;
if (fileHandlers.length == 1) {
fileHandler = fileHandlers[0];
} else {
fileHandler = await this.askHandlerToUser(fileHandlers, fileInfo);
}
return fileHandler;
}
/**
* Ask the user to select a specific handler.
*
* @param {Handler[]} fileHandlers
* @param {File} fileInfo the file info
* @return {Promise<Handler>} the selected handler
*/
async askHandlerToUser(fileHandlers, fileInfo) {
const extension = this.getFileExtension(fileInfo);
// Build the modal parameters from the event data.
const modalParams = {
title: getString('addresourceoractivity', 'moodle'),
body: Templates.render(
'core_courseformat/fileuploader',
this.getModalData(
fileHandlers,
fileInfo,
this.lastHandlers[extension] ?? null
)
),
type: ModalFactory.types.SAVE_CANCEL,
saveButtonText: getString('upload', 'moodle'),
};
// Create the modal.
const modal = await this.modalBodyRenderedPromise(modalParams);
const selectedHandler = await this.modalUserAnswerPromise(modal, fileHandlers);
// Cancel action.
if (selectedHandler === null) {
return null;
}
// Save last selected handler.
this.lastHandlers[extension] = selectedHandler.module;
return selectedHandler;
}
/**
* Generated the modal template data.
*
* @param {Handler[]} fileHandlers
* @param {File} fileInfo the file info
* @param {String|null} defaultModule the default module if any
* @return {Object} the modal template data.
*/
getModalData(fileHandlers, fileInfo, defaultModule) {
const data = {
filename: fileInfo.name,
uploadid: ++this.lastUploadId,
handlers: [],
};
let hasDefault = false;
fileHandlers.forEach((handler, index) => {
const isDefault = (defaultModule == handler.module);
data.handlers.push({
...handler,
selected: isDefault,
labelid: `fileuploader_${data.uploadid}`,
value: index,
});
hasDefault = hasDefault || isDefault;
});
if (!hasDefault && data.handlers.length > 0) {
const lastHandler = data.handlers.pop();
lastHandler.selected = true;
data.handlers.push(lastHandler);
}
return data;
}
/**
* Get the user handler choice.
*
* Wait for the user answer in the modal and resolve with the selected index.
*
* @param {Modal} modal the modal instance
* @param {Handler[]} fileHandlers the availabvle file handlers
* @return {Promise} with the option selected by the user.
*/
modalUserAnswerPromise(modal, fileHandlers) {
const modalBody = getFirst(modal.getBody());
return new Promise((resolve, reject) => {
modal.getRoot().on(
ModalEvents.save,
event => {
// Get the selected option.
const index = modalBody.querySelector('input:checked').value;
event.preventDefault();
modal.destroy();
if (!fileHandlers[index]) {
reject('Invalid handler selected');
}
resolve(fileHandlers[index]);
}
);
modal.getRoot().on(
ModalEvents.cancel,
() => {
resolve(null);
}
);
});
}
/**
* Create a new modal and return a Promise to the body rendered.
*
* @param {Object} modalParams the modal params
* @returns {Promise} the modal body rendered promise
*/
modalBodyRenderedPromise(modalParams) {
return new Promise((resolve, reject) => {
ModalFactory.create(modalParams).then((modal) => {
modal.setRemoveOnClose(true);
// Handle body loading event.
modal.getRoot().on(ModalEvents.bodyRendered, () => {
resolve(modal);
});
// Configure some extra modal params.
if (modalParams.saveButtonText !== undefined) {
modal.setSaveButtonText(modalParams.saveButtonText);
}
modal.show();
return;
}).catch(() => {
reject(`Cannot load modal content`);
});
});
}
}
/**
* Add a section to refresh.
*
* @param {number} courseId the course id
* @param {number} sectionId the seciton id
*/
function addRefreshSection(courseId, sectionId) {
let refresh = courseUpdates.get(courseId);
if (!refresh) {
refresh = new Set();
}
refresh.add(sectionId);
courseUpdates.set(courseId, refresh);
refreshCourseEditors();
}
/**
* Debounced processing all pending course refreshes.
* @private
*/
const refreshCourseEditors = debounce(
() => {
const refreshes = courseUpdates;
courseUpdates = new Map();
refreshes.forEach((sectionIds, courseId) => {
const courseEditor = getCourseEditor(courseId);
if (!courseEditor) {
return;
}
courseEditor.dispatch('sectionState', [...sectionIds]);
});
},
DEBOUNCETIMER
);
/**
* Load and return the course handler manager instance.
*
* @param {Number} courseId the course Id to load
* @returns {Promise<HandlerManager>} promise of the the loaded handleManager
*/
async function loadCourseHandlerManager(courseId) {
if (handlerManagers[courseId] !== undefined) {
return handlerManagers[courseId];
}
const handlerManager = new HandlerManager(courseId);
await handlerManager.loadHandlers();
handlerManagers[courseId] = handlerManager;
return handlerManagers[courseId];
}
/**
* Load all the erros messages at once in the module "errors" variable.
* @param {Number} courseId the course id
*/
async function loadErrorStrings(courseId) {
if (errors !== null) {
return;
}
const courseEditor = getCourseEditor(courseId);
const maxbytestext = courseEditor.get('course')?.maxbytestext ?? '0';
errors = {};
const allStrings = [
{key: 'dndmaxbytes', component: 'core_error', param: {size: maxbytestext}},
{key: 'dndread', component: 'core_error'},
{key: 'dndupload', component: 'core_error'},
{key: 'dndunkownfile', component: 'core_error'},
];
window.console.log(allStrings);
const loadedStrings = await getStrings(allStrings);
allStrings.forEach(({key}, index) => {
errors[key] = loadedStrings[index];
});
}
/**
* Start a batch file uploading into the course.
*
* @private
* @param {number} courseId the course id.
* @param {number} sectionId the section id.
* @param {number} sectionNum the section number.
* @param {File} fileInfo the file information object
* @param {HandlerManager} handlerManager the course handler manager
*/
const queueFileUpload = async function(courseId, sectionId, sectionNum, fileInfo, handlerManager) {
let handler;
uploadQueue = await processMonitor.createProcessQueue();
try {
handlerManager.validateFile(fileInfo);
handler = await handlerManager.getFileHandler(fileInfo);
} catch (error) {
uploadQueue.addError(fileInfo.name, error.message);
return;
}
// If we don't have a handler means the user cancel the upload.
if (!handler) {
return;
}
const fileProcessor = new FileUploader(courseId, sectionId, sectionNum, fileInfo, handler);
uploadQueue.addPending(fileInfo.name, fileProcessor.getExecutionFunction());
};
/**
* Upload a file to the course.
*
* This method will show any necesary modal to handle the request.
*
* @param {number} courseId the course id
* @param {number} sectionId the section id
* @param {number} sectionNum the section number
* @param {Array} files and array of files
*/
export const uploadFilesToCourse = async function(courseId, sectionId, sectionNum, files) {
// Get the course handlers.
const handlerManager = await loadCourseHandlerManager(courseId);
await loadErrorStrings(courseId);
for (let index = 0; index < files.length; index++) {
const fileInfo = files[index];
await queueFileUpload(courseId, sectionId, sectionNum, fileInfo, handlerManager);
}
};
@@ -473,12 +473,16 @@ export default class {
* @param {boolean} collapsed the new collapsed value
*/
async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {
const collapsedIds = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);
if (!collapsedIds) {
const affectedSections = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);
if (!affectedSections) {
return;
}
const course = stateManager.get('course');
await this._callEditWebservice('section_index_collapsed', course.id, collapsedIds);
let actionName = 'section_index_collapsed';
if (!collapsed) {
actionName = 'section_index_expanded';
}
await this._callEditWebservice(actionName, course.id, affectedSections);
}
/**
@@ -489,12 +493,16 @@ export default class {
* @param {boolean} collapsed the new collapsed value
*/
async sectionContentCollapsed(stateManager, sectionIds, collapsed) {
const collapsedIds = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);
if (!collapsedIds) {
const affectedSections = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);
if (!affectedSections) {
return;
}
const course = stateManager.get('course');
await this._callEditWebservice('section_content_collapsed', course.id, collapsedIds);
let actionName = 'section_content_collapsed';
if (!collapsed) {
actionName = 'section_content_expanded';
}
await this._callEditWebservice(actionName, course.id, affectedSections);
}
/**
@@ -508,32 +516,22 @@ export default class {
*/
_updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {
stateManager.setReadOnly(false);
const affectedSections = new Set();
const affectedSections = [];
// Check if we need to update preferences.
sectionIds.forEach(sectionId => {
const section = stateManager.get('section', sectionId);
if (section === undefined) {
stateManager.setReadOnly(true);
return null;
}
const newValue = preferenceValue ?? section[preferenceName];
if (section[preferenceName] != newValue) {
section[preferenceName] = newValue;
affectedSections.add(section.id);
affectedSections.push(section.id);
}
});
stateManager.setReadOnly(true);
if (affectedSections.size == 0) {
return null;
}
// Get all collapsed section ids.
const collapsedSectionIds = [];
const state = stateManager.state;
state.section.forEach(section => {
if (section[preferenceName]) {
collapsedSectionIds.push(section.id);
}
});
return collapsedSectionIds;
return affectedSections;
}
/**
@@ -180,12 +180,8 @@ export default class Component extends DndCmItem {
const exporter = this.reactive.getExporter();
const data = exporter.cmCompletion(state, element);
try {
const {html, js} = await Templates.renderForPromise(completionTemplate, data);
Templates.replaceNode(completionElement, html, js);
} catch (error) {
throw error;
}
const {html, js} = await Templates.renderForPromise(completionTemplate, data);
Templates.replaceNode(completionElement, html, js);
}
/**
@@ -131,9 +131,16 @@ export default class Component extends BaseComponent {
const toggler = section.querySelector(this.selectors.COLLAPSE);
const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;
if (isChevron || isCollapsed) {
// Update the state.
const sectionId = section.getAttribute('data-id');
// Update the state.
const sectionId = section.getAttribute('data-id');
if ((!sectionlink)) {
this.reactive.dispatch(
'sectionIndexCollapsed',
[sectionId],
!isCollapsed
);
} else if (isCollapsed) {
// Always expand the section when clicking on section name.
this.reactive.dispatch(
'sectionIndexCollapsed',
[sectionId],
+60 -4
View File
@@ -617,8 +617,9 @@ abstract class base {
global $USER;
$course = $this->get_course();
try {
$sectionpreferences = (array) json_decode(
get_user_preferences("coursesectionspreferences_{$course->id}", '', $USER->id)
$sectionpreferences = json_decode(
get_user_preferences("coursesectionspreferences_{$course->id}", '', $USER->id),
true
);
if (empty($sectionpreferences)) {
$sectionpreferences = [];
@@ -637,10 +638,65 @@ abstract class base {
*
*/
public function set_sections_preference(string $preferencename, array $sectionids) {
global $USER;
$course = $this->get_course();
$sectionpreferences = $this->get_sections_preferences_by_preference();
$sectionpreferences[$preferencename] = $sectionids;
$this->persist_to_user_preference($sectionpreferences);
}
/**
* Add section preference ids.
*
* @param string $preferencename preference name
* @param array $sectionids affected section ids
*/
public function add_section_preference_ids(
string $preferencename,
array $sectionids
): void {
$sectionpreferences = $this->get_sections_preferences_by_preference();
if (!isset($sectionpreferences[$preferencename])) {
$sectionpreferences[$preferencename] = [];
}
foreach ($sectionids as $sectionid) {
if (!in_array($sectionid, $sectionpreferences[$preferencename])) {
$sectionpreferences[$preferencename][] = $sectionid;
}
}
$this->persist_to_user_preference($sectionpreferences);
}
/**
* Remove section preference ids.
*
* @param string $preferencename preference name
* @param array $sectionids affected section ids
*/
public function remove_section_preference_ids(
string $preferencename,
array $sectionids
): void {
$sectionpreferences = $this->get_sections_preferences_by_preference();
if (!isset($sectionpreferences[$preferencename])) {
$sectionpreferences[$preferencename] = [];
}
foreach ($sectionids as $sectionid) {
if (($key = array_search($sectionid, $sectionpreferences[$preferencename])) !== false) {
unset($sectionpreferences[$preferencename][$key]);
}
}
$this->persist_to_user_preference($sectionpreferences);
}
/**
* Persist the section preferences to the user preferences.
*
* @param array $sectionpreferences the section preferences
*/
private function persist_to_user_preference(
array $sectionpreferences
): void {
global $USER;
$course = $this->get_course();
set_user_preference('coursesectionspreferences_' . $course->id, json_encode($sectionpreferences), $USER->id);
// Invalidate section preferences cache.
$coursesectionscache = cache::make('core', 'coursesectionspreferences');

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