Compare commits

..

356 Commits

Author SHA1 Message Date
Huong Nguyen fe8d759b17 Moodle release 4.3.1 2023-12-08 09:21:43 +07:00
Sara Arjona f72244a8c2 weekly release 4.3+ 2023-12-07 16:22:04 +01:00
Paul Holden d09f737c7d MDL-79980 mod_survey: remove responses link if user cannot access.
Update Behat scenario to match.
2023-12-07 12:08:47 +01:00
Brendan Heywood b654282b18 MDL-80309 tasks: Clean up mtrace logs 2023-12-07 12:08:05 +08:00
Andrew Nicols bdb5a4ba40 Merge branch 'MDL-80257-403' of https://github.com/meirzamoodle/moodle into MOODLE_403_STABLE 2023-12-07 11:43:05 +08:00
Huong Nguyen 419d7f40c3 Merge branch 'MDL-80341_403' of https://github.com/timhunt/moodle into MOODLE_403_STABLE 2023-12-07 10:27:58 +07:00
Paul Holden 7be05aae41 MDL-79839 tool_task: clean task classname parameter prior to use. 2023-12-07 10:54:01 +08:00
Huong Nguyen 04261e8722 Merge branch 'MDL-79061-403-2' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-12-07 09:52:28 +07:00
Jun Pataleta a53121b74a MDL-79061 course: Create a sr-only mutations logger
* Create SRLogger that extends from the default Logger class. This
logger outputs the feedback to a sr-only ARIA live region to allow
screen readers to announce the feedback from mutations.
* Update the course editor to use the SRLogger.
2023-12-07 10:43:13 +08:00
Ferran Recio 6814c42260 MDL-79061 core: Mechanism for logging mutation feedbacks
* Create a mechanism for logging mutation feedbacks.
* The mutation feedbacks are displayed as a toast by default.
* Apply this logging mechanism on the course homepage to provide
feedback for the results of actions performed on course modules and
course sections.
2023-12-07 10:43:13 +08:00
Huong Nguyen 658953e34f Merge branch 'MDL-68652-403-1' of https://github.com/ilyatregubov/moodle into MOODLE_403_STABLE 2023-12-07 09:31:46 +07:00
Jun Pataleta c2c03f203f Merge branch 'MDL-80281_403_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_403_STABLE 2023-12-07 10:16:52 +08:00
Huong Nguyen 9a1a58e593 Merge branch 'MDL-77136-403' of https://github.com/lucaboesch/moodle into MOODLE_403_STABLE 2023-12-07 08:36:35 +07:00
Paul Holden 0f63155d58 MDL-79980 mod_survey: respect activity group mode getting report. 2023-12-07 09:32:11 +08:00
Ilya Tregubov 25e00b5e1f MDL-68652 core_grades: Only return allowed fields.
Grade iterator should check user permissions.
2023-12-07 09:12:39 +08:00
Ilya Tregubov b2bc3b9e7e Merge branch 'MOODLE_403_MDL-73378' of https://github.com/danielneis/moodle into MOODLE_403_STABLE 2023-12-07 09:01:58 +08:00
Jun Pataleta 38ed732727 Merge branch 'MDL-79761-403' of https://github.com/marinaglancy/moodle into MOODLE_403_STABLE 2023-12-07 08:54:00 +08:00
Paul Holden 463f16617e MDL-80239 grade: escape matched data in user search results. 2023-12-07 08:42:38 +08:00
Paul Holden 260924aeb8 MDL-80287 grade: escape current value in search templates. 2023-12-07 08:36:32 +08:00
Jun Pataleta b38413afe4 Merge branch 'MDL-80127_403' of https://github.com/timhunt/moodle into MOODLE_403_STABLE 2023-12-06 22:12:17 +08:00
Jun Pataleta 05ab53734b Merge branch 'MDL-80079-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-12-06 21:59:11 +08:00
Luca Bösch 708732ed72 MDL-77136 qtype_randomsamatch: Newest versions for shortanswer questions 2023-12-06 14:14:14 +01:00
Luca Bösch 121d6fb20a MDL-77136 core_question: Newest versions get_questions_from_categories 2023-12-06 14:14:14 +01:00
Tim Hunt e4acb387bb MDL-80127 question engine: prevent fatal errors from old bad data 2023-12-06 11:38:58 +00:00
Tim Hunt ff74bb2682 MDL-80127 question engine: don't convert null to '' before storing 2023-12-06 11:38:58 +00:00
Tim Hunt 17fb4afaab MDL-80341 quiz: unseen descriptions are not unanswered questonis
This was causing confusing in the 'are you sure you want to submit' confirmation pop-up.
2023-12-06 11:13:27 +00:00
Ilya Tregubov 60541d90e5 Merge branch 'MDL-76109-403' of https://github.com/meirzamoodle/moodle into MOODLE_403_STABLE 2023-12-06 15:42:55 +08:00
Huong Nguyen 4b26df011f Merge branch 'MDL-50650-403' of https://github.com/ilyatregubov/moodle into MOODLE_403_STABLE 2023-12-06 14:06:13 +07:00
Stephan Robotta fbecc7132d MDL-41465 reports: In separate group mode, limit to same 2023-12-06 13:52:58 +07:00
Ilya Tregubov bbea6e1d31 MDL-68652 mod_forum: Only include gradable users in grading 2023-12-06 12:45:09 +08:00
David Woloszyn 9e3ca2a866 MDL-79759 repository_url: Limit css imports and remove fragments
There are checks to urls that attempt to limit recurrsion when
parse_file is called. This is problematic for css import urls that
can call an indefinite amount of nested import urls. An import limit
has been introduced to address this. Fragments have also been removed.
2023-12-06 03:54:20 +01:00
Stevani Andolo 40ee4ab3fa MDL-80174 tool_log: ensure "other" data is safely unserialized. 2023-12-06 03:54:20 +01:00
Paul Holden d40fc8708a MDL-80268 badges: require capability to view awarded badges. 2023-12-06 03:54:20 +01:00
Paul Holden 2a60bc604f MDL-79797 backup: ensure block config is safely unserialized. 2023-12-06 03:54:20 +01:00
Ilya Tregubov e8237560d3 Merge branch 'MDL-79692-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-12-06 10:50:13 +08:00
Huong Nguyen 258783a754 Merge branch 'MDL-80180-403' of https://github.com/jbs1/moodle into MOODLE_403_STABLE 2023-12-06 09:16:28 +07:00
Ilya Tregubov 81de0469c1 Merge branch 'MDL-74248-MOODLE_403_STABLE-fix_mp3_mod_folder' of https://github.com/praxisdigital/moodle into MOODLE_403_STABLE 2023-12-06 10:01:00 +08:00
Sara Arjona 638cbfac93 Merge branch 'MDL-80286-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-12-05 16:42:14 +01:00
Sara Arjona 85c70539df Merge branch 'MDL-79862-403' of https://github.com/aanabit/moodle into MOODLE_403_STABLE 2023-12-05 16:35:25 +01:00
Sara Arjona 5b442418ad Merge branch 'MDL-79979-403' of https://github.com/aanabit/moodle into MOODLE_403_STABLE 2023-12-05 16:16:47 +01:00
Jun Pataleta 9c641bf38e Merge branch 'MDL-73974-403' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-12-05 23:09:37 +08:00
Karen Holland f9797aca63 MDL-73974 tool_brickfield: Processing alpha fontsizes 2023-12-05 23:07:57 +08:00
Sara Arjona d4b1ecbed4 Merge branch 'MDL-55640-m403' of https://github.com/sammarshallou/moodle into MOODLE_403_STABLE 2023-12-05 16:01:54 +01:00
Sara Arjona d140771110 Merge branch 'MDL-80015-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-12-05 15:51:28 +01:00
Sara Arjona 762df4050c Merge branch 'MDL-80254-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-12-05 15:45:38 +01:00
Jun Pataleta bee2d552b7 Merge branch 'MDL-79806_403_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_403_STABLE 2023-12-05 12:48:31 +08:00
Huong Nguyen f172248f20 Merge branch 'MDL-71336-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-12-05 11:19:41 +07:00
Jun Pataleta db71e79e05 Merge branch 'm43-mdl64431' of https://github.com/danmarsden/moodle into MOODLE_403_STABLE 2023-12-05 12:13:40 +08:00
Huong Nguyen 05c9ce4bd8 Merge branch 'MOODLE_403_STABLE' of https://github.com/dragos5436/moodle into MOODLE_403_STABLE 2023-12-05 10:40:29 +07:00
Ilya Tregubov ade9f7177a MDL-68652 core_grades: Refactor grades functions.
get_gradable_users now has extra param to retrieve only active
users. Grade reports hase their own grade_report::get_gradable_users
since they decided whether to inlcude active users from report preferences
2023-12-05 10:52:12 +08:00
Ilya Tregubov 05846ae405 MDL-68652 core_grades: Add a webservice to retrive only gradable users. 2023-12-05 10:52:12 +08:00
Ilya Tregubov e186ad572d Merge branch 'MDL-80043-403' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-12-05 09:11:08 +08:00
Ilya Tregubov 9f636a3de3 Merge branch 'MDL-80131-403' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-12-05 09:00:43 +08:00
Marina Glancy d79ad5de6a MDL-79761 navigation: fix default setting during web installation 2023-12-04 13:06:06 +00:00
Sara Arjona b7c8f7f723 Merge branch 'MDL-80185-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-12-04 11:00:44 +01:00
Paul Holden 46192ca77e MDL-80254 reportbuilder: better handling of deleted users in schedules.
We should verify that schedule and audience users are active accounts,
and that we don't try to send report schedules either from/to them if
they aren't.
2023-12-04 09:48:42 +00:00
meirzamoodle d68ea95f26 MDL-80257 editory_tiny: Implement ui_mode: split
Since version 6.4 and later, TinyMCE had a feature `ui_mode: split`.

It enables support for editors in scrollable containers,
and Moodle has some pages, especially the course pages, that are using the scrollable containers.
Therefore, all workaround that is addressed should be replaced by the feature to avoid future problems
regarding the overflow and the z-index issues.
2023-12-04 16:28:44 +07:00
meirzamoodle 85af2e6a7c MDL-80257 editory_tiny: Remove tox-tinymce-aux z-index
Remove .tox-tinymce-aux from style.css because z-index is already defined in TinyMCE skin.css
2023-12-04 16:28:44 +07:00
Jun Pataleta 136aef1fe6 Merge branch 'MDL-80221-403-enfix' of https://github.com/mudrd8mz/moodle into MOODLE_403_STABLE 2023-12-04 16:11:30 +08:00
Ilya Tregubov 79ea6a2d22 MDL-50650 core_grades: Validate minimum grade when importing grades
Also add behat test for max and min grade validation.
2023-12-04 16:03:21 +08:00
Kyle Matter e2f5104676 MDL-50650 core_grades: allow grade imports larger than 100 points 2023-12-04 12:53:21 +08:00
Jun Pataleta bc133f6fc3 Merge branch 'MDL-79726-403' of https://github.com/sarjona/moodle into MOODLE_403_STABLE 2023-12-04 12:09:17 +08:00
Huong Nguyen bea6119692 Merge branch 'MDL-79784-403' of https://github.com/jleyva/moodle into MOODLE_403_STABLE 2023-12-04 09:25:35 +07:00
Jun Pataleta b04fcb4527 MDL-80131 mod_data: Make page titles unique 2023-12-02 07:20:35 +08:00
dragos5436 b4278ca6b0 MDL-60632 mod_quiz: Fix chart rendering fail in quiz report
If a quiz activity has negative grading enabled for incorrect answers,
students can achieve overall negative grades in this quiz. If at least
one student achieves a negative grade, the chart at the bottom of the
quiz 'Results' tab will fail to display. This patch add a search for
negative results, removes them from the band below 0 and adds them to
the 0 band. This will make the chart render correctly again.

Co-authored-by: Susana Leitão <sleitao@uporto.pt>
Co-authored-by: Matthias Opitz <m.opitz@ucl.ac.uk>

Removed whitespace at end of lines
2023-12-01 15:28:24 +00:00
Sara Arjona d20c47f89c Merge branch 'MDL-80209-403' of https://github.com/aanabit/moodle into MOODLE_403_STABLE 2023-12-01 10:56:09 +01:00
Sara Arjona d29433ddc6 MDL-79726 completion: Overall should consider pass/fail states too
In MDL-78744, when the overallcompletion state was added to be displayed
in the activity card, only COMPLETE/INCOMPLETE status were considered.
However, depending on the activity completion settings, there are a
couple of states more that should also be included (COMPLETE_FAIL and
COMPLETE_PASS).

A new method has been added to the cm_completion_details class to
calculate whether a module should be considered or not completed,
based on their completion options and the current value for the
overall complete state.
2023-12-01 09:19:11 +01:00
David Mudrák 0a494aa9f6 MDL-80221 lang: Use fixed strings in the tests, too 2023-12-01 06:28:07 +01:00
Ilya Tregubov a418e9e53a weekly release 4.3+ 2023-12-01 13:12:23 +08:00
Jun Pataleta 3f10ec31b5 MDL-80043 grade: Add upgrade.txt notes 2023-12-01 09:10:53 +08:00
Jun Pataleta 488a653e50 MDL-80043 gradereport_grader: Define editingmode_title lang string 2023-12-01 09:10:53 +08:00
Jun Pataleta 7fa08a0b35 MDL-80043 grade: editingmode_title support for grade report plugins
* Add support for grade report plugins that define an
`editingmode_title` lang string. If the grade report plugin defines this
 language string, it will be used for the page title's unique
 identifying information when editing mode is turned on.
2023-12-01 09:10:53 +08:00
Jun Pataleta 6a89731462 MDL-80043 gradereport_singleview: Improve heading wordings
* Make the headings for the single view easier to read and understand
* When on editing mode, update the heading for the grade and user
screens to convey to the user that the page is currently on editing
mode. This is helpful for screen reader users as they can immediately
know the current display mode of the single view report page when this
gets read via the page title.
2023-12-01 09:10:53 +08:00
Jun Pataleta fd8b7fc730 MDL-80043 grade: Use heading parameter for the page title if provided 2023-12-01 09:10:52 +08:00
sam marshall 006eaeef08 MDL-55640 setuplib: test_localcachedir fails sporadically 2023-11-30 13:57:48 +00:00
Paul Holden 2862cce2a9 MDL-80286 datafield_date: cast field content to real when sorting.
This ensures we don't under/overflow the size of an integer in DB.
2023-11-30 10:05:01 +00:00
Amaia Anabitarte e3d76202cd MDL-80209 mod_scorm: Save minimum score completion 2023-11-30 10:40:17 +01:00
Mark Johnson 6441cca4d1 MDL-79806 quiz_statistics: Queue recalculation on attempt_deleted 2023-11-30 08:53:16 +00:00
Juan Leyva 04892e4707 MDL-79784 tool_mobile: Add missing external::format_text 2023-11-30 09:47:46 +01:00
Huong Nguyen 17282deeea Merge branch 'MDL-79816-403' of https://github.com/rmady/moodle into MOODLE_403_STABLE 2023-11-30 10:40:37 +07:00
Meirza c2e8975523 MDL-76109 core: Don't check password policy for guest user
Besides adding the is_guest() check in the check_password_policy() condition,
I also changed the temporary user ID because the temporary user and
the guest must not share the same ID. Otherwise, it will raise an error.

Co-authored-by: Andrei Bautu <abautu@gmail.com>
2023-11-30 10:35:38 +07:00
Huong Nguyen c5e3ce289e Merge branch 'MDL-74378_403' of https://github.com/PhilippImhof/moodle into MOODLE_403_STABLE 2023-11-30 10:11:45 +07:00
Ilya Tregubov 83e442cc69 Merge branch 'MDL-79890-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-11-30 10:45:01 +08:00
Ilya Tregubov b8f0f87b5f Merge branch 'MDL-78849-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-30 10:38:09 +08:00
Ilya Tregubov 539e5db429 Merge branch 'MDL-79325-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-30 09:34:46 +08:00
Sara Arjona 4c3523d987 Merge branch 'MDL-80205-403' of https://github.com/aanabit/moodle into MOODLE_403_STABLE 2023-11-29 18:42:09 +01:00
Jun Pataleta a0951e25b3 Merge branch 'MDL-79841-403' of https://github.com/laurentdavid/moodle into MOODLE_403_STABLE 2023-11-29 23:48:44 +08:00
Sara Arjona 70a95b8eae Merge branch 'MDL-79610-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-11-29 16:37:08 +01:00
Sara Arjona 5705496179 Merge branch 'MDL-80150-403' of https://github.com/lucaboesch/moodle into MOODLE_403_STABLE 2023-11-29 13:34:40 +01:00
Mikel Martín e77912a7c5 MDL-79610 courseformat: Fix availability restrictions rendering
When duplicating a course module, availability restrictions was not rendering
properly. That happened because 'editing' was not being exported for template
for a single course module. This patch adds 'editing' to the cm exported data.
2023-11-29 13:11:05 +01:00
Paul Holden fb13f6dd01 Merge branch 'MDL-79967-403-fix' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-11-29 12:03:05 +00:00
Jun Pataleta fbeef47a58 MDL-79967 mod_scorm: Use only the required field for the query 2023-11-29 18:32:07 +08:00
Sara Arjona 8fa5a3008d MDL-80150 resource: Reintroduce file icon
The new activity card design, planned for Moodle 4.3, reintroduces
the resource MIME icon that was removed in Moodle 4.0.
This is a backport of MDL-78285.
2023-11-29 09:58:14 +01:00
Jun Pataleta 2c94380acf Merge branch 'MDL-79148-403' of https://github.com/ssj365/moodle into MOODLE_403_STABLE 2023-11-29 15:20:32 +08:00
Jun Pataleta 8cd66034e4 Merge branch 'm43-mdl79967' of https://github.com/danmarsden/moodle into MOODLE_403_STABLE 2023-11-29 12:25:05 +08:00
Huong Nguyen 0bb3a2f20a Merge branch 'v_4_3_0' of https://github.com/dominicchinkh/moodle into MOODLE_403_STABLE 2023-11-29 10:33:21 +07:00
Huong Nguyen 96f06326f8 Merge branch 'm43-mdl80004' of https://github.com/danmarsden/moodle into MOODLE_403_STABLE 2023-11-29 09:52:51 +07:00
Huong Nguyen 8b6d43705e Merge branch 'MDL-80002-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-11-29 09:36:06 +07:00
Huong Nguyen eb0575859b Merge branch 'MDL-79267-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-29 09:17:38 +07:00
Huong Nguyen a6ce3c0758 Merge branch 'MDL-79512-403' of https://github.com/laurentdavid/moodle into MOODLE_403_STABLE 2023-11-29 09:08:38 +07:00
Huong Nguyen a9bfc2075b Merge branch 'MDL-76694-403' of https://github.com/snake/moodle into MOODLE_403_STABLE 2023-11-29 09:01:12 +07:00
Jun Pataleta 6a1644abff Merge branch 'MDL-80181-403' of https://github.com/srobotta/moodle into MOODLE_403_STABLE 2023-11-29 09:37:46 +08:00
Jeremy Schulz 1bbc0425e3 MDL-80180 admin: added readonly support for multicheckbox 2023-11-28 14:34:37 -05:00
Mark Johnson cb18237b5c MDL-80281 session: Don't report long session locks for readonly sessions
Previously, $CFG->sessionlockdebug was reporting long session locks for
all requests, even if they defined READ_ONLY_SESSION. This change
ensures that only sessions with locks are reported.
2023-11-28 16:21:17 +00:00
Shamiso.Jaravaza c898074fd8 MDL-79148 mod_bigbluebuttonbn: fix recording link 2023-11-27 23:54:56 -07:00
Ilya Tregubov 14b5d0694f Merge branch 'MDL-79603-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-11-28 13:53:20 +08:00
Laurent David 54d0894ec3 MDL-79841 theme_boost: Do not remove card borders
* When we are in small screensize we had removed the
left and right borders for cards. This is now restored
2023-11-28 06:37:37 +01:00
Jun Pataleta 0b326e9e72 Merge branch 'MDL-80210_403' of https://github.com/timhunt/moodle into MOODLE_403_STABLE 2023-11-28 10:35:14 +08:00
Amaia Anabitarte eae154a081 MDL-80205 mod_scorm: Remove all Pass grade options from completion 2023-11-27 18:04:30 +01:00
Helen Foster 42022a1d26 MDL-80221 lang: Import fixed English strings (en_fix) 2023-11-24 18:30:16 +01:00
Stephan Robotta c59c0f906b MDL-80181 wiki: fix width of left column in comments 2023-11-24 11:18:07 +01:00
Philipp Imhof e82f1d43c8 MDL-74378 mod_quiz: make sure timer is initialized 2023-11-24 08:52:05 +01:00
Huong Nguyen 6ff6327a6b weekly release 4.3+ 2023-11-24 12:23:15 +07:00
Huong Nguyen 4b34028221 Merge branch 'install_403_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_403_STABLE 2023-11-24 12:23:12 +07:00
Eloy Lafuente (stronk7) edbbe29706 Merge branch 'MDL-79966-m403' of https://github.com/sammarshallou/moodle into MOODLE_403_STABLE 2023-11-23 23:40:50 +01:00
sam marshall fc35bc5876 MDL-79966 core_task: NEVER_RUN_TIME did not fit in 10 digits 2023-11-23 16:23:39 +00:00
Rodrigo Mady 6c5f358ca2 MDL-79816 block_recentlyaccesseditems: Fix modname type in WS 2023-11-23 11:57:22 -03:00
Paul Holden 895189107f MDL-71336 forms: ensure grouped date elements have unique IDs.
Previously they were each taking the ID of their parent group element.
2023-11-23 12:01:28 +00:00
Tim Hunt d8c4a57ce8 MDL-80210 quiz: fix default for reviewmaxmarks when upgrading
It is correct that the database default should be 0, to match
other similar columns, but when sites are upgraded, we need
to set the settings for all existing quizzes to match the previous
behaviour.
2023-11-23 11:03:46 +00:00
Andrew Nicols 3d5c60ad26 Merge branch 'MDL-79966-m403' of https://github.com/sammarshallou/moodle into MOODLE_403_STABLE 2023-11-23 10:30:40 +08:00
Huong Nguyen a2cfa93011 Merge branch 'MDL-78496-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-11-23 09:23:00 +07:00
Ilya Tregubov e40f4e4d83 Merge branch 'MDL-80157_storedfileproblem-403' of https://github.com/leonstr/moodle into MOODLE_403_STABLE 2023-11-23 09:47:03 +08:00
Ilya Tregubov 1f0f185176 Merge branch 'MDL-79185-MOODLE_403_STABLE' of https://github.com/dmitriim/moodle into MOODLE_403_STABLE 2023-11-22 11:48:09 +08:00
Dmitrii Metelkin d5d2c59161 MDL-79185 cache: don't throw exception if cache data is corrupted 2023-11-22 12:49:18 +11:00
Ilya Tregubov c2c357dfb2 Merge branch 'MDL-78670-403' of https://github.com/rmady/moodle into MOODLE_403_STABLE 2023-11-22 09:10:54 +08:00
Paul Holden fccf80c3a3 MDL-80185 lang: use iso6391 language code for HTML lang attributes.
This resolves accessibility issues where Moodle language pack codes
didn't always map to correspondingly named iso6391 codes.

Where this value is defined in the language configuration, it will
now be used.
2023-11-21 22:55:21 +00:00
Mikel Martín ee26c16e90 MDL-80015 course: Fix category 'More' dropdown alignment
Co-authored-by: Stephen Sharpe <stephen.sharpe@synergy-learning.com>
2023-11-21 14:15:39 +01:00
Amaia Anabitarte d38211efc6 MDL-79979 tool_generator: Use auto-enrol admin in new courses setting 2023-11-21 08:41:57 +01:00
sam marshall dd844035ac MDL-79966 core_task: Scheduled task timing (crontab fields) is wrong 2023-11-20 17:12:27 +00:00
Amaia Anabitarte 778b8f51b1 MDL-79979 tool_generator: Use auto-enrol admin in new courses setting 2023-11-20 17:13:59 +01:00
Juan Olivares 4d28563eeb MDL-74248 mod_folder: Fix mp3 files being unaccessible in chrome
Platform users that host mp3 files within a folder are not able to access them using Chrome. This fix allows them to do so.
2023-11-20 11:30:28 +01:00
Dan Marsden 89f3fced22 MDL-79967 scorm: Fix scorm_insert_track grading calls. 2023-11-20 22:03:10 +13:00
Amaia Anabitarte 1ed0cb2562 MDL-79862 completion: Improve error handling for default completion 2023-11-20 09:52:22 +01:00
Huong Nguyen 7e1589b2ee Merge branch 'MDL-80037-403' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-11-20 14:48:38 +07:00
Huong Nguyen bc5d145dfb Merge branch 'MDL-79783-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-20 14:05:10 +07:00
Huong Nguyen 5aaba2850f Merge branch 'MDL-79604-403' of https://github.com/srobotta/moodle into MOODLE_403_STABLE 2023-11-20 14:01:22 +07:00
Laurent David 3116b46192 MDL-79512 mod_feedback: Add toast dialog when saved
* Add notification to notify the user once the changes have been
successfully saved
2023-11-20 06:55:14 +01:00
Laurent David 3a1aed3578 MDL-79512 mod_feedback: Fix issue with drag and drop
* A teacher can not change the order of the questions in feedback
as the order is not saved.
2023-11-20 06:55:14 +01:00
AMOS bot a2544fd5ca Automatically generated installer lang files 2023-11-20 00:10:44 +00:00
Rodrigo Mady ebab82c9f3 MDL-78670 mod_quiz: Add offline atempt rule for sequential navigation 2023-11-17 20:30:50 -03:00
Mark Johnson 40e56b3adc MDL-79806 mod_quiz: Implement new quiz_attempt_deleted callback 2023-11-17 16:06:58 +00:00
Leon Stringer 0a360e1eb2 MDL-80157 file: Exception message spelling 2023-11-17 14:35:38 +00:00
Paul Holden 59aa25852c MDL-79325 group: consistent name formatting in overview page/export. 2023-11-17 09:48:06 +00:00
Jun Pataleta ba60319c94 MDL-80037 theme: Indicate when $CFG->theme is defined in config.php
* And prevent selection of theme if $CFG->theme is defined in config.php
2023-11-17 09:24:32 +08:00
Dominic Chin ad836b6d4c MDL-80035 theme_boost: Fix fourth argument of single_button constructor 2023-11-17 09:17:27 +11:00
Sara Arjona 72d7395c96 weekly release 4.3+ 2023-11-16 16:22:34 +01:00
Sara Arjona d376741b93 Merge branch 'install_403_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_403_STABLE 2023-11-16 16:22:30 +01:00
Paul Holden ca991dbe29 MDL-78849 user: correct logical operator behaviour for guest button.
Apart from being banned per MDL-74990, it causes a subtle bug here
due to operator precedence.

Co-authored-by: Tom Mayfield <tmayfield@costrack.com>
2023-11-16 11:31:49 +00:00
Huong Nguyen 3a7594c71a Merge branch 'MDL-76457-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-11-16 10:58:12 +07:00
Huong Nguyen 9f88fb2125 Merge branch 'MDL-80066-403' of https://github.com/elza3ym/moodle into MOODLE_403_STABLE 2023-11-16 10:53:03 +07:00
Ilya Tregubov f29c4e36a4 Merge branch 'MDL-80121-403' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-11-16 10:01:03 +08:00
Ilya Tregubov a87db5a60d Merge branch 'MDL-75616-403' of https://github.com/davewoloszyn/moodle into MOODLE_403_STABLE 2023-11-16 09:23:06 +08:00
Mohamed Shehata 07bbe2fa5e MDL-80066 form: fix date picker on small screens 2023-11-15 23:44:09 +02:00
Sara Arjona 1c11297195 Merge branch 'MDL-79981-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-11-15 17:10:10 +01:00
Sara Arjona 5f59e2a61e Merge branch 'MDL-79962-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-15 16:47:39 +01:00
Sara Arjona 7e4119de7e Merge branch 'MDL-79697-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-11-15 16:15:49 +01:00
Andrew Nicols c30c1ca9cd MDL-79697 core: PSR-4 NS can be spread across multiple dirs
The PSR-4 specification does not preclude a single namespaces from
having multiple sources. This is the case for several PSR standards
including standard packagist packages distributed by PSR:

- PSR-7 - HTTP Message Interfaces: \Psr\Http\Message
- PSR-17 - HTTP Factories: \Psr\Http\Message
- PSR-15 - http-server-handler: \Psr\Http\Server
- PSR-15 - http-server-middleware: \Psr\Http\Server
2023-11-15 21:24:52 +08:00
Sara Arjona f50c83ac37 Merge branch 'MDL-80045-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-11-15 13:47:10 +01:00
Sara Arjona c54b573bd0 Merge branch 'MDL-79956-403' of https://github.com/ssj365/moodle into MOODLE_403_STABLE 2023-11-15 13:31:44 +01:00
Jun Pataleta 1f5e140ae9 MDL-80121 gradereport_singleview: Increase zero-state heading level
With the h2 heading not being rendered on the zero-state pages of the
Single view report, we need to increase the heading level of the
"Search for a user to view all their grades" and
"Select a grade item above" headings from h3 to h2.
2023-11-15 19:24:04 +08:00
Jun Pataleta f65d067dec MDL-80121 grade: Trim heading
In order to prevent rendering an empty heading <h2>, we need to make
sure that the $heading parameter passed to print_grade_page_head() is
not an empty string.
2023-11-15 19:24:04 +08:00
Stephan Robotta dbe914883c MDL-79604 mod_data: exception if mandatory checkox list is missing 2023-11-15 12:22:49 +01:00
Ilya Tregubov 783dc4cdb2 Merge branch 'MDL-79996-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-15 15:24:26 +08:00
Dan Marsden 26e91219e8 MDL-64431 assignfeedback_editpdf: Flatten PDF files 2023-11-15 19:28:21 +13:00
Huong Nguyen 15526541b3 Merge branch 'MDL-79648-403' of https://github.com/SergioComeron/moodle into MOODLE_403_STABLE 2023-11-15 09:13:35 +07:00
Huong Nguyen c78364eb73 Merge branch 'MDL-79601-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-11-15 09:09:25 +07:00
Angelia Dela Cruz 0c9bc880c1 MDL-79603 mod_scorm: Behat coverage for Scorm package display options 2023-11-15 09:41:38 +08:00
Andrew Nicols 93f282a345 MDL-78496 phpcs: Moodle 4.3.x supports up to php82 only
At this point, and per our PHP policy, Moodle 4.3 is not expected to
support PHP 8.3.
2023-11-15 09:23:31 +08:00
Andrew Nicols 7d4ba18a65 MDL-79890 core: Stop using get_class() without args
PHP 8.3 deprecates the use of `get_class()` without arguments. The
following alternatives should instead be used as appropriate:

- `get_class($this)` - The equivalent for instance methods
- `self::class` - The current class

Note that the behaviour of `get_class()` in a static method was
equivalent to `self::class` and _not_ `static::class`. That is to say
that the previous behaviour did not respect late static binding.
2023-11-15 09:14:08 +08:00
Andrew Nicols e78cbaa6c4 MDL-79890 core: ReflectionProperties::setValue() should take 2 args
Since PHP 8.3 setValue() must be called with null as the first arg if
called statically.
2023-11-15 09:13:08 +08:00
Mikel Martín 9b401dfc29 MDL-76457 tool_usertours: Fix tour popovers in RTL 2023-11-14 21:27:38 +01:00
Mikel Martín 3e179325e9 MDL-76457 theme_boost: Fix bootstrap popovers in RTL
- Fix the arrow placement for bootstrap popovers in RTL mode.
- Remove unneded css parser workaround for popover arrows.
  After MDL-67386 was integrated those changes were not needed anymore.
2023-11-14 21:27:38 +01:00
Sara Arjona 60727a7cdf MDL-77581 lesson: Fix behat random failures 2023-11-14 16:24:43 +01:00
Andrew Nicols 4d59c788ee MDL-78496 environment: Moodle 4.4 requirements
The only changes to requirements since Moodle 4.3 are increases to:

* the minimum PHP version required; and
* the minimum required Moodle version.
2023-11-14 22:47:55 +08:00
Andrew Nicols d5993fffd4 MDL-80079 core: Correct incorrect arg to format_text::$options 2023-11-14 22:28:55 +08:00
Andrew Nicols 136bd2c4c4 MDL-80079 core: Correct incorrect arg to format_string::$options 2023-11-14 22:28:55 +08:00
Mikel Martín b2d8615725 MDL-80045 block_starredcourses: Fix cards alignment in small screens 2023-11-14 14:36:23 +01:00
Sara Arjona 4e880779f9 Merge branch 'MDL-77581-403' of https://github.com/dpalou/moodle into MOODLE_403_STABLE 2023-11-14 11:52:51 +01:00
Dani Palou e5efe8b6d1 MDL-77581 lesson: Fix generator unit tests in oracle 2023-11-14 11:11:25 +01:00
SergioComeron da4a2d6191 MDL-79648 core_course: 29 October repeating day 2023-11-13 18:37:23 +01:00
Sara Arjona 84ec153634 Merge branch 'MDL-77581-403' of https://github.com/dpalou/moodle into MOODLE_403_STABLE 2023-11-13 17:19:41 +01:00
Paul Holden b69c42db5d MDL-79692 enrol_meta: check for group validity when adding members. 2023-11-13 10:23:14 +00:00
Huong Nguyen 55d5f7756d Merge branch 'MDL-74129-403' of https://github.com/sarjona/moodle into MOODLE_403_STABLE 2023-11-13 10:43:18 +07:00
David Saylor b262a67c89 MDL-75616 backup: Use standard temp directory for extraction 2023-11-13 11:08:17 +11:00
AMOS bot 05492ddbb3 Automatically generated installer lang files 2023-11-12 00:10:15 +00:00
Sara Arjona 185856927d MDL-74129 h5p: Preserve metadata information
The metadata information was not restored when an H5P file was uploaded
(it was saved when it was edited directly from the Content bank, but the
original information in the H5P file was lost).
This patch includes all the metadata fields and its values in the original
H5P file to preserve this information too.
2023-11-10 10:33:47 +01:00
Ilya Tregubov 935c2509f1 weekly release 4.3+ 2023-11-10 09:17:15 +08:00
Paul Holden a502fbcd1e MDL-79962 roles: consistent sorting of role entity name column.
The "name" field is typically omitted for default/pre-defined roles,
which should be accounted for when sorting.
2023-11-09 17:04:03 +00:00
Dani Palou 061186c215 MDL-77581 behat: Test create lesson pages using UI 2023-11-09 11:32:00 +01:00
Dani Palou eac49ffa4c MDL-77581 behat: Apply new lesson cluster generators to existing tests 2023-11-09 11:32:00 +01:00
Dani Palou db82329f6c MDL-77581 behat: Create generators for lesson clusters 2023-11-09 11:32:00 +01:00
Dani Palou 875ad48c9d MDL-77581 lesson: Fix wrong PHPDoc in some generator functions
While creating the new perform_create_page function I noticed that the PHPDoc return type of the existing functions was wrong
2023-11-09 11:01:51 +01:00
Dani Palou c627e1a4de MDL-77581 behat: Remove lesson_student_dashboard test file
After f7c476bac5 was integrated, these tests didn't assert anything specific and similar steps are already tested in lesson_navigation
2023-11-09 11:01:51 +01:00
Dani Palou 3b0165ec67 MDL-77581 behat: Apply new lesson generators to existing tests 2023-11-09 11:01:51 +01:00
Dani Palou 96bfaf67fd MDL-77581 behat: Create generators for lesson pages and answers 2023-11-09 11:01:51 +01:00
Dani Palou fc4354c7e3 MDL-77581 behat: Support finish_generate_ in generators 2023-11-09 11:01:51 +01:00
Shamiso.Jaravaza b5c49b8ac9 MDL-79956 mod_bigbluebuttonbn: fix import links 2023-11-08 21:48:03 -07:00
Michael Aherne 2721a7cdc3 MDL-80030 filelib: Fix info type. 2023-11-09 11:13:52 +08:00
Ilya Tregubov ab79a0e972 Merge branch 'MDL-79992-403' of https://github.com/sarjona/moodle into MOODLE_403_STABLE 2023-11-09 09:57:09 +08:00
Sara Arjona 275af6c114 Merge branch 'MDL-79827-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-08 18:10:44 +01:00
Sara Arjona dcc8300ef4 Merge branch 'MDL-79975-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-08 18:03:03 +01:00
Sara Arjona af62e9773c Merge branch 'MDL-79781-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-08 17:19:51 +01:00
Sara Arjona 0ac35f9248 Merge branch 'MDL-76555-403' of https://github.com/pmaneggia/moodle into MOODLE_403_STABLE 2023-11-08 17:06:18 +01:00
Mikel Martín a028969791 MDL-79601 report_completion: Fix RTL First/Last name alignment 2023-11-08 16:23:29 +01:00
Mikel Martín 7bc41babf8 MDL-79601 report_progress: Fix RTL First/Last name alignment 2023-11-08 16:23:29 +01:00
Andrew Nicols 4e08cbd42e MDL-79981 core: Add ability to manually run and filter phpunit on GHA 2023-11-08 12:53:43 +08:00
Ilya Tregubov d3427d45fa Merge branch 'MDL-79615_403_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_403_STABLE 2023-11-08 10:43:00 +08:00
Huong Nguyen 67a690d176 Merge branch 'MDL-79739-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-11-08 09:23:50 +07:00
Huong Nguyen 81d72254b5 Merge branch 'MDL-79840-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-08 09:23:09 +07:00
Ilya Tregubov b2e58aed7b Merge branch 'MDL-79858-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-08 09:49:29 +08:00
Paul Holden 5ca36b0731 MDL-79267 mod_forum: define report subplugin type language strings. 2023-11-07 09:49:56 +00:00
Huong Nguyen b105ec5188 Merge branch 'MDL-78953-403' of https://github.com/pedrojordao/moodle into MOODLE_403_STABLE 2023-11-07 15:33:52 +07:00
Jun Pataleta a85f7c200b MDL-80019 mod_bigbluebuttonbn: Use updated completion field names
Field names for the completion conditions have been updated since 4.3.
So the fields names should also be updated in the test.
2023-11-07 13:13:18 +08:00
Jun Pataleta f09f15e0be Merge branch 'MDL-80019-403' of https://github.com/call-learning/moodle into MOODLE_403_STABLE 2023-11-07 13:11:51 +08:00
Laurent David e35be12fb7 MDL-80019 mod_bigbluebuttonbn: Completion form freeze fix
* When the completion is locked, we should not be able to edit the completion elements
2023-11-06 22:11:02 +01:00
Sara Arjona 2f1a67685f Merge branch 'mdl-78650-403-stable' of https://github.com/Fragonite/moodle into MOODLE_403_STABLE 2023-11-06 09:51:00 +01:00
Huong Nguyen d3bc17fe56 Merge branch 'MDL-79062-403-2' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-11-06 10:52:04 +07:00
Dan Marsden adfd93a303 MDL-80004 scorm: add missing require locallib during course reset. 2023-11-06 12:05:12 +13:00
Paola Maneggia 82f9838f28 MDL-76555 mod_resource: Fix PHP8.0 type error (legacy resources) 2023-11-05 13:46:39 +01:00
Mikel Martín a18057fd06 MDL-80002 forumreport_summary: Fix summary report date filtering 2023-11-03 15:47:30 +01:00
Paul Holden dbc285e49e MDL-79996 communication: valid instance checks during user deletion. 2023-11-03 12:55:55 +00:00
Sara Arjona 31b409d920 MDL-79992 backup: Display course backups from activity restore
The Course backup area was not displaying the course backups when
accessing to the Restore page for an activity because the context
was not updated properly for this area.
2023-11-03 12:23:18 +01:00
Jake Dallimore 501c58729f MDL-76694 core_comment: exit the js init if the element cannot be found
In pages which result in the theme_boost/embedded template being used,
blocks may be rendered and the resulting HTML ignored, leaving any JS
they may have added to run without a corresponding DOM element to act
on. This fixes that case for comments, making sure we exit gracefully
during the init if the element can't be found.
2023-11-03 10:13:14 +08:00
pedrojordao 3cc283a25e MDL-78953 autocomplete: Add only one empty option when deselecting item.
Each option that was deselected was prepending an empty option in the select.
2023-11-02 18:47:59 -03:00
Paul Holden 188b5021fb MDL-79975 tool_task: correct class attribute table cell property. 2023-11-02 13:19:50 +00:00
Sara Arjona be7a698223 weekly release 4.3+ 2023-11-02 13:40:53 +01:00
Jun Pataleta 60a1c97289 MDL-79062 grade: Behat test fixes
* Feature: We can use a minimum grade different than zero
  - Use correct minimum input values.
  - Use data generators for faster test execution.
* Scenario: Disable category overriding
  - Use the field name to ensure that the correct field is being tested.
2023-11-02 17:34:29 +08:00
Jun Pataleta 92afaa484f MDL-79062 grades: Sort-by-name menu item accessibility fixes
* <h6> tags cannot be in table cells. Replace them with <div> tags.
* Group the ascending/descending sort menu items to better indicate
where they belong to.
2023-11-02 17:34:28 +08:00
Jun Pataleta 9fdb460330 MDL-79062 gradereport_grader: Set numeric type for grade value inputs
* Set grade inputs for grade items with type set to GRADE_VALUE to
number and add min and max input attributes, where necessary.
2023-11-02 17:34:28 +08:00
Jun Pataleta ccfc156b43 MDL-79062 gradereport_grader: Remove setting of tabindex >= 1
Tab indices that are greater than or equal to 1 break the natural
tab order of the document and may cause problems for keyboard users.
2023-11-02 17:34:27 +08:00
Huong Nguyen 10f10ee269 Merge branch 'MDL-78903-403' of https://github.com/meirzamoodle/moodle into MOODLE_403_STABLE 2023-11-02 10:18:20 +07:00
Huong Nguyen 47bf3467a2 Merge branch 'MDL-79801-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-11-02 10:14:46 +07:00
Huong Nguyen 04d7b13fc4 Merge branch 'MDL-79617_403_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_403_STABLE 2023-11-02 09:05:36 +07:00
Alexander Van der Bellen a3e7f7fd14 MDL-78650 mod_assign: Improve group submission action event handling
Don't return an event action when a group submission has already been submitted
by another student.
2023-11-01 20:38:30 +08:00
Alexander Van der Bellen 745b504426 MDL-78650 mod_assign: Add Behat test 2023-11-01 20:38:24 +08:00
Alexander Van der Bellen 89913c7eeb MDL-78650 mod_assign: Add unit test 2023-11-01 20:38:20 +08:00
Daniel Neis Araujo eb658e33c3 MDL-73378 gradebook: Category grade null when all subgrades are null 2023-10-31 13:58:16 -03:00
Sara Arjona 03e68d81b4 Merge branch 'MDL-79856-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-31 13:26:32 +01:00
Sara Arjona ae80f632bf Merge branch 'MDL-79849-403' of https://github.com/jleyva/moodle into MOODLE_403_STABLE 2023-10-31 13:08:20 +01:00
Jun Pataleta e2079313e2 Merge branch 'MDL-79853-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-10-31 16:46:06 +08:00
Huong Nguyen b4bade5ca9 Merge branch 'MDL-79807_403_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_403_STABLE 2023-10-31 12:49:48 +08:00
Huong Nguyen 10883ef268 Merge branch 'MDL-79460-403' of https://github.com/HuongNV13/moodle into MOODLE_403_STABLE 2023-10-31 12:49:48 +08:00
David Woloszyn 19e1e834a7 MDL-79460 communication_matrix: Re-enabled tests that use new sync route 2023-10-31 09:34:06 +07:00
Meirza f30b0eee9b MDL-78903 tiny_h5p: show repositories if editor filepicker type set 2023-10-31 08:00:28 +07:00
Meirza 0ad939f062 MDL-78903 tiny_media: show repositories if editor filepicker type set 2023-10-31 08:00:27 +07:00
Jun Pataleta e8f534c4f4 Merge branch 'MDL-77274-403' of https://github.com/laurentdavid/moodle into MOODLE_403_STABLE 2023-10-30 17:14:27 +08:00
Jun Pataleta 7e22a9c2c7 Merge branch 'MDL-79800-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-10-30 16:40:12 +08:00
Mikel Martín c542a77f11 MDL-79800 courseformat: Fix activity cards right elements margins
- Fix margins in elements displayed in the right side of activity cards.
Replace right margins with left margins to avoid extra space when editing
mode is off and action menu is not displayed.
- Fix groupmode icon spacing when displayed in small resolutions with edit
mode disabled
2023-10-30 07:52:38 +01:00
Laurent David db52a8bc31 MDL-77274 core_renderer: Respect full name format in letter avatars
* Make sure that we respect the fullnamedisplay and alternativefullnameformat
parameters to decide on the initials for a given user
* Add further tests

Co-authored-by: Tatsunori Uchino <tats.u@live.jp>
2023-10-30 06:16:09 +01:00
Huong Nguyen 4800afbc3e Merge branch 'MDL-79639_403_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_403_STABLE 2023-10-30 11:22:59 +07:00
Jun Pataleta 990b740ec1 Merge branch 'MDL-79194-403' of https://github.com/laurentdavid/moodle into MOODLE_403_STABLE 2023-10-27 23:33:20 +08:00
Mark Johnson b1d436a281 MDL-79615 course: Improve efficency of cache rebuilds
When bulk-updating course module visibility, set_coursemodule_visibily
was triggering a partial cache purge and rebuild for each course module.
This potentially led to 2 cache sets each requiring a lock to be
acquired and released per course module, plus any other cache updates
for other changes to the course in the same request.

This adds a new $rebuildcache paramter to the
set_coursemodule_visibilty, which is true by default to retain the
existing behaviour. If set false, it will skip doing the partial purge
and rebuild for that course module, and it is up to the calling code to
ensure the cache is updated as requried.

To assist with this, there is a new
course_modinfo::purge_course_module_cache_multiple() method, which
allows multiple course modules to be purged from the cache in a single
cache set.
2023-10-27 10:51:29 +01:00
Ilya Tregubov 8bf31f2277 Merge branch 'MDL-79693-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-27 16:45:37 +08:00
Jun Pataleta 97fc41ebcb Merge branch 'MDL-77364-403' of https://github.com/sarjona/moodle into MOODLE_403_STABLE 2023-10-27 16:34:00 +08:00
Sara Arjona bac3ffbe79 MDL-77364 myoverview: Display course name tooltip
Based on the feedback given by the UX team:
- The text has been increased to display more (50 vs 55).
- A tooltip has been added to display the full course name on hover,
following the accessibility guidelines.
2023-10-27 07:26:24 +02:00
Jun Pataleta dc7dd96189 weekly release 4.3+ 2023-10-27 09:43:04 +08:00
Jun Pataleta 67c567062e NOBUG: Fixed SVG browser compatibility 2023-10-27 09:43:03 +08:00
Jun Pataleta b7ee271c8b Merge branch 'MDL-79768-403-fix' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-10-26 23:05:58 +08:00
Ferran Recio 81cdd8db81 MDL-79194 core_courseformat: optimize section reload
* When reloading a section, we also forced the reloading of
course modules, resulting in doing the same work twice (once for
the section reload and once for the course module reload)
* Debounce the reloading process so prevent unwanted duplicate
reloads
2023-10-26 14:53:07 +02:00
Paul Holden 90d7b45f5f MDL-79858 block_myoverview: correctly export request course button. 2023-10-26 13:40:31 +01:00
Mark Johnson 632d4faeec MDL-79807 quiz_statistics: Clear stats cache when question added/removed 2023-10-26 13:38:31 +01:00
Paul Holden aac3b19bff MDL-79781 course: detect invalid creator role for pending courses. 2023-10-26 13:18:34 +01:00
Paul Holden a65a8fc40a MDL-79856 roles: use role localname when confirming deletion. 2023-10-26 12:00:54 +01:00
Angelia Dela Cruz c0bedfd833 MDL-79768 fix: Fixed random failing Behat due to time difference 2023-10-26 18:05:04 +08:00
Laurent David b72551c475 MDL-79194 core_course: Refresh modules menus with section changes
* Whenever a section is hidden, we need to refresh all contextual (hamburger)
menus of each course modules.
2023-10-26 11:04:40 +02:00
Andrew Nicols 437967f78e MDL-79853 core: Add phpunit options to manual runs 2023-10-26 14:48:51 +08:00
Jun Pataleta e05747e353 Merge branch 'MDL-79768-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-10-26 10:48:18 +08:00
Jun Pataleta 4865c40790 Merge branch 'MDL-42189-403' of https://github.com/davewoloszyn/moodle into MOODLE_403_STABLE 2023-10-26 10:32:13 +08:00
Paul Holden d63fd0365a Merge branch 'MDL-79762-43' of https://github.com/kabalin/moodle into MOODLE_403_STABLE 2023-10-25 23:55:54 +01:00
Juan Leyva f054958593 MDL-79849 tool_mobile: Add search disabled features 2023-10-25 17:32:26 +02:00
Ruslan Kabalin 7ee0ee6206 MDL-79762 core: Add possibility to define modal type for modalform
If `moduleName` param is passed in config object for modalform, it will be
used to instantiate modal class, otherwise 'core/modal_save_cancel' is
used.

Co-authored-by: Marina Glancy <marina@moodle.com>
2023-10-25 15:57:11 +01:00
Paul Holden cf715c550a MDL-79840 contentbank: fix references to invalid icon images. 2023-10-25 08:45:51 +01:00
Jun Pataleta 440a27c5de Merge branch 'MDL-75572-403' of https://github.com/sarjona/moodle into MOODLE_403_STABLE 2023-10-25 13:44:40 +08:00
Sara Arjona 2130e4ff8b MDL-75572 criteria: Only award badges to sucessfull completions
The UX team confirmed a badge shouldn't be awarded when the criteria
use a passing grade and the user gets a failing grade.
So the COMPLETION_COMPLETE_FAIL status won't be considered
completed for activities with completion that require a passing
grade.
2023-10-25 07:08:26 +02:00
Jun Pataleta 676626064c Merge branch 'MDL-79782-403' of https://github.com/roland04/moodle into MOODLE_403_STABLE 2023-10-24 22:47:47 +08:00
Jun Pataleta 655ac0b6a6 Merge branch 'MDL-79767-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-24 16:23:12 +08:00
Jun Pataleta 28233e5b3d Merge branch 'MDL-79637-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-10-24 10:48:55 +08:00
Paul Holden e001508f68 MDL-79827 tiny_link: show repositories only if editor filepicker set. 2023-10-23 22:01:01 +01:00
Mark Johnson 9b96653fc5 MDL-79617 repository_upload: Close session early in upload process 2023-10-23 15:32:15 +01:00
Angelia Dela Cruz 920ddcb70f MDL-79768 mod_data: Test for student can see upcoming data activity 2023-10-23 08:50:51 +08:00
David Woloszyn 82d1033f43 MDL-42189 gradereport_overview: Report hidden from suspended enrolments 2023-10-23 09:46:58 +11:00
Paul Holden 6856da7f09 MDL-79801 tag: fix report entity flagged column/filter query.
The "flag" field isn't a boolean state (1/0), it's a cumulative
count of how many times the tag has been flagged by users. This
should be accounted for to ensure that column aggregation and
filtering performs correctly.
2023-10-20 14:22:17 +01:00
Sara Arjona 150d134fa2 weekly release 4.3+ 2023-10-20 12:16:13 +02:00
Sara Arjona d30fa9918f Merge branch 'install_403_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_403_STABLE 2023-10-20 12:16:09 +02:00
Mikel Martín 343ad024c0 MDL-79782 pix: Replace angles- SVGs with Free versions 2023-10-20 08:50:10 +02:00
Huong Nguyen 4f8778cfc7 Merge branch 'MDL-78775-403' of https://github.com/ilyatregubov/moodle into MOODLE_403_STABLE 2023-10-20 09:29:47 +07:00
Ilya Tregubov b770c56b6d MDL-78775 gradereport_grade: Fix Behat.
Sort by lastname before doing any assertions.
2023-10-20 09:56:26 +08:00
Paul Holden 156953f115 MDL-79783 theme_boost: remove form-check padding inside table cells. 2023-10-19 14:13:46 +01:00
Mark Johnson 07a6698d01 MDL-79639 quiz_statistics: Prevent database deadlocks
This changes the cache purge code to select IDs for deletion
first, rather than using a subquery in a DELETE, since this
risks causing a deadlock in MySQL.
2023-10-19 08:57:34 +01:00
Huong Nguyen 1ec619db69 Merge branch 'MDL-79689-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-10-19 10:03:37 +07:00
Paul Holden 9f5f8d4a0a MDL-79767 communication: correct arguments when loading instance. 2023-10-18 22:38:50 +01:00
Sara Arjona 810f134cdc Merge branch 'MDL-79635-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-18 19:55:48 +02:00
Sara Arjona 7b1a0a0e65 Merge branch 'MDL-77651-403' of https://github.com/junpataleta/moodle into MOODLE_403_STABLE 2023-10-18 15:47:11 +02:00
Sara Arjona 447530591d Merge branch 'MDL-79747_403' of https://github.com/timhunt/moodle into MOODLE_403_STABLE 2023-10-18 10:28:57 +02:00
Huong Nguyen 34223e32ef Merge branch 'MDL-78775-403' of https://github.com/ilyatregubov/moodle into MOODLE_403_STABLE 2023-10-18 12:56:24 +07:00
Ilya Tregubov ac3cda51f0 MDL-78775 core_grades: Fix Behat 2023-10-18 12:53:53 +08:00
Angelia Dela Cruz f25ac24adf MDL-79689 mod_assign: Behat for enabling anonymous submissions 2023-10-18 10:46:30 +08:00
Huong Nguyen c3ba14a052 Merge branch 'MDL-79705-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-18 09:36:17 +07:00
Huong Nguyen bd844a184c Merge branch 'MDL-78979-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-18 09:23:31 +07:00
AMOS bot c968b1236f Automatically generated installer lang files 2023-10-18 00:11:10 +00:00
Jun Pataleta ff2d542295 MDL-77651 calendar: Hide left/right arrow characters
* Hide the arrow characters for the calendar templates so they won't be
read out by screen readers and avoid confusion for screen reader users.
2023-10-17 23:51:52 +08:00
Jun Pataleta bf68987951 MDL-77651 core: Hide left/right arrow characters for link_arrow_xx()
* The arrow characters in link_arrow_right() and link_arrow_left()
functions get announced by screen readers. This causes confusion
and is unnecessary.
2023-10-17 23:51:52 +08:00
Paul Holden e2049d770c MDL-79705 reportbuilder: remove checkbox field callback handling.
We no longer need special handling of this field type, because it
now returns language strings rather than HTML markup for a checkbox
element on display.
2023-10-17 16:51:29 +01:00
Paul Holden 021bdd0e4b MDL-79705 profilefield_checkbox: display data as simple Yes/No. 2023-10-17 16:51:29 +01:00
Tim Hunt 9bb59ad004 MDL-79747 quiz stats: task should run as admin the right way
The correct way to get an ad-hoc task to run as the main admin account
is to leave userid as null.

Setting it causes fatal errors if the main admin account is set to
auth: nologin.
2023-10-17 16:50:32 +01:00
Iñaki Arenaza fd1035ecf0 MDL-78481 mod_lesson: matching pagetype applying filters fix 2023-10-17 17:15:39 +02:00
Paul Holden 0602fdc0cb MDL-79693 output: remove embedded toast region from clipboard modal.
This was causing the toast notification upon the clipboard action
result to immediately disappear as the modal was closed. The toast
module can happily handle this for us itself, so remove from the
template.
2023-10-17 08:20:57 +01:00
Andrew Nicols 1294e309dc MDL-79739 core: Only run some tests in separate processes 2023-10-17 12:21:12 +08:00
Huong Nguyen 596bc2c2bf Merge branch 'MDL-78775-403' of https://github.com/ilyatregubov/moodle into MOODLE_403_STABLE 2023-10-17 10:47:02 +07:00
Ilya Tregubov 09568062fd Merge branch 'MDL-79691-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-10-17 10:28:27 +08:00
Huong Nguyen b205b758e8 Merge branch 'MDL-79367-403' of https://github.com/sarjona/moodle into MOODLE_403_STABLE 2023-10-17 09:17:46 +07:00
Paul Holden 55077e45bb MDL-78979 mod_data: correct restore decode rule for data view page. 2023-10-16 20:53:52 +01:00
Angelia Dela Cruz 9c4f0d31f2 MDL-79691 mod_h5pactivity: Behat coverage for H5P activity grades reset 2023-10-16 16:18:43 +08:00
Sara Arjona c220ad62dd Merge branch 'MDL-79566-403' of https://github.com/HuongNV13/moodle into MOODLE_403_STABLE 2023-10-16 09:53:48 +02:00
Ilya Tregubov ffc8c116fd Merge branch 'MDL-79365-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-16 14:07:55 +08:00
Sara Arjona 8679a15604 MDL-79367 availability: Fail grade does not mark activities as completed
The UX team confirmed that an activity shouldn't be considered completed
when the criteria use a passing grade and the user gets a failing grade.
So the COMPLETION_COMPLETE_FAIL status won't be considered
completed from now on.
2023-10-16 07:52:40 +02:00
Huong Nguyen 9995038273 Merge branch 'MDL-79698-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-10-16 11:39:53 +07:00
Huong Nguyen 70ee7b1beb Merge branch 'MDL-79653-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-10-16 10:54:38 +07:00
Huong Nguyen 5cc2ab26eb Merge branch 'MDL-79352-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-16 10:42:06 +07:00
Huong Nguyen cf0446155b MDL-79566 tiny_autosave: Add Javascript submission handler 2023-10-16 09:29:16 +07:00
Paul Holden 87c42699d9 MDL-79365 customfield_textarea: ensure field uniqueness is correct. 2023-10-13 14:30:34 +01:00
Andrew Nicols 97bcb22dba MDL-79693 core: Move toast regions to document on modal close 2023-10-13 14:22:48 +01:00
Andrew Nicols ecd82700a7 MDL-79698 core: Correct duplicate array key in test provider 2023-10-13 15:44:17 +08:00
Ilya Tregubov daa598b1b4 MDL-78775 core_grades: Fix saving on grader report page
When we have a pagination on grader page and search for user
that is not on first page, saving was throwing an error
since page didn't have enough data
2023-10-13 14:09:25 +08:00
Ilya Tregubov 13eb20d8d1 weekly release 4.3+ 2023-10-13 13:39:23 +08:00
Angelia Dela Cruz d0a9c57c0b MDL-79653 gradingform_guide: Behat coverage for deleting marking guide 2023-10-13 10:46:23 +08:00
Huong Nguyen 21b11cdb3d Merge branch 'MDL-79638-403' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-10-12 11:07:19 +07:00
Ilya Tregubov d4b2c50163 Merge branch 'MDL-79160-master' of https://github.com/danghieu1407/moodle into MOODLE_403_STABLE 2023-10-12 09:43:41 +08:00
Ilya Tregubov bd50ac1876 Merge branch 'MDL-79422-master' of https://github.com/andrewnicols/moodle into MOODLE_403_STABLE 2023-10-12 09:09:27 +08:00
Ilya Tregubov da4204c491 Merge branch 'MDL-79562-403' of https://github.com/lameze/moodle into MOODLE_403_STABLE 2023-10-12 09:06:32 +08:00
Simey Lameze 780b99229c MDL-79562 behat: make verification step more specific 2023-10-12 08:16:22 +08:00
Jun Pataleta bebde352fe Merge branch 'MDL-79611-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-11 18:15:46 +08:00
Jun Pataleta 1adf99597e Merge branch 'MDL-79633-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-11 13:41:12 +08:00
Huong Nguyen 70c1b02809 Merge branch 'MDL-78103-403' of https://github.com/HuongNV13/moodle into MOODLE_403_STABLE 2023-10-11 11:25:04 +07:00
chen levy 335c9c920d MDL-78103 theme: Fixed dropdown menu size by the content 2023-10-11 11:10:26 +07:00
Safat b1ad4fb830 MDL-79614 communication_matrix: Add missing call for sync flag 2023-10-11 11:04:44 +08:00
Jun Pataleta 2b5073a222 MDL-79626 upgrade: add the 4.3.0 separation line to all upgrade scripts 2023-10-11 09:40:13 +08:00
Andrew Nicols 731b150241 MDL-79637 phpunit: Do not reset DB during init of isolated tests
During the bootstrap of PHPUnit we ensure that the database has been
reset to its initial state.

We do this by checking the internally-stored DB write count between
runs. If the count is not yet set (null), or it has been increased, we
force a reset.

When running an isolated test the test runner resets the database, it
then sets up a new isolated test environment by writing a new PHPUnit
test case and passing it to a new PHP Process using standard in. As part
of this, the bootstrap is run for that process.

Because we are in a new process, the db write count is fresh and not yet
set. This has been leading to an additional db reset before the isolated
test.

To handle this we want to _not_ perform a reset during the
initialisation for isolated runs. We know that the DB is in a fresh
state before we start the run.

To support this we need to know whether the test is an isolated test
during the bootstrap, which means we cannot use the previous approach to
calculating this.

Instead we look at the PHP_SELF value. PHP sets this to "Standard input
code" when run from stdin, instead of running a file.

There should not be any other legitimate reason to run a PHPUnit
bootstrap via this stdin approach.

Unfortunately this approach is a little bit risky as it depends on the
presence of a specific string, however this string has been in place
since 2016, and there is no legitimate way of calculating this.

I did consider looking at whether the called script included `/vendor/`
and `/phpunit`, but this is also likely a risky approach if someone
calls PHPUnit in an unexpected way.

This approach is itself unit tested so any change to PHP's stdin string
before we deprecate this approach entirely in 12 months time will be
caught.
2023-10-11 09:29:26 +08:00
Paul Holden 228ee621e6 Merge branch 'MDL-79642-q_tags_off-403' of https://github.com/leonstr/moodle into MOODLE_403_STABLE 2023-10-10 21:18:55 +01:00
Andrew Nicols 16f65ba275 MDL-79638 phpunit: Add unit test for increment resets 2023-10-11 00:10:33 +08:00
Andrew Nicols 165918258d MDL-79638 phpunit: Remove mysql/mariadb hack
This hack was introduced to work around a bug in MySQL 5.6.14 and
MariaDB at the time.

https://bugs.mysql.com/bug.php?id=69882

It was addressed a few months later in 5.6.16, and 5.7.4.
MariaDB merged version 5.6.16 of MySQL's InnoDB engine in MariaDB
10.0.11 and got hte patch from there.

Moodle has required MySQL 8.0, and MariaDB 10.6.7 since Moodle 4.2 and
it is therefore safe to remove these hacks.
2023-10-10 23:35:45 +08:00
Sara Arjona 3392a198d7 Merge branch 'MDL-79623-MOODLE_403_STABLE' of https://github.com/PhMemmel/moodle into MOODLE_403_STABLE 2023-10-10 16:19:19 +02:00
Leon Stringer 7cba44007c MDL-79642 qbank_tagquestion: Fix Return type err
Previously if $CFG->usetags was not enabled the course question bank
page would show:

  Exception - qbank_tagquestion\plugin_feature::get_question_filters():
  Return value must be of type array, none returned

This is now fixed.
2023-10-10 12:13:56 +01:00
Paul Holden 0004f31e2e MDL-79635 group: ensure unique params used to determine visibility.
Or we'll trigger DML "Incorrect number of query parameters" exception.
2023-10-10 10:08:53 +01:00
Sara Arjona 83865c7979 Merge branch 'MDL-77816-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-10-10 09:21:27 +02:00
Ilya Tregubov b43222dc67 Merge branch 'MDL-79501-master' of https://github.com/geichelberger/moodle into MOODLE_403_STABLE 2023-10-10 15:06:11 +08:00
Ilya Tregubov e857efcf28 Merge branch 'MDL-79279' of https://github.com/timhunt/moodle into MOODLE_403_STABLE 2023-10-10 11:20:58 +08:00
Ilya Tregubov e7ab69fa26 Merge branch 'MDL-71414-403' of https://github.com/laurentdavid/moodle into MOODLE_403_STABLE 2023-10-10 10:19:00 +08:00
Paul Holden f4743bbb8d MDL-79633 mod_data: correct timemodified tag replacement attribute. 2023-10-09 19:41:02 +01:00
Paul Holden d1b63c5e18 MDL-79352 admin: fix links to tool plugin management page. 2023-10-09 15:11:52 +01:00
Paul Holden cf5065ed71 MDL-79611 group: early exit getting members of non-existing group. 2023-10-09 15:00:33 +01:00
Paul Holden bf380e2d46 MDL-77816 mod_bigbluebuttonbn: feedback when validating completion. 2023-10-09 14:58:49 +01:00
Yannick Schillinger 4b40f8273d MDL-77816 mod_bigbluebuttonbn: Fix completionValidate argument format 2023-10-09 14:58:38 +01:00
Laurent David c0f2ff773f MDL-71414 mod_h5pactivity: Add a new "None" message
* In matching types of questions, sometimes the user does not answer all
questions. We now display "None" in the result report. The answer can
be either correct or incorrect.
* Fix "unknown" answer constant name.
2023-10-09 11:57:34 +02:00
Laurent David 2ef18bce0d MDL-71414 mod_h5pactivity: Fix issue with drop down report
* Drag and Drop report were missing answers when target could be dropped on multiple
drop zones.
2023-10-09 11:57:34 +02:00
Philipp Memmel 536780ce38 MDL-79623 tool_componentlibrary: Fix correct modal content attribute 2023-10-09 09:05:41 +00:00
19682 changed files with 706231 additions and 882007 deletions
+1 -3
View File
@@ -1,5 +1,3 @@
**/yui/build/** -diff
**/amd/build/** -diff
**.js.map -diff
**-min.js -diff
**.min.js -diff
lib/dml/oci_native_moodle_package.sql text eol=lf
+1 -1
View File
@@ -1,2 +1,2 @@
# Primary donations pages.
custom: ["https://moodle.com/donations/", "https://fund.moodle.org"]
custom: ["https://moodle.com/donations/", moodle.org]
-7
View File
@@ -1,7 +0,0 @@
*** PLEASE DO NOT OPEN PULL REQUESTS VIA GITHUB ***
The moodle.git repository at Github is just a mirror of the official repository. We do not accept pull requests at Github.
See CONTRIBUTING.md guidelines for how to contribute patches for Moodle. Thank you.
--
-6
View File
@@ -1,6 +0,0 @@
> [!IMPORTANT]
> PLEASE DO NOT PUBLISH SECURITY FINDINGS PUBLICLY.
If you discover a potential security issue, please report it via our [Security Submission Form](https://moodle.org/security/report).
For more information about our security processes and responsible disclosure policy, see the [Security Procedures documentation](https://moodledev.io/general/development/process/security).
-1
View File
@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1231.24 314.44" preserveAspectRatio="xMinYMid meet"><defs><style>.cls-1{fill:#f48020;}.cls-2,.cls-3{fill:#383738;stroke:#4a4a4c;}.cls-2{stroke-width:2.37px;}.cls-3{stroke-width:2.38px;}.cls-4{fill:#585858;}</style></defs><title>Moodle_Logo_UseonBlack_RGB</title><path class="cls-1" d="M1086.3,235.34a53.42,53.42,0,0,0,16.71,29q13.26,12.27,29.48,12.27,11.29,0,24.56-5.89,16.23-6.88,16.22-16.22l58-.49q-12.29,29-41.76,45.2-27.52,15.23-60.43,15.24-42.25,0-70-26.29t-27.75-68.54q0-43.73,27.75-69.51t72-25.8q48.15,0,75.17,31.94,25.07,29.47,25.06,79.1Zm89.91-33.91q-3.43-17.69-14.73-29-12.8-12.28-29.49-12.29-17.21,0-28.74,11.79t-15.47,29.48Z"/><path class="cls-1" d="M965.45,308.54V0h55V308.54Z"/><path class="cls-1" d="M892.69,308.54V290.36q-7.38,9.83-25.06,15.72a92.31,92.31,0,0,1-30,5.41q-39.31,0-63.13-27T750.7,217.65q0-39.3,23.34-65.84t62.15-26.53q43.71,0,56.5,23.58V0h53.55V308.54Zm0-102.68q0-14.75-14-28.26t-28.74-13.51q-21.12,0-33.41,17.2-10.8,15.24-10.8,37.33,0,21.63,10.8,36.85,12.29,17.71,33.41,17.69,12.78,0,27.76-12t15-24.81Z"/><path class="cls-1" d="M647,313.45q-41.77,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.75,0,69.27,26.53t27.52,68.28q0,41.79-27.52,68.3T647,313.45ZM647,168q-19.89,0-30.58,15.07t-10.68,35.79q0,20.73,9.94,35.05,11.43,16.31,31.32,16.28t31.32-16.28q10.44-14.31,10.44-35.05t-9.94-35Q667.42,168,647,168Z"/><path class="cls-1" d="M448.07,313.45q-41.76,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.74,0,69.28,26.53t27.5,68.28q0,41.79-27.5,68.3T448.07,313.45Zm0-145.43q-19.89,0-30.59,15.07t-10.69,35.79q0,20.73,9.94,35.05,11.43,16.31,31.34,16.28t31.32-16.28q10.42-14.31,10.44-35.05t-9.94-35Q468.45,168,448.07,168Z"/><path class="cls-1" d="M288.89,309.11V201q0-33.9-28-33.9t-28,33.9V309.11h-55V201q0-33.9-27.52-33.9-28,0-28,33.9V309.11h-55V194.65q0-35.37,24.56-53.54,21.61-16.22,58.46-16.22,37.35,0,55,19.16,15.23-19.16,55.51-19.16,36.86,0,58.45,16.22,24.57,18.16,24.57,53.54V309.11Z"/><path class="cls-2" d="M.76,145.76"/><path class="cls-3" d="M1.73,145.32"/><path class="cls-4" d="M173.86,116.85l55.08-40.1-.7-2.44C135,83.92,80.42,96.44,0,144.13l.73,2.38,21-.67c15.17.52,30.85,1.26,46,1.32-1,24.09.83,32.65.83,32.65,51.92,17.58,101.22,1.53,127.14-43.9A248.72,248.72,0,0,0,173.86,116.85Z"/><path class="cls-4" d="M113.78,110c-.3-1.08-2.23-1.48-4.28-.9a4.73,4.73,0,0,0-3.14,2.19c-18.53,5-49.16,13.72-70.73,21l-1.33.45c-16.68,5.61-24.73,8.49-25.22,12.55-2.29,18.44-.87,42.18-.86,42.48l.19,6.4c-9.74,27.56.06,49.6,8.38,69.77.06-24.92,1.07-45.22-6-71-.2-3.82-1.49-30.36.61-47.34.34-2.75,11.65-6.56,23.63-10.59l1.33-.44c22-7.42,53.57-16.39,71.9-21.29a6.75,6.75,0,0,0,2.29-.25C112.64,112.43,114.08,111.09,113.78,110Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

-31
View File
@@ -1,31 +0,0 @@
name: 'Close Pull Requests'
on:
pull_request_target:
types: opened
workflow_dispatch:
jobs:
close:
# Avoid forks to run this job
if: github.repository_owner == 'moodle'
name: Close Pull Requests
runs-on: ubuntu-latest
steps:
- uses: dessant/repo-lockdown@v4
with:
process-only: prs
close-pr: true
skip-closed-pr-comment: true
pr-comment: >
**Please, don't open pull requests via GitHub.**
This repository (moodle.git) at GitHub is just a mirror of the official Moodle repository.
For how to contribute patches for Moodle see
[CONTRIBUTING.md](https://github.com/moodle/moodle/blob/main/CONTRIBUTING.md).
Closing this pull request.
+1 -1
View File
@@ -45,7 +45,7 @@ $CFG->admin = 'admin';
$CFG->directorypermissions = 0777;
// Debug options - possible to be controlled by flag in future.
$CFG->debug = (E_ALL); // DEBUG_DEVELOPER.
$CFG->debug = (E_ALL | E_STRICT); // DEBUG_DEVELOPER.
$CFG->debugdisplay = 1;
$CFG->debugstringids = 1; // Add strings=1 to url to get string ids.
$CFG->perfdebug = 15;
-143
View File
@@ -1,143 +0,0 @@
name: One by One Testing
# Run all the individual unit tests one by one, with
# fully independent PHPUnit executions. Useful to
# detect issues with some tests that are using stuff
# that has been made available by others, but is not
# available when running individually.
#
# Note that we aren't using PHPUnit's own isolation
# here but completely separated runs, one for each
# test.
#
# The workflow will fail reporting all the tests
# that have failed (and will pass if no failure is
# detected, of course).
#
# It's only executed via workflow dispatch (automated
# or manual), not by push/tag. And acceptd configuration
# of phpunit, specially useful to run it with PHPUnit's
# own isolation or any other option.
on:
workflow_dispatch:
inputs:
phpunit_extra_options:
description: Additional options to apply to PHPUnit
required: false
default: ''
env:
chunks: 7
jobs:
collect:
name: Collect individual unit tests
runs-on: ubuntu-latest
outputs:
matrix: ${{steps.individual-tests.outputs.matrix }}
steps:
- name: Checking out code
uses: actions/checkout@v4
- name: Looking for all individual tests
id: individual-tests
run: |
count=0 # Number of individual tests found.
while read -r testfile; do # For each test file.
while read -r testname; do # For each unit test in a file.
count=$((count + 1))
# Sent it to the correct chunk file.
chunk=$(((($count % $chunks)) + 1))
echo "$testname $testfile" >> ./chunk_$chunk.txt
done < <(grep "function test_" "${testfile}" | sed -r "s/^.*function (test_[a-zA-Z0-9_]+).*/\1/")
done < <(find . -name "*_test.php")
# Generate the matrix to run tests.
echo "matrix=$(ls -1 chunk_*.txt | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
echo "$count individual tests collected in $chunks files"
- name: Upload individual tests files
uses: actions/upload-artifact@v4
with:
name: individual_tests
path: chunk_*.txt
retention-days: 1
test:
name: Run tests
needs: collect
runs-on: ubuntu-latest
services:
exttests:
image: moodlehq/moodle-exttests
ports:
- 8080:80
redis:
image: redis
ports:
- 6379:6379
strategy:
fail-fast: false
matrix:
file: ${{ fromJson(needs.collect.outputs.matrix) }}
steps:
- name: Setting up DB pgsql
uses: m4nu56/postgresql-action@v1
with:
postgresql version: 14
postgresql db: test
postgresql user: test
postgresql password: test
- name: Setting up PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
ini-values: max_input_vars=5000
coverage: none
- name: Checking out code
uses: actions/checkout@v4
- name: Download individual test files
uses: actions/download-artifact@v4
with:
name: individual_tests # Make all the chunk files available for the next steps.
- name: Setting up PHPUnit
env:
dbtype: pgsql
run: |
echo "pathtophp=$(which php)" >> $GITHUB_ENV
cp .github/workflows/config-template.php config.php
mkdir ../moodledata
sudo locale-gen en_AU.UTF-8
php admin/tool/phpunit/cli/init.php --no-composer-self-update
- name: Run PHPUnit test (one by one)
env:
dbtype: pgsql
run: |
status=0
count=0
while read -r line; do # For each line in the chunk file
count=$((count + 1))
filter="${line% *}"
file="${line#* }"
# Run the individual unit test and report problems if needed to.
if ! php vendor/bin/phpunit \
--fail-on-empty-test-suite \
--fail-on-warning \
--fail-on-risky \
--filter "$filter" ${{ inputs.phpunit_extra_options }} \
"$file" >/dev/null 2>&1; then
if [ $status -eq 0 ]; then
echo "Problems found, list of PHPUnit commands failing:"
fi
echo "vendor/bin/phpunit --filter '${filter}' ${{ inputs.phpunit_extra_options }} $file"
status=$((status + 1))
fi
done < ${{ matrix.file }}
echo "Finished: $count individual tests executed, $status tests failed"
exit $status
+12 -15
View File
@@ -3,7 +3,7 @@ name: Core
on:
push:
branches-ignore:
- main
- master
- MOODLE_[0-9]+_STABLE
tags-ignore:
- v[0-9]+.[0-9]+.[0-9]+*
@@ -15,18 +15,18 @@ on:
default: ''
env:
php: 8.4
php: 8.2
jobs:
Grunt:
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- name: Checking out code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Configuring node & npm
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
@@ -61,13 +61,13 @@ jobs:
matrix:
include:
# MySQL builds always run with the lowest PHP supported version.
- os: ubuntu-24.04
php: 8.2
- os: ubuntu-22.04
php: 8.0
extensions:
db: mysqli
# PostgreSQL builds always run with the highest PHP supported version.
- os: ubuntu-24.04
php: 8.4
- os: ubuntu-22.04
php: 8.2
db: pgsql
steps:
@@ -76,7 +76,7 @@ jobs:
uses: moodlehq/mysql-action@v1
with:
collation server: utf8mb4_bin
mysql version: 8.4
mysql version: 8.0
mysql database: test
mysql user: test
mysql password: test
@@ -88,7 +88,7 @@ jobs:
if: ${{ matrix.db == 'pgsql' }}
uses: m4nu56/postgresql-action@v1
with:
postgresql version: 14
postgresql version: 13
postgresql db: test
postgresql user: test
postgresql password: test
@@ -105,7 +105,7 @@ jobs:
coverage: none
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setting up PHPUnit
env:
@@ -122,6 +122,3 @@ jobs:
dbtype: ${{ matrix.db }}
phpunit_options: ${{ secrets.phpunit_options }}
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
- name: Git status
run: git status
-23
View File
@@ -1,23 +0,0 @@
name: Web Installer Testing
on:
workflow_dispatch:
jobs:
InstallationTest:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.2, 8.4]
steps:
- name: Construct repository URL
run: echo "REPOSITORY_URL=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" >> $GITHUB_ENV
shell: bash
- uses: moodlehq/moodle-webinstaller-test@main
with:
repository: ${{ env.REPOSITORY_URL }}
branch: ${{ github.ref_name }}
php: ${{ matrix.php }}
+9 -9
View File
@@ -8,7 +8,7 @@ on:
required: false
default: ''
env:
php: 8.4
php: 8.2
jobs:
Grunt:
@@ -21,10 +21,10 @@ jobs:
git config --global core.eol lf
- name: Checking out code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Configuring node & npm
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
@@ -51,7 +51,7 @@ jobs:
matrix:
include:
- os: windows-latest
php: 8.4
php: 8.2
# Ideally we should use mysql/mariadb, but they are 4x slower without tweaks and configuration
# so let's run only postgres (1.5h vs 6h) only, If some day we want to improve the mysql runs,
# this is the place to enable them.
@@ -59,16 +59,16 @@ jobs:
db: pgsql
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
- os: windows-latest
php: 8.2
php: 8.0
db: pgsql
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium
steps:
- name: Setting up DB mysql
if: ${{ matrix.db == 'mysqli' }}
uses: shogo82148/actions-setup-mysql@v1
with:
mysql-version: 8.4
mysql-version: 8.0
user: test
password: test
@@ -117,7 +117,7 @@ jobs:
git config --global core.eol lf
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
uses: actions/checkout@v4
uses: actions/checkout@v3
# Needs to be done after php is available, git configured and Moodle checkout has happened.
- name: Setting up moodle-exttests service
@@ -128,7 +128,7 @@ jobs:
- name: Setting up redis service
run: |
choco install redis --version 5.0.7 --no-progress
choco install redis-64 --version 3.0.503 --no-progress
nssm install redis redis-server
nssm start redis
-5
View File
@@ -26,9 +26,6 @@
/.patches/
/.idea/
.phpstorm.*
!/.phpstorm.meta.php/
/.phpstorm.meta.php/*
!/.phpstorm.meta.php/di.php
/nbproject/
CVS
.DS_Store
@@ -58,5 +55,3 @@ moodle-plugin-ci.phar
.hugo_build.lock
phpcs.xml
jsconfig.json
UPGRADING-CURRENT.md
.phpunit.cache
+11 -49
View File
@@ -40,20 +40,9 @@ const fetchComponentData = () => {
if (!Object.entries(componentData).length) {
componentData.subsystems = {};
componentData.pathList = [];
componentData.components = {};
componentData.standardComponents = {};
// Fetch the component definiitions from the distributed JSON file.
const components = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/components.json`));
const pluginData = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/plugins.json`));
componentData.pluginTypes = components.plugintypes;
const standardPlugins = Object.entries(pluginData.standard).map(
([pluginType, pluginNames]) => {
return pluginNames.map(pluginName => `${pluginType}_${pluginName}`);
}
).reduce((acc, val) => acc.concat(val), []);
// Build the list of moodle subsystems.
componentData.subsystems.lib = 'core';
@@ -66,19 +55,8 @@ const fetchComponentData = () => {
}
}
// The list of components includes the list of subsystems.
componentData.components = {...componentData.subsystems};
const subpluginAdder = (subpluginType, subpluginTypePath) => {
glob.sync(`${subpluginTypePath}/*/version.php`).forEach(versionPath => {
const componentPath = fs.realpathSync(path.dirname(versionPath));
const componentName = path.basename(componentPath);
const frankenstyleName = `${subpluginType}_${componentName}`;
componentData.components[`${subpluginTypePath}/${componentName}`] = frankenstyleName;
componentData.pathList.push(componentPath);
});
};
// The list of components incldues the list of subsystems.
componentData.components = componentData.subsystems;
// Go through each of the plugintypes.
Object.entries(components.plugintypes).forEach(([pluginType, pluginTypePath]) => {
@@ -95,36 +73,20 @@ const fetchComponentData = () => {
if (fs.existsSync(subPluginConfigurationFile)) {
const subpluginList = JSON.parse(fs.readFileSync(fs.realpathSync(subPluginConfigurationFile)));
if (subpluginList.subplugintypes) {
Object.entries(subpluginList.subplugintypes).forEach(([subpluginType, subpluginTypePath]) => {
subpluginAdder(
subpluginType,
`${pluginTypePath}/${componentName}/${subpluginTypePath}`
);
Object.entries(subpluginList.plugintypes).forEach(([subpluginType, subpluginTypePath]) => {
glob.sync(`${subpluginTypePath}/*/version.php`).forEach(versionPath => {
const componentPath = fs.realpathSync(path.dirname(versionPath));
const componentName = path.basename(componentPath);
const frankenstyleName = `${subpluginType}_${componentName}`;
componentData.components[`${subpluginTypePath}/${componentName}`] = frankenstyleName;
componentData.pathList.push(componentPath);
});
} else if (subpluginList.plugintypes) {
Object.entries(subpluginList.plugintypes).forEach(([subpluginType, subpluginTypePath]) => {
subpluginAdder(subpluginType, subpluginTypePath);
});
}
});
}
});
});
// Create a list of the standard subsystem and plugins.
componentData.standardComponents = Object.fromEntries(
Object.entries(componentData.components).filter(([, name]) => {
if (name === 'core' || name.startsWith('core_')) {
return true;
}
return standardPlugins.indexOf(name) !== -1;
})
);
componentData.componentMapping = Object.fromEntries(
Object.entries(componentData.components).map(([path, name]) => [name, path])
);
}
return componentData;
-138
View File
@@ -1,138 +0,0 @@
// 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/>.
import * as Components from '../../components.js';
const componentData = Components.fetchComponentData();
/**
* The standard components shipped with core Moodle.
*
* @type {Object}
*/
export const standardComponents = componentData.standardComponents;
/**
* All components of the current Moodle instance.
*
* @type {Object}
*/
export const allComponents = componentData.components;
/**
* Get all components of the current Moodle instance.
*
* @returns {Array}
*/
export const getAllComponents = () => {
let components = new Map(Object.entries(componentData.pluginTypes).map(([value, path]) => ([path,{
path,
value,
name: `${value} (plugin type)`,
}])));
Object
.entries(componentData.components)
.filter(([path, value]) => Object.values(componentData.standardComponents).includes(value))
.forEach(([path, value]) => {
const entry = {
path,
value,
name: value,
};
if (Object.values(componentData.subsystems).includes(value)) {
if (components.has(path)) {
entry.name = `${value} (subsystem / plugintype)`;
} else {
entry.name = `${value} (subsystem)`;
}
}
components.set(path, entry);
});
return Array.from(components.values());
};
/**
* Whether the specified component is a standard component shipped with core Moodle.
*
* @param {string} componentName
* @returns {boolean}
*/
export const isStandardComponent = (componentName) => {
if (Object.values(componentData.standardComponents).includes(componentName)) {
return true;
}
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
return true;
}
return false;
};
export const rewritePlugintypeAsSubsystem = (componentName) => {
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
const pluginTypePath = componentData.pluginTypes[componentName];
if (Object.keys(componentData.subsystems).includes(pluginTypePath)) {
return true;
}
}
return false;
};
/**
* Whether the specified component is a community component.
*
* @param {string} componentName
* @returns {boolean}
*/
export const isCommunityComponent = (componentName) => {
if (isStandardComponent(componentName)) {
return false;
}
return Object.values(componentData.components).indexOf(componentName) !== -1;
};
/**
* Sort method for components.
*
* This method sorts components putting `core` first, followed by core subsystems, then everything else.
*
* @param {String} a
* @param {String} b
* @returns {Number}
*/
export const sortComponents = (a, b) => {
// Always put 'core' first.
if (a === 'core') {
return -1;
} else if (b === 'core') {
return 1;
}
// Put core subsystems next.
if (a.startsWith('core_') && !b.startsWith('core_')) {
return -1;
} else if (b.startsWith('core_') && !a.startsWith('core_')) {
return 1;
}
// Sort alphabetically for everything else.
return a.localeCompare(b);
};
-85
View File
@@ -1,85 +0,0 @@
#!/usr/bin/env node
// 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/>.
import inquirer from 'inquirer';
import chalk from 'chalk';
import { createNote } from './note.mjs';
import { getInitialValues } from './helpers.mjs';
import * as Prompts from './prompts.mjs';
import logger from './logger.mjs';
export default async (options) => {
// Processs the initial values.
const initialValues = getInitialValues(options);
// Fetch information.
const messages = [];
const { issueNumber } = await inquirer.prompt([
Prompts.getIssuePrompt(),
], initialValues);
let selection = {};
let notePath;
do {
selection = {};
selection = await inquirer.prompt([
Prompts.getComponentsPrompt(),
Prompts.getTypePrompt(),
Prompts.getMessagePromptInput(),
], initialValues);
if (selection.message === '') {
selection = Object.assign(
selection,
await inquirer.prompt([
Prompts.getMessagePromptEditor(),
]),
);
}
logger.info(`
Creating upgrade note with the following options:
- Issue: ${chalk.bold(issueNumber)}
- Component: ${chalk.bold(selection.components)}
- Type: ${chalk.bold(selection.type)}
- Message:
${chalk.bold(selection.message)}
`);
messages.push({
components: [selection.components],
type: selection.type,
message: selection.message,
});
// Save the note so far.
if (notePath) {
await createNote(issueNumber, messages, notePath);
logger.info(`Updated note at: ${chalk.underline(chalk.bold(notePath))}`);
} else {
notePath = await createNote(issueNumber, messages);
logger.info(`Note created at: ${chalk.underline(chalk.bold(notePath))}`);
}
selection = Object.assign(
selection,
await inquirer.prompt([
Prompts.getAddAnotherPrompt(),
], initialValues),
);
} while (selection.addAnother);
};
-357
View File
@@ -1,357 +0,0 @@
#!/usr/bin/env node
// 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/>.
import chalk from 'chalk';
import { getAllComponents } from './components.mjs';
import { getCombinedNotesByComponent, deleteAllNotes } from './note.mjs';
import { getNoteName } from './noteTypes.mjs';
import { writeFile, readFile, unlink } from 'fs/promises';
import { join as joinPath } from 'path';
import logger from './logger.mjs';
import { getCurrentVersion } from './helpers.mjs';
/**
* Helper to fetch the current notes from a file.
*
* @param {string} file
* @returns {Promise<string>}
*/
const getCurrentNotes = async (file) => {
try {
return await readFile(file, 'utf8');
} catch (error) {
return null;
}
}
/**
* Update the UPGRADING.md file.
*
* @param {string} upgradeNotes
* @param {Object} options
* @param {boolean} options.deleteNotes
* @returns {Promise<void>}
*/
const updateUpgradeNotes = async (upgradeNotes, options) => {
const fileName = 'UPGRADING.md';
// Write the notes to a file.
logger.info(`Writing notes to ${chalk.underline(chalk.bold(fileName))}`);
// Prepend to the existing file.
const existingContent = await getCurrentNotes(fileName);
if (existingContent) {
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
} else {
// This should not normally happen.
await writeFile(fileName, upgradeNotes);
}
if (options.deleteNotes) {
logger.warn(`>>> Deleting all notes <<<`)
// Delete the notes.
deleteAllNotes();
}
};
/**
* Create the current summary notes.
*
* @param {string} upgradeNotes
* @returns {Promise<void>}
*/
const createCurrentSummary = async (upgradeNotes) => {
const fileName = 'UPGRADING-CURRENT.md';
const notes = `# Moodle upgrade notes\n\n${upgradeNotes}`;
await writeFile(fileName, notes);
logger.info(`Running upgrade notes written to ${chalk.underline(chalk.bold(fileName))}`);
};
/**
* Get the indexes of the lines that contain the version headings.
*
* @param {array<string>} lines
* @returns {array<object>}
*/
const getVersionLineIndexes = (lines) => {
const h2Indexes = [];
lines.forEach((line, index) => {
const matches = line.match(/^##\s(?<version>.*)$/);
if (matches) {
h2Indexes.push({
index,
line,
version: matches.groups.version,
});
}
});
return h2Indexes;
};
/**
* Find the index of the Unreleased heading.
*
* @param {array<object>} versionHeadings
* @returns {number}
*/
const findUnreleasedHeadingIndex = (versionHeadings) => versionHeadings.findIndex((heading) => {
if (heading.version === 'Unreleased') {
// Used if version cannot be guessed.
return true;
}
if (heading.version.endsWith('+')) {
// Weekly release for a stable branch.
return true;
}
if (heading.version.match(/beta|rc\d/)) {
// Beta and RC rolls are treated as weeklies.
return true;
}
if (heading.version.endsWith('dev')) {
// Development version.
return true;
}
return false;
});
/**
* Get the before and after content, to facilitate replacing any existing Unreleased notes.
*
* @param {array<string>} lines
* @returns {Object} {beforeContent: string, afterContent: string}
*/
const getBeforeAndAfterContent = (lines) => {
const existingLines = lines.split('\n');
const versionHeadings = getVersionLineIndexes(existingLines);
if (versionHeadings.length > 0) {
const unreleasedHeadingIndex = findUnreleasedHeadingIndex(versionHeadings);
if (unreleasedHeadingIndex !== -1) {
const beforeContent = existingLines.slice(0, versionHeadings[unreleasedHeadingIndex].index).join('\n');
if (versionHeadings.length > unreleasedHeadingIndex + 1) {
const afterContent = existingLines.slice(versionHeadings[unreleasedHeadingIndex + 1].index).join('\n');
return {
beforeContent,
afterContent,
};
}
return {
beforeContent,
afterContent: '',
};
}
return {
beforeContent: existingLines.slice(0, versionHeadings[0].index).join('\n'),
afterContent: existingLines.slice(versionHeadings[0].index).join('\n'),
};
}
return {
beforeContent: existingLines.join('\n'),
afterContent: '',
}
};
/**
* Get the notes for the component.
*
* @param {string} types
* @param {Number} headingLevel
* @returns {string}
*/
const getNotesForComponent = (types, headingLevel) => {
let upgradeNotes = '';
Object.entries(types).forEach(([type, notes]) => {
upgradeNotes += '#'.repeat(headingLevel);
upgradeNotes += ` ${getNoteName(type)}\n\n`;
notes.forEach(({ message, issueNumber }) => {
// Split the message into lines, removing empty lines.
const messageLines = message
.split('\n')
// Remove empty lines between tables, and list entries, but not after lists.
.filter((line, index, lines) => {
if (line.trim().length === 0) {
// This line is empty.
// If it's the first line in the file, remove it.
if (index === 0) {
return false;
}
// This is the last line in the file, remove it.
if (index === lines.length - 1) {
return false;
}
// If the previous line relates to a table, remove this line.
if (lines[index - 1].match(/^\s*\|/)) {
return false;
}
// If the next line is also empty, do not remove this line.
if (lines[index + 1].trim().length === 0) {
return true;
}
// Do not remove the line if the previous line was a list item.
if (lines[index - 1].match(/^\s*[-*]\s/)) {
return true;
}
if (lines[index - 1].match(/^\s*\d+\.\s/)) {
return true;
}
// Preserve all other empty lines by default.
return true;
}
// Keep any line which has content.
return true;
});
const firstLine = messageLines.shift().trim();
upgradeNotes += `- ${firstLine}\n`;
messageLines
.forEach((line) => {
upgradeNotes += ` ${line}`.trimEnd() + `\n`;
});
upgradeNotes += `\n For more information see [${issueNumber}](https://tracker.moodle.org/browse/${issueNumber})\n`;
});
upgradeNotes += '\n';
});
return upgradeNotes;
};
/**
* Get the updated notes mixed with existing content.
*
* @param {string} existingContent
* @param {string} upgradeNotes
*/
const getUpdatedNotes = (existingContent, upgradeNotes) => {
const { beforeContent, afterContent } = getBeforeAndAfterContent(existingContent);
const newContent = `${beforeContent}\n${upgradeNotes}\n${afterContent}`
.split('\n')
.filter((line, index, lines) => {
if (line === '' && lines[index - 1] === '') {
// Remove multiple consecutive empty lines.
return false;
}
return true;
})
.join('\n');
return newContent;
};
/**
* Update the notes for each component.
*/
const updateComponentNotes = (
notes,
version,
notesFileName = 'UPGRADING.md',
removeEmpty = false,
) => {
return getAllComponents().map(async (component) => {
logger.verbose(`Updating notes for ${component.name} into ${component.path}`);
const fileName = joinPath(component.path, notesFileName);
const existingContent = await getCurrentNotes(fileName);
if (!existingContent) {
if (!notes[component.value]) {
// No existing notes, and no new notes to add.
return;
}
} else {
if (!notes[component.value]) {
// There is existing content, but nothing to add.
if (removeEmpty) {
logger.verbose(`Removing empty notes file ${fileName}`);
await unlink(fileName);
}
return;
}
}
const componentNotes = notes[component.value];
let upgradeNotes = `## ${version}\n\n`;
upgradeNotes += getNotesForComponent(componentNotes, 3);
if (existingContent) {
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
} else {
await writeFile(
fileName,
`# ${component.name} Upgrade notes\n\n${upgradeNotes}`,
);
}
});
}
/**
* Generate the upgrade notes for a new release.
*
* @param {string|undefined} version
* @param {Object} options
* @param {boolean} options.generateUpgradeNotes
* @param {boolean} options.deleteNotes
* @returns {Promise<void>}
*/
export default async (version, options = {}) => {
const notes = await getCombinedNotesByComponent();
if (Object.keys(notes).length === 0) {
logger.warn('No notes to generate');
return;
}
if (!version) {
version = await getCurrentVersion();
}
// Generate the upgrade notes for this release.
// We have
// - a title with the release name
// - the change types
// - which contain the components
// - which document each change
let upgradeNotes = `## ${version}\n\n`;
Object.entries(notes).forEach(([component, types]) => {
upgradeNotes += `### ${component}\n\n`;
upgradeNotes += getNotesForComponent(types, 4);
});
await Promise.all([
createCurrentSummary(upgradeNotes),
...updateComponentNotes(notes, version, 'UPGRADING-CURRENT.md', true),
]);
if (options.generateUpgradeNotes) {
await Promise.all(updateComponentNotes(notes, version));
await updateUpgradeNotes(upgradeNotes, options);
}
};
-230
View File
@@ -1,230 +0,0 @@
// 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/>.
import chalk from 'chalk';
import { isStandardComponent, isCommunityComponent, rewritePlugintypeAsSubsystem } from './components.mjs';
import { isValidNoteName } from './noteTypes.mjs';
import logger from './logger.mjs';
import { readFile } from 'fs/promises';
/**
* Validate an issue number input
*
* @param {string} input
* @returns {string|boolean}
*/
export const validateIssueNumber = (input) => {
if (!input) {
return 'You must provide a tracker issue number';
}
if (input.match(/^[a-zA-Z]*-\d+$/)) {
return true;
}
if (input.match(/^\d+$/)) {
return true;
}
return 'The issue number was not recognised as a valid issue number';
};
/**
* Format an issue number input.
*
* @param {string} input
* @returns {string}
*/
export const formatIssueNumber = (input) => {
if (input.match(/^[a-zA-Z]*-\d+$/)) {
return input;
}
if (input.match(/^\d+$/)) {
return `MDL-${input}`;
}
return input;
};
/**
* Validate a component.
*
* @param {string} input
* @returns {string|boolean}
*/
export const validateComponent = (input) => {
if (isStandardComponent(input)) {
return true;
}
if (isCommunityComponent(input)) {
return 'Currently only core plugins are supported.';
}
return 'The component was not recognised as a standard component';
};
export const formatComponent = (input) => {
if (rewritePlugintypeAsSubsystem(input)) {
return `core_${input}`;
}
return input;
}
/**
* Get the initial values from the options.
*
* @param {object} options
* @returns {object}
*/
export const getInitialValues = (options) => {
const initialValues = {};
const type = getInitialTypeValue(options);
if (type) {
initialValues.type = type;
}
const issueNumber = getInitialIssueValue(options);
if (issueNumber) {
initialValues.issueNumber = issueNumber;
}
const component = getInitialComponentValue(options);
if (component) {
initialValues.components = component;
}
const message = getInitialMessageValue(options);
if (message) {
initialValues.message = message
initialValues.addAnother = false;
}
return initialValues;
};
/**
* Get the initial type value.
*
* @param {Object} options
* @returns {string|undefined}
*/
const getInitialTypeValue = (options) => {
if (!options.type) {
return;
}
options.type = options.type.trim().toLowerCase();
if (isValidNoteName(options.type)) {
return options.type;
}
logger.warn(`Note type "${chalk.underline(chalk.red(options.type))}" is not valid.`);
};
/**
* Get the initial issue number value.
*
* @param {Object} options
* @returns {string|undefined}
*/
const getInitialIssueValue = (options) => {
if (!options.issue) {
return;
}
options.issue = options.issue.trim().toUpperCase();
const issueNumberValidated = validateIssueNumber(options.issue);
if (issueNumberValidated === true) {
const issueNumber = formatIssueNumber(options.issue);
if (issueNumber !== options.issue) {
logger.warn(
`Issue number "${chalk.underline(chalk.red(options.issue))}" was updated to ` +
`"${chalk.underline(chalk.green(issueNumber))}"`
);
}
return issueNumber;
} else {
logger.warn(`Issue number "${chalk.underline(chalk.red(options.issue))}" is not valid: ${issueNumberValidated}`);
}
};
/**
* Get the initial component value.
*
* @param {Object} options
* @returns {string|undefined}
*/
const getInitialComponentValue = (options) => {
if (!options.component) {
return;
}
options.component = options.component.trim().toLowerCase();
const componentValidated = validateComponent(options.component);
if (componentValidated === true) {
const component = formatComponent(options.component);
if (component !== options.component) {
logger.warn(
`Component "${chalk.underline(chalk.red(options.component))}" was updated to ` +
`"${chalk.underline(chalk.green(component))}"`
);
}
return component;
} else {
logger.warn(`Component "${chalk.underline(chalk.red(options.component))}" is not valid: ${componentValidated}`);
}
};
/**
* Get the initial message value.
*
* @param {Object} options
* @returns {string|undefined}
*/
const getInitialMessageValue = (options) => {
if (!options.message) {
return;
}
return options.message.trim();
};
/**
* Get the current version from the project /version.php file.
*
* @returns {Promise<string>}
*/
export const getCurrentVersion = async () => {
const versionRegex = new RegExp(/^ *\$release *= *['\"](?<release>[^ \+]+\+?) *\(Build:.*/m);
try {
const versionFile = await readFile('version.php', 'utf8');
const match = versionFile.match(versionRegex);
if (match) {
return match.groups.release;
}
} catch(error) {
logger.error('Unable to read the version file');
}
return "Unreleased";
}
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env node
// 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/>.
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
transports: [
new winston.transports.Console(),
],
});
export default logger;
-194
View File
@@ -1,194 +0,0 @@
// 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/>.
import yaml from 'js-yaml';
import path from 'path';
import { writeFile, mkdir, readdir, readFile, unlink } from 'fs/promises';
import { isValidNoteName, sortNoteTypes } from './noteTypes.mjs';
import { sortComponents } from './components.mjs';
const unreleasedPath = path.resolve('.upgradenotes');
/**
* Get the filename for the note.
*
* @param {string} issueNumnber The issue number
* @returns {string}
*/
const getFilename = (issueNumber) => {
const dateTimeFormat = new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 2,
timeZone: 'UTC',
});
const date = Object.fromEntries(
dateTimeFormat.formatToParts(new Date())
.filter((p) => p.type !== 'literal')
.map((p) => ([p.type, p.value]))
);
const dateString = [
date.year,
date.month,
date.day,
date.hour,
date.minute,
date.second,
date.fractionalSecond,
].join('');
return `${issueNumber}-${dateString}.yml`;
};
/**
* Create a new note.
*
* @param {string} issueNumber
* @param {[type: string]: {message: string}} messages
* @returns {string} The path to the note on disk
*/
export const createNote = async (
issueNumber,
messages,
notePath,
) => {
const note = {
issueNumber,
notes: {},
};
messages.forEach(({components, type, message}) => {
note.notes[components] = note.notes[components] || [];
note.notes[components].push({message, type});
});
if (!notePath) {
notePath = path.resolve(unreleasedPath, getFilename(issueNumber));
}
const noteContent = yaml.dump(note);
await mkdir(unreleasedPath, {recursive: true});
await writeFile(notePath, noteContent);
return notePath;
};
/**
* Get all unreleased notes.
*
* @returns {Promise<{issueNumber: string, components: string[], types: {[type: string]: {message: string}[]}}[]>
*/
export const getAllNotes = async () => {
const files = await readdir(unreleasedPath);
const notes = files
.filter((file) => file.endsWith('.yml'))
.map(async (file) => {
const filePath = path.resolve(unreleasedPath, file);
const fileContent = await readFile(filePath, 'utf8');
return {
...yaml.load(fileContent),
filePath,
};
});
return Promise.all(notes);
};
/**
* Get the list of notes, grouped by note type, then component.
*
* @returns {Promise<{[type: string]: {[components: string]: {message: string, issueNumber: string}[]}}>}
*/
export const getCombinedNotes = async () => {
const notes = await getAllNotes();
const combinedNotes = {};
notes.forEach((note) => {
Object.entries(note.notes).forEach(([components, data]) => {
data.forEach((entry) => {
if (!isValidNoteName(entry.type)) {
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
}
combinedNotes[entry.type] = combinedNotes[entry.type] || {};
combinedNotes[entry.type][components] = combinedNotes[entry.type][components] || [];
combinedNotes[entry.type][components].push({message: entry.message, issueNumber: note.issueNumber});
});
});
});
return combinedNotes;
};
/**
* Get the list of notes, grouped by component, then by note type.
*
* @returns {Promise<{[component: string]: {[type: string]: {message: string, issueNumber: string}[]}}>}
*/
export const getCombinedNotesByComponent = async () => {
const notes = await getAllNotes();
const combinedNotes = {};
notes.forEach((note) => {
Object.entries(note.notes).forEach(([component, data]) => {
combinedNotes[component] = combinedNotes[component] || {};
data.forEach((entry) => {
if (!isValidNoteName(entry.type)) {
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
}
combinedNotes[component][entry.type] = combinedNotes[component][entry.type] || [];
combinedNotes[component][entry.type].push({
component,
message: entry.message,
issueNumber: note.issueNumber,
type: entry.type,
});
});
});
});
// Sort notes by note type.
Object.entries(combinedNotes).forEach(([component, types]) => {
combinedNotes[component] = Object.fromEntries(
Object.entries(types).sort(([a], [b]) => sortNoteTypes(a, b))
);
});
// Sort components.
return Object.fromEntries(
Object.entries(combinedNotes).sort(([a], [b]) => sortComponents(a, b))
);
};
/**
* Delete all unreleased notes.
*
* @returns {Promise<void>}
*/
export const deleteAllNotes = async () => {
const files = await readdir(unreleasedPath);
return Promise.all(
files
.filter((item, index) => files.indexOf(item) === index)
.filter((file) => file.endsWith('.yml'))
.map((file) => unlink(`${unreleasedPath}/${file}`))
);
};
-80
View File
@@ -1,80 +0,0 @@
// 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 note types.
*
* Matches the suggested order in https://keepachangelog.com/en/1.1.0/.
* @type {Object<string, string>}
*/
const noteTypes = {
'improved': 'Added',
'changed': 'Changed',
'deprecated': 'Deprecated',
'removed': 'Removed',
'fixed': 'Fixed',
};
/**
* The preferred order of note types.
*
* @type {string[]}
*/
const preferredOrder = Object.keys(noteTypes);
/**
* Comparison method to sort note types.
*
* @param {String} a
* @param {String} b
* @returns {Number}
*/
export const sortNoteTypes = (a, b) => {
const aIndex = preferredOrder.indexOf(a);
const bIndex = preferredOrder.indexOf(b);
if (aIndex === -1) {
return 1;
}
if (bIndex === -1) {
return -1;
}
return aIndex - bIndex;
};
/**
* Get the note names.
*
* @returns {string[]}
*/
export const getNoteNames = () => Object.keys(noteTypes);
/**
* Get the human-readable note name.
*
* @param {string} type
* @returns {string}
*/
export const getNoteName = (type) => noteTypes[type];
/**
* Whether the note name is valid.
*
* @param {string} type
* @returns {boolean}
*/
export const isValidNoteName = (type) => noteTypes[type] !== undefined;
-153
View File
@@ -1,153 +0,0 @@
// 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/>.
import inquirer from 'inquirer';
import SearchList from 'inquirer-search-list';
import { getNoteNames } from './noteTypes.mjs';
import { getAllComponents } from './components.mjs';
import {
formatComponent,
formatIssueNumber,
validateComponent,
validateIssueNumber,
} from './helpers.mjs';
/**
* A Search List which accepts an initial value.
*/
class SearchListWithInitialValue extends SearchList {
constructor(options, ...args) {
super(options, ...args);
if (options.default) {
const pointer = this.filterList.findIndex((item) => {
return item.value === options.default;
});
if (pointer > -1) {
this.pointer = pointer;
}
}
}
}
inquirer.registerPrompt('search-list', SearchListWithInitialValue);
/**
* Get the issue type prompt.
*
* @param {string} defaultData The initially selected value
* @returns {Object}
*/
export const getTypePrompt = (defaultData) => ({
default: defaultData,
type: 'search-list',
message: 'Type of change',
name: 'type',
choices: getNoteNames(),
validate: (selection) => {
if (selection.length < 1) {
return 'You must select at least one type of change';
}
return true;
},
});
/**
* Get the component prompt.
*
* @param {string} [defaultValue='core'] The initally selected value.
* @returns
*/
export const getComponentsPrompt = (defaultValue) => {
if (!defaultValue ) {
defaultValue = 'core';
}
return {
choices: getAllComponents(),
default: defaultValue,
type: 'search-list',
message: 'Component',
name: 'components',
validate: validateComponent,
filter: formatComponent,
};
};
/**
* Get the issue number prompt as an inline input.
*
* @param {string} defaultData
* @returns {object}
*/
export const getIssuePrompt = (defaultData) => ({
default: defaultData,
type: 'input',
message: 'Tracker issue number',
name: 'issueNumber',
validate: validateIssueNumber,
filter: formatIssueNumber,
});
/**
* Get a message prompt.
*
* @param {string} defaultData
* @returns
*/
export const getMessagePromptEditor = (defaultData) => ({
default: defaultData,
type: process.stdin.isTTY ? 'editor' : 'input',
postfix: '.md',
message: 'Message',
name: 'message',
waitUserInput: false,
validate: (input) => {
if (!input) {
return 'You must provide a message';
}
return true;
},
// Remove any trailing whitespace.
filter: (input) => input.split('\n').map((line) => line.trimEnd()).join('\n'),
});
/**
* Get a message prompt.
*
* @param {string} defaultData
* @returns
*/
export const getMessagePromptInput = (defaultData) => ({
default: defaultData,
type: 'input',
message: 'Message (leave empty to use editor)',
name: 'message',
filter: (input) => input.trim(),
});
/**
* Get a prompt to ask the user if they wish to add another entry.
*
* @returns {Object}
*/
export const getAddAnotherPrompt = () => ({
type: 'confirm',
message: 'Do you want to add another note?',
default: false,
name: 'addAnother',
});
+14 -6
View File
@@ -21,7 +21,7 @@
*/
/**
* Function to generate the destination for the minification task
* Function to generate the destination for the uglify 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,6 +60,7 @@ 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');
@@ -103,7 +104,6 @@ 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,17 +113,15 @@ 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) => {
options: async() => {
return new Promise(resolve => {
queue.push(resolve);
startQueue();
return options;
});
},
// When an item in the queue completes, start the next item in the queue.
generateBundle: (options, bundle) => {
grunt.log.debug(`Finished output phase for ${Object.keys(bundle).join(', ')}`);
buildEnd: () => {
runQueue();
},
};
@@ -157,6 +155,16 @@ module.exports = grunt => {
],
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
}]
+12 -28
View File
@@ -20,33 +20,17 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = (grunt) => {
const path = require('path');
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();
});
module.exports = grunt => {
// Project configuration.
grunt.config.merge({
jsdoc: {
dist: {
options: {
configure: ".grunt/jsdoc/jsdoc.conf.js",
},
},
},
});
grunt.loadNpmTasks('grunt-jsdoc');
};
+1 -5
View File
@@ -29,7 +29,6 @@ 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.
@@ -46,10 +45,7 @@ module.exports = grunt => {
return {
stylelint: {
scss: {
options: {
quietDeprecationWarnings: true,
customSyntax: 'postcss-scss',
},
options: {syntax: 'scss'},
src: files,
},
},
-103
View File
@@ -1,103 +0,0 @@
#!/usr/bin/env node
// 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/>.
import { Argument, Option, program } from 'commander';
import chalk from 'chalk';
import { getNoteNames } from './notes/src/noteTypes.mjs';
import createAction from './notes/src/create.mjs';
import generateAction from './notes/src/generate.mjs';
import logger from './notes/src/logger.mjs';
console.log(`
${chalk.bold(chalk.underline(chalk.green('Moodle Upgrade Notes Generator')))}
This tool is used to generate the upgrade notes for changes you make in Moodle.
Please remember that the intended audience of these changes is
${chalk.italic('plugin developers')} who need to know how to update their plugins
for a new Moodle version.
Upgrade notes should not be used to document changes for site administrators, or
for internal API changes which are not expected to be used outside of the
relevant component.
`)
program.configureHelp({
helpWidth: 100,
});
program.on('option:verbose', () => {
logger.level = 'verbose';
});
program.addOption(
new Option(
'-v, --verbose',
'Output more information during the generation process',
)
.default(false)
);
// Define the command line options.
program
.command('create')
.summary('Generate a new upgrade note')
.addOption(
new Option('-t, --type <type>', `The type of change to document. Valid types are: ${getNoteNames().join(', ')}`)
)
.addOption(new Option('-i, --issue <issue>', 'The tracker issue number'))
.addOption(new Option('-c, --component <component>', 'The component to write a note for'))
.addOption(new Option(
'-m, --message <message>',
'The message to use for the upgrade note',
))
.action((options) => createAction(options));
program
.command('summary')
.summary('Generate a local copy of the upgrade notes summary')
.addArgument(
new Argument('[version]', 'The Moodle version to create the summary notes for')
)
.action((version) => generateAction(version));
program
.command('release')
.summary('Generate the markdown copies of the upgrade notes for a Moodle release')
.addArgument(
new Argument('[version]', 'The Moodle version to create the release notes for')
)
.addOption(
new Option(
'--generate-upgrade-notes',
'Generate the UPGRADING.md notes for the release. ' +
'Note: This option is intended for use by the release manager when generating the upgrade notes.',
)
.default(true)
)
.addOption(
new Option(
'-d, --delete-notes',
'Delete the notes after generating the UPGRADING.md notes for the release. ' +
'Note: This option is intended for use by the release manager when generating the upgrade notes.' +
'This option has no effect unless --generate-upgrade-notes is also set.'
)
.default(false)
)
.action((version, options) => generateAction(version, options));
program.parse();
+1 -1
View File
@@ -1 +1 @@
lts/jod
lts/gallium
-40
View File
@@ -1,40 +0,0 @@
<?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/>.
/**
* Helper file for PhpStorm, and other IDEs, to provide better code completion.
*
* @package core
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace PHPSTORM_META;
// Assume that anything returned by \core\di::get('...') is an instance of the first argument.
override(\core\di::get(0), map([
'' => '@',
]));
// Assume that anything returned by \Psr\Container\ContainerInterface::get('...') is an instance of the first argument.
override(\Psr\Container\ContainerInterface::get(0), map([
'' => '@',
]));
// Assume that anything returned by \DI\Container::get('...') is an instance of the first argument.
override(\DI\Container::get(0), map([
'' => '@',
]));
+4 -16
View File
@@ -1,14 +1,9 @@
{
"customSyntax": "postcss-scss",
"plugins": [
"stylelint-csstree-validator"
],
"rules": {
"csstree/validator": {
"syntaxExtensions": [
"sass"
]
},
"csstree/validator": true,
"at-rule-empty-line-before": [ "always",
{"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
],
@@ -50,7 +45,7 @@
"function-name-case": "lower",
"function-parentheses-newline-inside": "always-multi-line",
"function-parentheses-space-inside": "never-single-line",
"function-url-scheme-disallowed-list": ["data"],
"function-url-scheme-blacklist": ["data"],
"function-whitespace-after": "always",
"indentation": 4,
"keyframe-declaration-no-important": true,
@@ -89,19 +84,12 @@
"selector-type-no-unknown": true,
"string-no-newline": true,
"time-min-milliseconds": 100,
"unit-disallowed-list": ["pt"],
"unit-blacklist": ["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": {
}
}
]
}
}
-52
View File
@@ -1,52 +0,0 @@
# Contributing to Moodle
[Moodle][1] is made by people like you. We are members of a big worldwide community of developers, designers, teachers, testers, translators and many more. We work in universities, schools, companies and other places. You are very welcome to join us and contribute to the project.
There are many ways that you can contribute to Moodle, not just through development. See our [community contribution guide][2] for some of the many ways that you can help.
## Github
> [!NOTE]
> Please do not open pull requests via Github.
All issues should be reported via, and patched provided to the [Moodle Tracker][3].
The Moodle [Github][4] repository is a clone of the official Moodle repository, whcih can be found at https://git.moodle.org.
## Moodle core bug fixes and new features
Over the years, Moodle has developed a mature process for reporting, reviewing, and accepting patches. This is fully documented in our [documentation on development processes][6], but in summary:
- Every bug fix or new feature must have a tracker issue.
- You publish the branch implementing the fix or new feature in your public clone of the moodle.git repository (typically on Github).
- Your patch is peer-reviewed, discussed, integrated, tested and then released as a part of one of our weekly releases.
- New features are developed on the `main` branch. Bug fixes are also backported to currently supported maintenance (stable) branches.
> [!IMPORTANT]
> Please do not publish security issues, or patches releating to them publicly.
> See our [Responsible Disclosure Policy][5] for more information.
## Moodle plugins
Moodle has a framework for additional plugins to extend its functionality. We
have a Moodle plugins directory <https://moodle.org/plugins/> where you can
register and maintain your plugin. Plugins hosted in the plugins directory can
be easily installed and updated via the Moodle administration interface.
* You are expected to have a public source code repository with your plugin
code.
* After registering your plugin in the plugins directory it is reviewed before
being published.
* You are expected to continuously release updated versions of the plugin via
the plugins directory. We do not pull from your code repository; you must do
it explicitly.
For further details, see <https://moodledev.io/general/community/plugincontribution>.
[1]: https://moodle.org
[2]: https://moodledev.io/general/community/contribute
[3]: https://tracker.moodle.org
[4]: https://github.com/moodle/moodle
[5]: https://moodledev.io/general/development/process/security
[6]: https://moodledev.io/general/development/process
+56
View File
@@ -0,0 +1,56 @@
CONTRIBUTING TO MOODLE
======================
Moodle is made by people like you. We are members of a big worldwide community
of developers, designers, teachers, testers, translators and other users. We
work in universities, schools, companies and other places. You are very welcome
to join us and contribute to the project.
See <https://moodledev.io/general/community/contribute> for the many ways you
can help, not only with coding.
Moodle is open to community contributions to core, though all code must go
through peer-review, automated behaviour testing, continuous integration and
human post-integration checks.
Pull requests
-------------
Please do not open pull requests via Github. The repository there is just a
mirror of the official repository at <https://git.moodle.org>. Issues are
reported and patches provided via <https://tracker.moodle.org>. See below for
more information.
Moodle core bug fixes and new features
--------------------------------------
During the years of intensive development, a mature process of including
submitted patches has evolved.
* Every bug fix or new feature must have a tracker issue.
* You publish the branch implementing the fix or new feature in your public
clone of the moodle.git repository (typically on Github).
* Your patch is peer-reviewed, discussed, integrated, tested and then released
as a part of moodle.git.
* New features are developed on the master branch. Bug fixes are also
backported to currently supported maintenance (stable) branches.
For further details, see <https://moodledev.io/general/development/process>.
Moodle plugins
--------------
Moodle has a framework for additional plugins to extend its functionality. We
have a Moodle plugins directory <https://moodle.org/plugins/> where you can
register and maintain your plugin. Plugins hosted in the plugins directory can
be easily installed and updated via the Moodle administration interface.
* You are expected to have a public source code repository with your plugin
code.
* After registering your plugin in the plugins directory it is reviewed before
being published.
* You are expected to continuously release updated versions of the plugin via
the plugins directory. We do not pull from your code repository; you must do
it explicitly.
For further details, see <https://moodledev.io/general/community/plugincontribution>.
+7
View File
@@ -0,0 +1,7 @@
*** PLEASE DO NOT OPEN PULL REQUESTS VIA GITHUB ***
The moodle.git repository at Github is just a mirror of the official repository. We do not accept pull requests at Github.
See CONTRIBUTING.txt guidelines for how to contribute patches for Moodle. Thank you.
--
-48
View File
@@ -1,48 +0,0 @@
# Moodle
<p align="center"><a href="https://moodle.org" target="_blank" title="Moodle Website">
<img src="https://raw.githubusercontent.com/moodle/moodle/main/.github/moodlelogo.svg" alt="The Moodle Logo">
</a></p>
[Moodle][1] is the World's Open Source Learning Platform, widely used around the world by countless universities, schools, companies, and all manner of organisations and individuals.
Moodle is designed to allow educators, administrators and learners to create personalised learning environments with a single robust, secure and integrated system.
## Documentation
- Read our [User documentation][3]
- Discover our [developer documentation][5]
- Take a look at our [demo site][4]
## Community
[moodle.org][1] is the central hub for the Moodle Community, with spaces for educators, administrators and developers to meet and work together.
You may also be interested in:
- attending a [Moodle Moot][6]
- our regular series of [developer meetings][7]
- the [Moodle User Association][8]
## Installation and hosting
Moodle is Free, and Open Source software. You can easily [download Moodle][9] and run it on your own web server, however you may prefer to work with one of our experienced [Moodle Partners][10].
Moodle also offers hosting through both [MoodleCloud][11], and our [partner network][10].
## License
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. For more information on our license see
[1]: https://moodle.org
[2]: https://moodle.com
[3]: https://docs.moodle.org/
[4]: https://sandbox.moodledemo.net/
[5]: https://moodledev.io
[6]: https://moodle.com/events/mootglobal/
[7]: https://moodledev.io/general/community/meetings
[8]: https://moodleassociation.org/
[9]: https://download.moodle.org
[10]: https://moodle.com/partners
[11]: https://moodle.com/cloud
[12]: https://moodledev.io/general/license
+28
View File
@@ -0,0 +1,28 @@
.-..-.
_____ | || |
/____/-.---_ .---. .---. .-.| || | .---.
| | _ _ |/ _ \/ _ \/ _ || |/ __ \
* | | | | | || |_| || |_| || |_| || || |___/
|_| |_| |_|\_____/\_____/\_____||_|\_____)
Moodle - the world's open source learning platform
Moodle <https://moodle.org> is a learning platform designed to provide
educators, administrators and learners with a single robust, secure and
integrated system to create personalised learning environments.
You can download Moodle <https://download.moodle.org> and run it on your own
web server, ask one of our Moodle Partners <https://moodle.com/partners/> to
assist you, or have a MoodleCloud site <https://moodle.com/cloud/> set up for
you.
Moodle is widely used around the world by universities, schools, companies and
all manner of organisations and individuals.
Moodle is provided freely as open source software, under the GNU General Public
License <https://moodledev.io/general/license>.
Moodle is written in PHP and JavaScript and uses an SQL database for storing
the data.
See <https://docs.moodle.org> for details of Moodle's many features.
-2608
View File
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
# core_admin (subsystem) Upgrade notes
## 4.5rc1
### Removed
- The HTTP Server setting "Use slash arguments" (slasharguments) configuration setting and related to it has been removed. Calling the option with $CFG->slasharguments or get_config('slasharguments') is no longer available.
For more information see [MDL-62640](https://tracker.moodle.org/browse/MDL-62640)
-65
View File
@@ -1,65 +0,0 @@
<?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/>.
/**
* Manage AI subsystem plugins settings.
*
* @package core_admin
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->libdir . '/adminlib.php');
$action = required_param('action', PARAM_ALPHANUMEXT);
$type = required_param('type', PARAM_PLUGIN); // Must be provider or placement.
$name = required_param('name', PARAM_PLUGIN); // Plugin name, e.g openai.
$syscontext = context_system::instance();
$PAGE->set_url('/admin/ai.php');
$PAGE->set_context($syscontext);
require_admin();
require_sesskey();
$return = new moodle_url('/admin/settings.php', ['section' => "aisettings$type"]);
$plugins = core_plugin_manager::instance()->get_plugins_of_type("ai$type");
$sortorder = array_flip(array_keys($plugins));
if (!isset($plugins[$name])) {
throw new moodle_exception('aipluginnotfound', 'core_ai', $return, $name);
}
$plugintypename = $plugins[$name]->type . '_' . $plugins[$name]->name;
switch ($action) {
case 'disable':
if ($plugins[$name]->is_enabled()) {
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
$class::enable_plugin($name, false);
}
break;
case 'enable':
if (!$plugins[$name]->is_enabled()) {
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
$class::enable_plugin($name, true);
}
break;
}
redirect($return);
-10
View File
@@ -1,10 +0,0 @@
define("core_admin/bulk_user_actions",["exports","core_reportbuilder/local/selectors","core_table/local/dynamic/events","core_form/changechecker","core/custom_interaction_events","jquery"],(function(_exports,reportSelectors,tableEvents,FormChangeChecker,CustomEvents,_jquery){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* Add bulk actions to the users list report
*
* @module core_admin/bulk_user_actions
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,reportSelectors=_interopRequireWildcard(reportSelectors),tableEvents=_interopRequireWildcard(tableEvents),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),CustomEvents=_interopRequireWildcard(CustomEvents),_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const Selectors_bulkActionsForm="form#user-bulk-action-form",Selectors_userReportWrapper='[data-region="report-user-list-wrapper"]',Selectors_checkbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',Selectors_masterCheckbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',Selectors_checkedRows='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked';_exports.init=()=>{var _userBulkForm$closest;const userBulkForm=document.querySelector(Selectors_bulkActionsForm),userReport=null==userBulkForm||null===(_userBulkForm$closest=userBulkForm.closest(Selectors_userReportWrapper))||void 0===_userBulkForm$closest?void 0:_userBulkForm$closest.querySelector(reportSelectors.regions.report);if(!userBulkForm||!userReport)return;const actionSelect=userBulkForm.querySelector("select");CustomEvents.define(actionSelect,[CustomEvents.events.accessibleChange]),(0,_jquery.default)(actionSelect).on(CustomEvents.events.accessibleChange,(event=>{if(event.target.value&&"0"!=="".concat(event.target.value)){const e=new Event("submit",{cancelable:!0});userBulkForm.dispatchEvent(e),e.defaultPrevented||(FormChangeChecker.markFormSubmitted(userBulkForm),userBulkForm.submit())}}));const updateUserIds=()=>{const selectedUsers=[...userReport.querySelectorAll(Selectors_checkedRows)],selectedUserIds=selectedUsers.map((check=>parseInt(check.value)));userBulkForm.querySelector('[name="userids"]').value=selectedUserIds.join(","),actionSelect.disabled=0===selectedUsers.length,actionSelect.disabled&&(actionSelect.value="0");const selectedUsersNames=selectedUsers.map((check=>document.querySelector('label[for="'.concat(check.id,'"]')).textContent));userBulkForm.data={userids:selectedUserIds,usernames:selectedUsersNames}};updateUserIds(),document.addEventListener("change",(event=>{(event.target.matches(Selectors_checkbox)||event.target.matches(Selectors_masterCheckbox))&&userReport.contains(event.target)&&updateUserIds()})),document.addEventListener(tableEvents.tableContentRefreshed,(event=>{userReport.contains(event.target)&&updateUserIds()}))}}));
//# sourceMappingURL=bulk_user_actions.min.js.map
File diff suppressed because one or more lines are too long
-12
View File
@@ -1,12 +0,0 @@
define("core_admin/expand_hash",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.expandSection=void 0;
/**
* Expand the collapse section element.
*
* @module core_admin/expand_hash
* @copyright Meirza <meirza.arson@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.5
*/
const SELECTORS_COLLAPSE_ELEMENTS='[data-bs-toggle="collapse"]',SELECTORS_FOCUSTHENEXPAND_ELEMENTS=".focus-expand";_exports.init=()=>{const focusexpand=document.querySelector(SELECTORS_FOCUSTHENEXPAND_ELEMENTS);null==focusexpand||focusexpand.addEventListener("click",(()=>{expandSection("".concat(focusexpand.getAttribute("href")))}))};const expandSection=hash=>{const container=document.querySelector(hash),targetContainer=null==container?void 0:container.querySelector(SELECTORS_COLLAPSE_ELEMENTS);if("false"===(null==targetContainer?void 0:targetContainer.getAttribute("aria-expanded"))){const collapseId=targetContainer.getAttribute("aria-controls"),collapseContainer=document.getElementById(collapseId);collapseContainer.classList.remove("collapse"),collapseContainer.classList.add("show"),targetContainer.setAttribute("aria-expanded","true"),targetContainer.classList.remove("collapsed");const collapseElement=document.querySelector(".collapseexpand.collapsemenu");collapseElement.setAttribute("aria-expanded","true"),collapseElement.classList.remove("collapsed")}};_exports.expandSection=expandSection}));
//# sourceMappingURL=expand_hash.min.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"expand_hash.min.js","sources":["../src/expand_hash.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Expand the collapse section element.\n *\n * @module core_admin/expand_hash\n * @copyright Meirza <meirza.arson@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.5\n */\n\nconst SELECTORS = {\n COLLAPSE_ELEMENTS: '[data-bs-toggle=\"collapse\"]',\n FOCUSTHENEXPAND_ELEMENTS: '.focus-expand',\n};\n\n/**\n * Initializes the focus and expand functionality.\n */\nexport const init = () => {\n // Select all collapsible elements only.\n const focusexpand = document.querySelector(SELECTORS.FOCUSTHENEXPAND_ELEMENTS);\n\n // Add click event listener to the anchor element\n focusexpand?.addEventListener('click', () => {\n expandSection(`${focusexpand.getAttribute('href')}`);\n });\n};\n\n/**\n * Expands a section based on the provided URL hash.\n *\n * This function takes a hash string, finds the corresponding element in the DOM,\n * and expands it if it is currently collapsed. It updates the necessary ARIA\n * attributes and classes to reflect the expanded state.\n *\n * @param {string} hash - The hash (e.g., '#elementId') of the element to expand.\n */\nexport const expandSection = (hash) => {\n const container = document.querySelector(hash);\n const targetContainer = container?.querySelector(SELECTORS.COLLAPSE_ELEMENTS);\n\n if (targetContainer?.getAttribute('aria-expanded') === 'false') {\n const collapseId = targetContainer.getAttribute('aria-controls');\n const collapseContainer = document.getElementById(collapseId);\n\n // Show the content.\n collapseContainer.classList.remove('collapse');\n collapseContainer.classList.add('show');\n\n // Update aria-expanded attribute to reflect the new state.\n targetContainer.setAttribute('aria-expanded', 'true');\n targetContainer.classList.remove('collapsed');\n\n // Get collapse expand menu element.\n const collapseElement = document.querySelector('.collapseexpand.collapsemenu');\n // Ensure it gets noticed to make it work.\n collapseElement.setAttribute('aria-expanded', 'true');\n collapseElement.classList.remove('collapsed');\n }\n};\n"],"names":["SELECTORS","focusexpand","document","querySelector","addEventListener","expandSection","getAttribute","hash","container","targetContainer","collapseId","collapseContainer","getElementById","classList","remove","add","setAttribute","collapseElement"],"mappings":";;;;;;;;;MAwBMA,4BACiB,8BADjBA,mCAEwB,8BAMV,WAEVC,YAAcC,SAASC,cAAcH,oCAG3CC,MAAAA,aAAAA,YAAaG,iBAAiB,SAAS,KACnCC,wBAAiBJ,YAAYK,aAAa,oBAarCD,cAAiBE,aACpBC,UAAYN,SAASC,cAAcI,MACnCE,gBAAkBD,MAAAA,iBAAAA,UAAWL,cAAcH,gCAEM,WAAnDS,MAAAA,uBAAAA,gBAAiBH,aAAa,kBAA8B,OACtDI,WAAaD,gBAAgBH,aAAa,iBAC1CK,kBAAoBT,SAASU,eAAeF,YAGlDC,kBAAkBE,UAAUC,OAAO,YACnCH,kBAAkBE,UAAUE,IAAI,QAGhCN,gBAAgBO,aAAa,gBAAiB,QAC9CP,gBAAgBI,UAAUC,OAAO,mBAG3BG,gBAAkBf,SAASC,cAAc,gCAE/Cc,gBAAgBD,aAAa,gBAAiB,QAC9CC,gBAAgBJ,UAAUC,OAAO"}
+1 -1
View File
@@ -1,3 +1,3 @@
define("core_admin/plugin_management_table",["exports","core_table/dynamic","core_table/local/dynamic/selectors","core/ajax","core/pending","core/notification"],(function(_exports,_dynamic,Selectors,_ajax,_pending,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};let watching=!1;return _exports.default=class{constructor(){!function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"clickHandlers",[]),this.addClickHandler(this.handleStateToggle),this.addClickHandler(this.handleMoveUpDown),this.registerEventListeners()}static init(){watching||(watching=!0,new this)}addClickHandler(handler){this.clickHandlers.push(handler.bind(this))}registerEventListeners(){document.addEventListener("click",function(e){const tableRoot=this.getTableRoot(e);tableRoot&&this.clickHandlers.forEach((handler=>handler(tableRoot,e)))}.bind(this))}getTableRoot(e){const tableRoot=e.target.closest(Selectors.main.region);return tableRoot||!1}setPluginState(methodname,plugin,state){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state}}])[0]}setPluginOrder(methodname,plugin,direction){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,direction:direction}}])[0]}async handleStateToggle(tableRoot,e){const stateToggle=e.target.closest('[data-action="togglestate"][data-toggle-method]');if(stateToggle){e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:togglestate");await this.setPluginState(stateToggle.dataset.toggleMethod,stateToggle.dataset.plugin,"1"===stateToggle.dataset.state?0:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]);updatedRoot.querySelector('[data-action="togglestate"][data-plugin="'.concat(stateToggle.dataset.plugin,'"]')).focus(),this.removeTooltips(),pendingPromise.resolve()}}async handleMoveUpDown(tableRoot,e){const actionLink=e.target.closest('[data-action="move"][data-method][data-direction]');if(!actionLink)return;e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:processAction");await this.setPluginOrder(actionLink.dataset.method,actionLink.dataset.plugin,"up"===actionLink.dataset.direction?-1:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),exactMatch=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"][data-direction="').concat(actionLink.dataset.direction,'"]'));var _updatedRoot$querySel;exactMatch?exactMatch.focus():null===(_updatedRoot$querySel=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"]')))||void 0===_updatedRoot$querySel||_updatedRoot$querySel.focus();pendingPromise.resolve()}removeTooltips(){const tooltips=document.querySelectorAll('[id*="tooltip"]');tooltips.length>0&&tooltips.forEach((tooltip=>{tooltip.remove()}))}},_exports.default}));
define("core_admin/plugin_management_table",["exports","core_table/dynamic","core_table/local/dynamic/selectors","core/ajax","core/pending","core/notification"],(function(_exports,_dynamic,Selectors,_ajax,_pending,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};let watching=!1;return _exports.default=class{constructor(){!function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"clickHandlers",[]),this.addClickHandler(this.handleStateToggle),this.addClickHandler(this.handleMoveUpDown),this.registerEventListeners()}static init(){watching||(watching=!0,new this)}addClickHandler(handler){this.clickHandlers.push(handler.bind(this))}registerEventListeners(){document.addEventListener("click",function(e){const tableRoot=this.getTableRoot(e);tableRoot&&this.clickHandlers.forEach((handler=>handler(tableRoot,e)))}.bind(this))}getTableRoot(e){const tableRoot=e.target.closest(Selectors.main.region);return tableRoot||!1}setPluginState(methodname,plugin,state){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state}}])[0]}setPluginOrder(methodname,plugin,direction){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,direction:direction}}])[0]}async handleStateToggle(tableRoot,e){const stateToggle=e.target.closest('[data-action="togglestate"][data-toggle-method]');if(stateToggle){e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:togglestate");await this.setPluginState(stateToggle.dataset.toggleMethod,stateToggle.dataset.plugin,"1"===stateToggle.dataset.state?0:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]);updatedRoot.querySelector('[data-action="togglestate"][data-plugin="'.concat(stateToggle.dataset.plugin,'"]')).focus(),pendingPromise.resolve()}}async handleMoveUpDown(tableRoot,e){const actionLink=e.target.closest('[data-action="move"][data-method][data-direction]');if(!actionLink)return;e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:processAction");await this.setPluginOrder(actionLink.dataset.method,actionLink.dataset.plugin,"up"===actionLink.dataset.direction?-1:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),exactMatch=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"][data-direction="').concat(actionLink.dataset.direction,'"]'));var _updatedRoot$querySel;exactMatch?exactMatch.focus():null===(_updatedRoot$querySel=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"]')))||void 0===_updatedRoot$querySel||_updatedRoot$querySel.focus();pendingPromise.resolve()}},_exports.default}));
//# sourceMappingURL=plugin_management_table.min.js.map
File diff suppressed because one or more lines are too long
-11
View File
@@ -1,11 +0,0 @@
define("core_admin/plugins_overview",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){const filters=document.querySelectorAll(SELECTORS_PLUGIN_FILTERS),pluginRows=document.querySelectorAll(SELECTORS_PLUGIN_ROWS),pluginTypeRows=document.querySelectorAll(SELECTORS_PLUGIN_TYPE_ROWS),filterPlugins=target=>{const filterBy=target.getAttribute("data-filterby"),headerVisibility={};for(const row of pluginRows){const type=[...row.classList].find((s=>s.startsWith("type-"))),visible="all"===filterBy||row.classList.contains(filterBy);row.style.display=visible?null:"none",visible&&type&&(headerVisibility[type]=!0)}for(const row of pluginTypeRows){const type=[...row.classList].find((s=>s.startsWith("type-")));if(type){const visible="all"===filterBy||headerVisibility[type];row.style.display=visible?null:"none"}}filters.forEach((el=>el.classList.remove("active"))),target.classList.add("active")};if(filters.forEach((target=>target.addEventListener("click",(e=>{e.preventDefault(),window.history.replaceState({},null,e.target.href),filterPlugins(target)})))),window.location.hash.length>1){const anchor=window.location.hash.substring(1),target=[...filters].find((t=>t.getAttribute("data-filterby")===anchor));target&&filterPlugins(target)}};
/**
* Allows to filter the plugin list on plugins overview page
*
* @module core_admin/plugins_overview
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const SELECTORS_PLUGIN_FILTERS="#plugins-overview-panel [data-filterby]",SELECTORS_PLUGIN_ROWS="table#plugins-control-panel tbody tr:not(.plugintypeheader)",SELECTORS_PLUGIN_TYPE_ROWS="table#plugins-control-panel tbody tr.plugintypeheader"}));
//# sourceMappingURL=plugins_overview.min.js.map
@@ -1 +0,0 @@
{"version":3,"file":"plugins_overview.min.js","sources":["../src/plugins_overview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Allows to filter the plugin list on plugins overview page\n *\n * @module core_admin/plugins_overview\n * @copyright 2024 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst SELECTORS = {\n PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',\n PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',\n PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',\n};\n\n/**\n * Initialise filters for the \"Plugins overview\" page\n */\nexport function init() {\n const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);\n const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);\n const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);\n\n const filterPlugins = (target) => {\n const filterBy = target.getAttribute('data-filterby');\n const headerVisibility = {};\n\n // Hide all plugin rows in the plugin table that do not match the filter and show all others.\n for (const row of pluginRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);\n row.style.display = visible ? null : 'none';\n if (visible && type) {\n headerVisibility[type] = true;\n }\n }\n\n // Hide all the plugin type headers that do not have any visible plugins and show all others.\n for (const row of pluginTypeRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n if (type) {\n const visible = filterBy === 'all' || headerVisibility[type];\n row.style.display = visible ? null : 'none';\n }\n }\n\n // Toggle 'active' class for the selected filter.\n filters.forEach(el => el.classList.remove('active'));\n target.classList.add('active');\n };\n\n // Add event listeners for the links changing plugins filters.\n filters\n .forEach(target => target.addEventListener('click', (e) => {\n e.preventDefault();\n window.history.replaceState({}, null, e.target.href);\n filterPlugins(target);\n }));\n\n // Pre-filter plugins based on the current url anchor.\n if (window.location.hash.length > 1) {\n const anchor = window.location.hash.substring(1);\n const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);\n if (target) {\n filterPlugins(target);\n }\n }\n}\n"],"names":["filters","document","querySelectorAll","SELECTORS","pluginRows","pluginTypeRows","filterPlugins","target","filterBy","getAttribute","headerVisibility","row","type","classList","find","s","startsWith","visible","contains","style","display","forEach","el","remove","add","addEventListener","e","preventDefault","window","history","replaceState","href","location","hash","length","anchor","substring","t"],"mappings":"4JAiCUA,QAAUC,SAASC,iBAAiBC,0BACpCC,WAAaH,SAASC,iBAAiBC,uBACvCE,eAAiBJ,SAASC,iBAAiBC,4BAE3CG,cAAiBC,eACbC,SAAWD,OAAOE,aAAa,iBAC/BC,iBAAmB,OAGpB,MAAMC,OAAOP,WAAY,OACpBQ,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,WACjDC,QAAuB,QAAbT,UAA4BG,IAAIE,UAAUK,SAASV,UACnEG,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,OACjCA,SAAWL,OACXF,iBAAiBE,OAAQ,OAK5B,MAAMD,OAAON,eAAgB,OACxBO,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,cACnDJ,KAAM,OACAK,QAAuB,QAAbT,UAAsBE,iBAAiBE,MACvDD,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,QAK7CjB,QAAQqB,SAAQC,IAAMA,GAAGT,UAAUU,OAAO,YAC1ChB,OAAOM,UAAUW,IAAI,cAIzBxB,QACCqB,SAAQd,QAAUA,OAAOkB,iBAAiB,SAAUC,IACjDA,EAAEC,iBACFC,OAAOC,QAAQC,aAAa,GAAI,KAAMJ,EAAEnB,OAAOwB,MAC/CzB,cAAcC,aAIdqB,OAAOI,SAASC,KAAKC,OAAS,EAAG,OAC3BC,OAASP,OAAOI,SAASC,KAAKG,UAAU,GACxC7B,OAAS,IAAIP,SAASc,MAAKuB,GAAKA,EAAE5B,aAAa,mBAAqB0B,SACtE5B,QACAD,cAAcC;;;;;;;;MAvDpBJ,yBACc,0CADdA,sBAEW,8DAFXA,2BAGgB"}
-10
View File
@@ -1,10 +0,0 @@
define("core_admin/themeselector/preview_modal",["exports","core/modal_events","core/modal_cancel","core/modal_save_cancel","core/notification","core/templates","core/str"],(function(_exports,_modal_events,_modal_cancel,_modal_save_cancel,_notification,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Modal for theme previews.
*
* @module core_admin/themeselector/preview_modal
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal_events=_interopRequireDefault(_modal_events),_modal_cancel=_interopRequireDefault(_modal_cancel),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);const SELECTORS_THEMES_CONTAINER="themelist",SELECTORS_PREVIEW='[data-action="preview"]';_exports.init=()=>{registerListenerEvents()};const registerListenerEvents=()=>{document.addEventListener("click",(e=>{const preview=e.target.closest(SELECTORS_PREVIEW);preview&&buildModal(preview).catch(_notification.default.exception)}))},buildModal=async element=>{let description=await(0,_str.getString)("choosereadme","theme_"+element.getAttribute("data-choose"));const themesContainer=document.getElementById(SELECTORS_THEMES_CONTAINER),definedInConfig=parseInt(themesContainer.dataset.definedinconfig),data={name:element.getAttribute("data-name"),image:element.getAttribute("data-image"),description:description.replace(/<[^>]+>/g," "),current:element.getAttribute("data-current"),actionurl:element.getAttribute("data-actionurl"),choose:element.getAttribute("data-choose"),sesskey:element.getAttribute("data-sesskey"),definedinconfig:definedInConfig};let modalTemplate=_modal_save_cancel.default;(data.current||data.definedinconfig)&&(modalTemplate=_modal_cancel.default);const modal=await modalTemplate.create({title:data.name,body:_templates.default.render("core_admin/themeselector/theme_preview_modal",data),large:!0,buttons:{save:(0,_str.getString)("selecttheme","moodle"),cancel:(0,_str.getString)("closebuttontitle","moodle")},show:!0});modal.getRoot().on(_modal_events.default.save,(()=>{modal.getRoot().find("form").submit()}))}}));
//# sourceMappingURL=preview_modal.min.js.map
@@ -1 +0,0 @@
{"version":3,"file":"preview_modal.min.js","sources":["../../src/themeselector/preview_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Modal for theme previews.\n *\n * @module core_admin/themeselector/preview_modal\n * @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalEvents from 'core/modal_events';\nimport ModalCancel from 'core/modal_cancel';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\n\nconst SELECTORS = {\n THEMES_CONTAINER: 'themelist',\n PREVIEW: '[data-action=\"preview\"]',\n};\n\n/**\n * Entrypoint of the js.\n *\n * @method init\n */\nexport const init = () => {\n registerListenerEvents();\n};\n\n/**\n * Register theme related event listeners.\n *\n * @method registerListenerEvents\n */\nconst registerListenerEvents = () => {\n document.addEventListener('click', (e) => {\n const preview = e.target.closest(SELECTORS.PREVIEW);\n if (preview) {\n buildModal(preview).catch(Notification.exception);\n }\n });\n};\n\n/**\n * Build the modal with the provided data.\n *\n * @method buildModal\n * @param {object} element\n */\nconst buildModal = async(element) => {\n\n // This string can be long. We will fetch it with JS as opposed to passing it as an attribute.\n let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose'));\n\n const themesContainer = document.getElementById(SELECTORS.THEMES_CONTAINER);\n const definedInConfig = parseInt(themesContainer.dataset.definedinconfig);\n // Prepare data for modal.\n const data = {\n name: element.getAttribute('data-name'),\n image: element.getAttribute('data-image'),\n description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags.\n current: element.getAttribute('data-current'),\n actionurl: element.getAttribute('data-actionurl'),\n choose: element.getAttribute('data-choose'),\n sesskey: element.getAttribute('data-sesskey'),\n definedinconfig: definedInConfig,\n };\n\n // Determine which modal template we should use.\n let modalTemplate = ModalSaveCancel;\n if (data.current || data.definedinconfig) {\n modalTemplate = ModalCancel;\n }\n\n const modal = await modalTemplate.create({\n title: data.name,\n body: Templates.render('core_admin/themeselector/theme_preview_modal', data),\n large: true,\n buttons: {\n 'save': getString('selecttheme', 'moodle'),\n 'cancel': getString('closebuttontitle', 'moodle'),\n },\n show: true,\n });\n\n modal.getRoot().on(ModalEvents.save, () => {\n modal.getRoot().find('form').submit();\n });\n};\n"],"names":["SELECTORS","registerListenerEvents","document","addEventListener","e","preview","target","closest","buildModal","catch","Notification","exception","async","description","element","getAttribute","themesContainer","getElementById","definedInConfig","parseInt","dataset","definedinconfig","data","name","image","replace","current","actionurl","choose","sesskey","modalTemplate","ModalSaveCancel","ModalCancel","modal","create","title","body","Templates","render","large","buttons","show","getRoot","on","ModalEvents","save","find","submit"],"mappings":";;;;;;;gWA8BMA,2BACgB,YADhBA,kBAEO,wCAQO,KAChBC,gCAQEA,uBAAyB,KAC3BC,SAASC,iBAAiB,SAAUC,UAC1BC,QAAUD,EAAEE,OAAOC,QAAQP,mBAC7BK,SACAG,WAAWH,SAASI,MAAMC,sBAAaC,eAW7CH,WAAaI,MAAAA,cAGXC,kBAAoB,kBAAU,eAAgB,SAAWC,QAAQC,aAAa,sBAE5EC,gBAAkBd,SAASe,eAAejB,4BAC1CkB,gBAAkBC,SAASH,gBAAgBI,QAAQC,iBAEnDC,KAAO,CACTC,KAAMT,QAAQC,aAAa,aAC3BS,MAAOV,QAAQC,aAAa,cAC5BF,YAAaA,YAAYY,QAAQ,WAAY,KAC7CC,QAASZ,QAAQC,aAAa,gBAC9BY,UAAWb,QAAQC,aAAa,kBAChCa,OAAQd,QAAQC,aAAa,eAC7Bc,QAASf,QAAQC,aAAa,gBAC9BM,gBAAiBH,qBAIjBY,cAAgBC,4BAChBT,KAAKI,SAAWJ,KAAKD,mBACrBS,cAAgBE,6BAGdC,YAAcH,cAAcI,OAAO,CACrCC,MAAOb,KAAKC,KACZa,KAAMC,mBAAUC,OAAO,+CAAgDhB,MACvEiB,OAAO,EACPC,QAAS,OACG,kBAAU,cAAe,kBACvB,kBAAU,mBAAoB,WAE5CC,MAAM,IAGVR,MAAMS,UAAUC,GAAGC,sBAAYC,MAAM,KACjCZ,MAAMS,UAAUI,KAAK,QAAQC"}
-97
View File
@@ -1,97 +0,0 @@
// 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/>.
/**
* Add bulk actions to the users list report
*
* @module core_admin/bulk_user_actions
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as reportSelectors from 'core_reportbuilder/local/selectors';
import * as tableEvents from 'core_table/local/dynamic/events';
import * as FormChangeChecker from 'core_form/changechecker';
import * as CustomEvents from 'core/custom_interaction_events';
import jQuery from 'jquery';
const Selectors = {
bulkActionsForm: 'form#user-bulk-action-form',
userReportWrapper: '[data-region="report-user-list-wrapper"]',
checkbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',
masterCheckbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',
checkedRows: '[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',
};
/**
* Initialise module
*/
export const init = () => {
const userBulkForm = document.querySelector(Selectors.bulkActionsForm);
const userReport = userBulkForm?.closest(Selectors.userReportWrapper)?.querySelector(reportSelectors.regions.report);
if (!userBulkForm || !userReport) {
return;
}
const actionSelect = userBulkForm.querySelector('select');
CustomEvents.define(actionSelect, [CustomEvents.events.accessibleChange]);
jQuery(actionSelect).on(CustomEvents.events.accessibleChange, event => {
if (event.target.value && `${event.target.value}` !== "0") {
const e = new Event('submit', {cancelable: true});
userBulkForm.dispatchEvent(e);
if (!e.defaultPrevented) {
FormChangeChecker.markFormSubmitted(userBulkForm);
userBulkForm.submit();
}
}
});
// Every time the checkboxes in the report are changed, update the list of users in the form values
// and enable/disable the action select.
const updateUserIds = () => {
const selectedUsers = [...userReport.querySelectorAll(Selectors.checkedRows)];
const selectedUserIds = selectedUsers.map(check => parseInt(check.value));
userBulkForm.querySelector('[name="userids"]').value = selectedUserIds.join(',');
// Disable the action selector if nothing selected, and reset the current selection.
actionSelect.disabled = selectedUsers.length === 0;
if (actionSelect.disabled) {
actionSelect.value = "0";
}
const selectedUsersNames = selectedUsers.map(check => document.querySelector(`label[for="${check.id}"]`).textContent);
// Add the user ids and names to the form data attributes so they can be available from the
// other JS modules that listen to the form submit event.
userBulkForm.data = {userids: selectedUserIds, usernames: selectedUsersNames};
};
updateUserIds();
document.addEventListener('change', event => {
// When checkboxes are checked next to individual users or the master toggle (Select all/none).
if ((event.target.matches(Selectors.checkbox) || event.target.matches(Selectors.masterCheckbox))
&& userReport.contains(event.target)) {
updateUserIds();
}
});
document.addEventListener(tableEvents.tableContentRefreshed, event => {
// When the report contents is updated (i.e. page is changed, filters applied, etc).
if (userReport.contains(event.target)) {
updateUserIds();
}
});
};
-74
View File
@@ -1,74 +0,0 @@
// 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/>.
/**
* Expand the collapse section element.
*
* @module core_admin/expand_hash
* @copyright Meirza <meirza.arson@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.5
*/
const SELECTORS = {
COLLAPSE_ELEMENTS: '[data-bs-toggle="collapse"]',
FOCUSTHENEXPAND_ELEMENTS: '.focus-expand',
};
/**
* Initializes the focus and expand functionality.
*/
export const init = () => {
// Select all collapsible elements only.
const focusexpand = document.querySelector(SELECTORS.FOCUSTHENEXPAND_ELEMENTS);
// Add click event listener to the anchor element
focusexpand?.addEventListener('click', () => {
expandSection(`${focusexpand.getAttribute('href')}`);
});
};
/**
* Expands a section based on the provided URL hash.
*
* This function takes a hash string, finds the corresponding element in the DOM,
* and expands it if it is currently collapsed. It updates the necessary ARIA
* attributes and classes to reflect the expanded state.
*
* @param {string} hash - The hash (e.g., '#elementId') of the element to expand.
*/
export const expandSection = (hash) => {
const container = document.querySelector(hash);
const targetContainer = container?.querySelector(SELECTORS.COLLAPSE_ELEMENTS);
if (targetContainer?.getAttribute('aria-expanded') === 'false') {
const collapseId = targetContainer.getAttribute('aria-controls');
const collapseContainer = document.getElementById(collapseId);
// Show the content.
collapseContainer.classList.remove('collapse');
collapseContainer.classList.add('show');
// Update aria-expanded attribute to reflect the new state.
targetContainer.setAttribute('aria-expanded', 'true');
targetContainer.classList.remove('collapsed');
// Get collapse expand menu element.
const collapseElement = document.querySelector('.collapseexpand.collapsemenu');
// Ensure it gets noticed to make it work.
collapseElement.setAttribute('aria-expanded', 'true');
collapseElement.classList.remove('collapsed');
}
};
-16
View File
@@ -138,10 +138,6 @@ export default class {
// Refocus on the link that as pressed in the first place.
updatedRoot.querySelector(`[data-action="togglestate"][data-plugin="${stateToggle.dataset.plugin}"]`).focus();
// When clicking the toggle and it remains focused, a new tooltip will be generated.
// Therefore, the old tooltip should be removed.
this.removeTooltips();
pendingPromise.resolve();
}
}
@@ -180,16 +176,4 @@ export default class {
pendingPromise.resolve();
}
/**
* Remove tooltips.
*/
removeTooltips() {
const tooltips = document.querySelectorAll('[id*="tooltip"]');
if (tooltips.length > 0) {
tooltips.forEach(tooltip => {
tooltip.remove();
});
}
}
}
-82
View File
@@ -1,82 +0,0 @@
// 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/>.
/**
* Allows to filter the plugin list on plugins overview page
*
* @module core_admin/plugins_overview
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const SELECTORS = {
PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',
PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',
PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',
};
/**
* Initialise filters for the "Plugins overview" page
*/
export function init() {
const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);
const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);
const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);
const filterPlugins = (target) => {
const filterBy = target.getAttribute('data-filterby');
const headerVisibility = {};
// Hide all plugin rows in the plugin table that do not match the filter and show all others.
for (const row of pluginRows) {
const type = [...row.classList].find(s => s.startsWith('type-'));
const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);
row.style.display = visible ? null : 'none';
if (visible && type) {
headerVisibility[type] = true;
}
}
// Hide all the plugin type headers that do not have any visible plugins and show all others.
for (const row of pluginTypeRows) {
const type = [...row.classList].find(s => s.startsWith('type-'));
if (type) {
const visible = filterBy === 'all' || headerVisibility[type];
row.style.display = visible ? null : 'none';
}
}
// Toggle 'active' class for the selected filter.
filters.forEach(el => el.classList.remove('active'));
target.classList.add('active');
};
// Add event listeners for the links changing plugins filters.
filters
.forEach(target => target.addEventListener('click', (e) => {
e.preventDefault();
window.history.replaceState({}, null, e.target.href);
filterPlugins(target);
}));
// Pre-filter plugins based on the current url anchor.
if (window.location.hash.length > 1) {
const anchor = window.location.hash.substring(1);
const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);
if (target) {
filterPlugins(target);
}
}
}
@@ -1,104 +0,0 @@
// 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/>.
/**
* Modal for theme previews.
*
* @module core_admin/themeselector/preview_modal
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import ModalEvents from 'core/modal_events';
import ModalCancel from 'core/modal_cancel';
import ModalSaveCancel from 'core/modal_save_cancel';
import Notification from 'core/notification';
import Templates from 'core/templates';
import {getString} from 'core/str';
const SELECTORS = {
THEMES_CONTAINER: 'themelist',
PREVIEW: '[data-action="preview"]',
};
/**
* Entrypoint of the js.
*
* @method init
*/
export const init = () => {
registerListenerEvents();
};
/**
* Register theme related event listeners.
*
* @method registerListenerEvents
*/
const registerListenerEvents = () => {
document.addEventListener('click', (e) => {
const preview = e.target.closest(SELECTORS.PREVIEW);
if (preview) {
buildModal(preview).catch(Notification.exception);
}
});
};
/**
* Build the modal with the provided data.
*
* @method buildModal
* @param {object} element
*/
const buildModal = async(element) => {
// This string can be long. We will fetch it with JS as opposed to passing it as an attribute.
let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose'));
const themesContainer = document.getElementById(SELECTORS.THEMES_CONTAINER);
const definedInConfig = parseInt(themesContainer.dataset.definedinconfig);
// Prepare data for modal.
const data = {
name: element.getAttribute('data-name'),
image: element.getAttribute('data-image'),
description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags.
current: element.getAttribute('data-current'),
actionurl: element.getAttribute('data-actionurl'),
choose: element.getAttribute('data-choose'),
sesskey: element.getAttribute('data-sesskey'),
definedinconfig: definedInConfig,
};
// Determine which modal template we should use.
let modalTemplate = ModalSaveCancel;
if (data.current || data.definedinconfig) {
modalTemplate = ModalCancel;
}
const modal = await modalTemplate.create({
title: data.name,
body: Templates.render('core_admin/themeselector/theme_preview_modal', data),
large: true,
buttons: {
'save': getString('selecttheme', 'moodle'),
'cancel': getString('closebuttontitle', 'moodle'),
},
show: true,
});
modal.getRoot().on(ModalEvents.save, () => {
modal.getRoot().find('form').submit();
});
};
-4
View File
@@ -118,10 +118,6 @@ foreach ($settingspage->children as $childpage) {
}
$outputhtml .= html_writer::tag('div', '<!-- -->', array('class' => 'clearer'));
$outputhtml .= $setting->output_html($data);
if ($childpage->has_dependencies()) {
$opts = ['dependencies' => $childpage->get_dependencies_for_javascript()];
$PAGE->requires->js_call_amd('core/showhidesettings', 'init', [$opts]);
}
}
$outputhtml .= html_writer::end_tag('fieldset');
}
@@ -1,73 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_admin\admin;
use admin_setting;
/**
* Render a template as part of other admin settings.
* Use for rendering additional html in settings.
*
* @package core_admin
* @subpackage admin
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_template_render extends admin_setting {
/**
* Constructor.
*
* @param string $name The name of the setting.
* @param string $templatename The name of the template to render.
* @param array|\stdClass $context The context to pass to the template.
*/
public function __construct(
string $name,
/** @var string The name of the template to render. */
protected string $templatename,
/** @var array|\stdClass The context to pass to the template. */
protected array|\stdClass $context
) {
$this->nosave = true;
parent::__construct($name, $templatename, '', '');
}
#[\Override]
public function get_setting(): bool {
return true;
}
#[\Override]
public function get_defaultsetting(): bool {
return true;
}
#[\Override]
// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
public function write_setting($data): string {
// Do not write any setting.
return '';
}
#[\Override]
public function output_html($data, $query = ''): string {
global $OUTPUT;
return $OUTPUT->render_from_template($this->templatename, $this->context);
}
}
-1
View File
@@ -48,7 +48,6 @@ class purge_caches extends \moodleform {
$mform->addElement('header', 'purgecacheheader', get_string('purgeselectedcaches', 'admin'));
$checkboxes = [
$mform->createElement('advcheckbox', 'theme', '', get_string('purgethemecache', 'admin')),
$mform->createElement('advcheckbox', 'courses', '', get_string('purgecoursecache', 'admin')),
$mform->createElement('advcheckbox', 'lang', '', get_string('purgelangcache', 'admin')),
$mform->createElement('advcheckbox', 'js', '', get_string('purgejscache', 'admin')),
$mform->createElement('advcheckbox', 'template', '', get_string('purgetemplates', 'admin')),
+1 -1
View File
@@ -62,7 +62,7 @@ class filesize extends \admin_setting {
* @param int|null $defaultunit GB, MB, etc. (in bytes)
*/
public function __construct(string $name, string $visiblename, string $description,
?int $defaultvalue = null, ?int $defaultunit = null) {
int $defaultvalue = null, int $defaultunit = null) {
$defaultsetting = self::parse_bytes($defaultvalue);
-71
View File
@@ -1,71 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_admin\output;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Theme selector renderable.
*
* @package core_admin
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class theme_selector implements renderable, templatable {
/** @var array $themedata Theme data to pass to the template. */
private $themedata = null;
/** @var bool Whether $CFG->theme is defined in config.php. */
private $definedinconfig;
/**
* Constructor.
*
* @param array $themedata Theme data used for template.
* @param bool $definedinconfig Whether $CFG->theme is defined in config.php.
*/
public function __construct(array $themedata, bool $definedinconfig = false) {
$this->themedata = $themedata;
$this->definedinconfig = $definedinconfig;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output Renderer base.
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$data = new stdClass();
// Theme data used to populate cards and modal.
$data->themes = $this->themedata;
// Reset theme caches button.
$reseturl = new moodle_url('/admin/themeselector.php', ['sesskey' => sesskey(), 'reset' => 1]);
$resetbutton = new \single_button($reseturl, get_string('themeresetcaches', 'admin'), 'post',
\single_button::BUTTON_SECONDARY);
$data->resetbutton = $resetbutton->export_for_template($output);
$data->definedinconfig = $this->definedinconfig;
return $data;
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ class provider implements \core_privacy\local\metadata\null_provider {
*
* @return string
*/
public static function get_reason(): string {
public static function get_reason() : string {
return 'privacy:metadata';
}
}
@@ -46,14 +46,12 @@ class task_log extends base {
protected const FAILED = 1;
/**
* Database tables that this entity uses
* Database tables that this entity uses and their default aliases
*
* @return string[]
* @return array
*/
protected function get_default_tables(): array {
return [
'task_log',
];
protected function get_default_table_aliases(): array {
return ['task_log' => 'tl'];
}
/**
@@ -105,7 +103,7 @@ class task_log extends base {
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$tablealias}.classname")
->add_field("$tablealias.classname")
->set_is_sortable(true)
->add_callback(static function(string $classname): string {
$output = '';
@@ -183,7 +181,15 @@ class task_log extends base {
->set_type(column::TYPE_FLOAT)
->add_field("{$tablealias}.timeend - {$tablealias}.timestart", 'duration')
->set_is_sortable(true)
->add_callback([format::class, 'format_time'], 2);
->add_callback(static function(float $value): string {
$duration = round($value, 2);
if (empty($duration)) {
// The format_time function returns 'now' when the difference is exactly 0.
// Note: format_time performs concatenation in exactly this fashion so we should do this for consistency.
return '0 ' . get_string('secs', 'moodle');
}
return format_time($duration);
});
// Hostname column.
$columns[] = (new column(
@@ -203,8 +209,11 @@ class task_log extends base {
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_field("{$tablealias}.pid")
->set_is_sortable(true);
->set_is_sortable(true)
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
// Database column.
$columns[] = (new column(
@@ -213,14 +222,17 @@ class task_log extends base {
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_fields("{$tablealias}.dbreads, {$tablealias}.dbwrites")
->set_is_sortable(true, ["{$tablealias}.dbreads", "{$tablealias}.dbwrites"])
->add_callback(static function($value, stdClass $row): string {
->add_callback(static function(int $value, stdClass $row): string {
$output = '';
$output .= \html_writer::div(get_string('task_stats:dbreads', 'admin', $row->dbreads));
$output .= \html_writer::div(get_string('task_stats:dbwrites', 'admin', $row->dbwrites));
return $output;
});
})
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
// Database reads column.
$columns[] = (new column(
@@ -271,6 +283,8 @@ class task_log extends base {
* @return filter[]
*/
protected function get_all_filters(): array {
global $DB;
$tablealias = $this->get_table_alias('task_log');
// Name filter (Filter by classname).
@@ -328,7 +342,7 @@ class task_log extends base {
'output',
new lang_string('task_logoutput', 'admin'),
$this->get_entity_name(),
"{$tablealias}.output"
$DB->sql_cast_to_char("{$tablealias}.output")
))
->add_joins($this->get_joins());
@@ -1,108 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_admin\reportbuilder\local\filters;
use core\context\system;
use core_course_category;
use MoodleQuickForm;
use core_reportbuilder\local\filters\base;
use core_reportbuilder\local\helpers\database;
/**
* Course role report filter (by role, category, course)
*
* The provided filter field SQL must refer/return an expression for the user ID (e.g. "{$user}.id")
*
* @package core_admin
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class courserole extends base {
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$elements = [];
// Role.
$elements['role'] = $mform->createElement('select', "{$this->name}_role", get_string('rolefullname', 'core_role'),
[0 => get_string('anyrole', 'core_filters')] + get_default_enrol_roles(system::instance()));
// Category.
$elements['category'] = $mform->createElement('select', "{$this->name}_category", get_string('category'),
[0 => get_string('anycategory', 'core_filters')] + core_course_category::make_categories_list());
// Course.
$elements['course'] = $mform->createElement('text', "{$this->name}_course", get_string('shortnamecourse'));
$mform->setType("{$this->name}_course", PARAM_RAW_TRIMMED);
$mform->addGroup($elements, "{$this->name}_group", $this->get_header(), '', false)
->setHiddenLabel(true);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
[$fieldsql, $params] = $this->filter->get_field_sql_and_params();
[$contextalias, $rolealias, $coursealias] = database::generate_aliases(3);
[$roleparam, $categoryparam, $courseparam] = database::generate_param_names(3);
// Role.
$role = (int) ($values["{$this->name}_role"] ?? 0);
if ($role > 0) {
$selects[] = "{$rolealias}.roleid = :{$roleparam}";
$params[$roleparam] = $role;
}
// Category.
$category = (int) ($values["{$this->name}_category"] ?? 0);
if ($category > 0) {
$selects[] = "{$coursealias}.category = :{$categoryparam}";
$params[$categoryparam] = $category;
}
// Course.
$course = trim($values["{$this->name}_course"] ?? '');
if ($course !== '') {
$selects[] = "{$coursealias}.shortname = :{$courseparam}";
$params[$courseparam] = $course;
}
// Filter values are not set.
if (empty($selects)) {
return ['', []];
}
return ["{$fieldsql} IN (
SELECT {$rolealias}.userid
FROM {role_assignments} {$rolealias}
JOIN {context} {$contextalias} ON {$contextalias}.id = {$rolealias}.contextid AND {$contextalias}.contextlevel = 50
JOIN {course} {$coursealias} ON {$coursealias}.id = {$contextalias}.instanceid
WHERE " . implode(' AND ', $selects) . "
)", $params];
}
}
@@ -20,12 +20,10 @@ use context_system;
use core_admin\reportbuilder\local\entities\task_log;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\report\action;
use core_reportbuilder\system_report;
use html_writer;
use lang_string;
use moodle_url;
use pix_icon;
use stdClass;
use core_reportbuilder\system_report;
/**
* Task logs system report class implementation
@@ -91,9 +89,7 @@ class task_logs extends system_report {
* unique identifier
*/
public function add_columns(): void {
$entitymainalias = $this->get_entity('task_log')->get_table_alias('task_log');
$this->add_columns_from_entities([
$columns = [
'task_log:name',
'task_log:type',
'user:fullname',
@@ -103,18 +99,14 @@ class task_logs extends system_report {
'task_log:pid',
'task_log:database',
'task_log:result',
]);
];
// Wrap the task name in a link.
$this->get_column('task_log:name')
->add_field("{$entitymainalias}.id")
->add_callback(static function(string $output, stdClass $row): string {
return html_writer::link(new moodle_url('/admin/tasklogs.php', ['logid' => $row->id]), $output);
});
$this->add_columns_from_entities($columns);
// Rename the user fullname column.
$this->get_column('user:fullname')
->set_title(new lang_string('user', 'admin'));
// It's possible to override the display name of a column, if you don't want to use the value provided by the entity.
if ($column = $this->get_column('user:fullname')) {
$column->set_title(new lang_string('user', 'admin'));
}
// It's possible to set a default initial sort direction for one column.
$this->set_initial_sort_column('task_log:starttime', SORT_DESC);
@@ -127,14 +119,16 @@ class task_logs extends system_report {
* unique identifier
*/
protected function add_filters(): void {
$this->add_filters_from_entities([
$filters = [
'task_log:name',
'task_log:type',
'task_log:output',
'task_log:result',
'task_log:timestart',
'task_log:duration',
]);
];
$this->add_filters_from_entities($filters);
}
/**
@@ -149,7 +143,7 @@ class task_logs extends system_report {
new moodle_url('/admin/tasklogs.php', ['logid' => ':id']),
new pix_icon('e/search', ''),
[],
false,
true,
new lang_string('view'),
)));
@@ -1,417 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_admin\reportbuilder\local\systemreports;
use core_admin\reportbuilder\local\filters\courserole;
use core\context\system;
use core_cohort\reportbuilder\local\entities\cohort;
use core_cohort\reportbuilder\local\entities\cohort_member;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\helpers\user_profile_fields;
use core_reportbuilder\local\report\action;
use core_reportbuilder\local\report\filter;
use core_reportbuilder\system_report;
use core_role\reportbuilder\local\entities\role;
use core_user\fields;
use lang_string;
use moodle_url;
use pix_icon;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/authlib.php');
require_once($CFG->libdir.'/enrollib.php');
require_once($CFG->dirroot.'/user/lib.php');
/**
* Browse users system report class implementation
*
* @package core_admin
* @copyright 2023 David Carrillo <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class users extends system_report {
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
global $CFG;
// Our main entity, it contains all of the column definitions that we need.
$entityuser = new user();
$entityuseralias = $entityuser->get_table_alias('user');
$this->set_main_table('user', $entityuseralias);
$this->add_entity($entityuser);
// Any columns required by actions should be defined here to ensure they're always available.
$fullnamefields = array_map(fn($field) => "{$entityuseralias}.{$field}", fields::get_name_fields());
$this->add_base_fields("{$entityuseralias}.id, {$entityuseralias}.confirmed, {$entityuseralias}.mnethostid,
{$entityuseralias}.suspended, {$entityuseralias}.username, " . implode(', ', $fullnamefields));
if ($this->get_parameter('withcheckboxes', false, PARAM_BOOL)) {
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
$this->set_checkbox_toggleall(static function(\stdClass $row) use ($canviewfullnames): array {
return [$row->id, fullname($row, $canviewfullnames)];
});
}
$paramguest = database::generate_param_name();
$this->add_base_condition_sql("{$entityuseralias}.deleted <> 1 AND {$entityuseralias}.id <> :{$paramguest}",
[$paramguest => $CFG->siteguest]);
$entitycohortmember = new cohort_member();
$entitycohortmemberalias = $entitycohortmember->get_table_alias('cohort_members');
$this->add_entity($entitycohortmember
->add_joins($entitycohortmember->get_joins())
->add_join("LEFT JOIN {cohort_members} {$entitycohortmemberalias}
ON {$entityuseralias}.id = {$entitycohortmemberalias}.userid")
);
$entitycohort = new cohort();
$entitycohortalias = $entitycohort->get_table_alias('cohort');
$this->add_entity($entitycohort
->add_joins($entitycohort->get_joins())
->add_joins($entitycohortmember->get_joins())
->add_join("LEFT JOIN {cohort} {$entitycohortalias}
ON {$entitycohortalias}.id = {$entitycohortmemberalias}.cohortid")
);
// Join the role entity (Needed for the system role filter).
$roleentity = new role();
$role = $roleentity->get_table_alias('role');
$this->add_entity($roleentity
->add_join("LEFT JOIN (
SELECT DISTINCT r0.id, ras.userid
FROM {role} r0
JOIN {role_assignments} ras ON ras.roleid = r0.id
WHERE ras.contextid = ".SYSCONTEXTID."
) {$role} ON {$role}.userid = {$entityuseralias}.id")
);
// Now we can call our helper methods to add the content we want to include in the report.
$this->add_columns();
$this->add_filters();
$this->add_actions();
// Set if report can be downloaded.
$this->set_downloadable(true);
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
return has_any_capability(['moodle/user:update', 'moodle/user:delete'], system::instance());
}
/**
* Adds the columns we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
public function add_columns(): void {
$entityuser = $this->get_entity('user');
$entityuseralias = $entityuser->get_table_alias('user');
$this->add_column($entityuser->get_column('fullnamewithpicturelink'));
// Include identity field columns.
$identitycolumns = $entityuser->get_identity_columns($this->get_context());
foreach ($identitycolumns as $identitycolumn) {
$this->add_column($identitycolumn);
}
// Add "Last access" column.
$this->add_column(($entityuser->get_column('lastaccess'))
->set_callback(static function ($value, \stdClass $row): string {
if ($row->lastaccess) {
return format_time(time() - $row->lastaccess);
}
return get_string('never');
})
);
if ($column = $this->get_column('user:fullnamewithpicturelink')) {
$column
->add_fields("{$entityuseralias}.suspended, {$entityuseralias}.confirmed")
->add_callback(static function(string $fullname, \stdClass $row): string {
if ($row->suspended) {
$fullname .= ' ' . \html_writer::tag('span', get_string('suspended', 'moodle'),
['class' => 'badge text-bg-secondary ms-1']);
}
if (!$row->confirmed) {
$fullname .= ' ' . \html_writer::tag('span', get_string('confirmationpending', 'admin'),
['class' => 'badge text-bg-danger ms-1']);
}
return $fullname;
});
}
$this->set_initial_sort_column('user:fullnamewithpicturelink', SORT_ASC);
$this->set_default_no_results_notice(new lang_string('nousersfound', 'moodle'));
}
/**
* Adds the filters we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_filters(): void {
$entityuser = $this->get_entity('user');
$entityuseralias = $entityuser->get_table_alias('user');
$filters = [
'user:fullname',
'user:firstname',
'user:lastname',
'user:username',
'user:idnumber',
'user:email',
'user:department',
'user:institution',
'user:city',
'user:country',
'user:confirmed',
'user:suspended',
'user:timecreated',
'user:lastaccess',
'user:timemodified',
'user:auth',
'user:lastip',
'cohort:idnumber',
'role:name',
];
$this->add_filters_from_entities($filters);
// Enrolled in any course filter.
$ue = database::generate_alias();
[$now1, $now2] = database::generate_param_names(2);
$now = time();
$sql = "CASE WHEN ({$entityuseralias}.id IN (
SELECT userid FROM {user_enrolments} {$ue}
WHERE {$ue}.status = " . ENROL_USER_ACTIVE . "
AND ({$ue}.timestart = 0 OR {$ue}.timestart < :{$now1})
AND ({$ue}.timeend = 0 OR {$ue}.timeend > :{$now2})
)) THEN 1 ELSE 0 END";
$this->add_filter((new filter(
boolean_select::class,
'enrolledinanycourse',
new lang_string('anycourses', 'filters'),
$this->get_entity('user')->get_entity_name(),
))
->set_field_sql($sql, [
$now1 => $now,
$now2 => $now,
])
);
// Course role filter.
$this->add_filter((new filter(
courserole::class,
'courserole',
new lang_string('courserole', 'filters'),
$this->get_entity('user')->get_entity_name(),
))
->set_field_sql("{$entityuseralias}.id")
);
// Add user profile fields filters.
$userprofilefields = new user_profile_fields($entityuseralias . '.id', $entityuser->get_entity_name());
foreach ($userprofilefields->get_filters() as $filter) {
$this->add_filter($filter);
}
// Set options for system role filter.
if ($filter = $this->get_filter('role:name')) {
$filter
->set_header(new lang_string('globalrole', 'role'))
->set_options(get_assignable_roles(system::instance()));
}
}
/**
* Add the system report actions. An extra column will be appended to each row, containing all actions added here
*
* Note the use of ":id" placeholder which will be substituted according to actual values in the row
*/
protected function add_actions(): void {
global $DB, $USER;
$contextsystem = system::instance();
// Action to edit users.
$this->add_action((new action(
new moodle_url('/user/editadvanced.php', ['id' => ':id', 'course' => get_site()->id]),
new pix_icon('t/edit', ''),
[],
false,
new lang_string('edit', 'moodle'),
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
return has_capability('moodle/user:update', $contextsystem) && (is_siteadmin($USER) || !is_siteadmin($row));
}));
// Action to suspend users (non mnet remote users).
$this->add_action((new action(
new moodle_url('/admin/user.php', ['suspend' => ':id', 'sesskey' => sesskey()]),
new pix_icon('t/show', ''),
[],
false,
new lang_string('suspenduser', 'admin'),
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended && !is_mnet_remote_user($row) &&
!($row->id == $USER->id || is_siteadmin($row));
}));
// Action to unsuspend users (non mnet remote users).
$this->add_action((new action(
new moodle_url('/admin/user.php', ['unsuspend' => ':id', 'sesskey' => sesskey()]),
new pix_icon('t/hide', ''),
[],
false,
new lang_string('unsuspenduser', 'admin'),
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
return has_capability('moodle/user:update', $contextsystem) && $row->suspended && !is_mnet_remote_user($row) &&
!($row->id == $USER->id || is_siteadmin($row));
}));
// Action to unlock users (non mnet remote users).
$this->add_action((new action(
new moodle_url('/admin/user.php', ['unlock' => ':id', 'sesskey' => sesskey()]),
new pix_icon('t/unlock', ''),
[],
false,
new lang_string('unlockaccount', 'admin'),
))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
return has_capability('moodle/user:update', $contextsystem) && !is_mnet_remote_user($row) &&
login_is_lockedout($row);
}));
// Action to suspend users (mnet remote users).
$this->add_action((new action(
new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'deny']),
new pix_icon('t/show', ''),
[],
false,
new lang_string('denyaccess', 'mnet'),
))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
)) {
$accessctrl = 'allow';
}
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
is_mnet_remote_user($row) && $accessctrl == 'allow';
}));
// Action to unsuspend users (mnet remote users).
$this->add_action((new action(
new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'allow']),
new pix_icon('t/hide', ''),
[],
false,
new lang_string('allowaccess', 'mnet'),
))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
)) {
$accessctrl = 'allow';
}
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
is_mnet_remote_user($row) && $accessctrl == 'deny';
}));
// Action to delete users.
$this->add_action((new action(
new moodle_url('/admin/user.php', ['delete' => ':id', 'sesskey' => sesskey()]),
new pix_icon('t/delete', ''),
[
'class' => 'text-danger',
'data-modal' => 'confirmation',
'data-modal-title-str' => json_encode(['deleteuser', 'admin']),
'data-modal-content-str' => ':deletestr',
'data-modal-yes-button-str' => json_encode(['delete', 'core']),
'data-modal-destination' => ':deleteurl',
],
false,
new lang_string('delete', 'moodle'),
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
// Populate deletion modal attributes.
$row->deletestr = json_encode([
'deletecheckfull',
'moodle',
fullname($row, true),
]);
$row->deleteurl = (new moodle_url('/admin/user.php', [
'delete' => $row->id,
'confirm' => md5($row->id),
'sesskey' => sesskey(),
]))->out(false);
return has_capability('moodle/user:delete', $contextsystem) &&
!is_mnet_remote_user($row) && $row->id != $USER->id && !is_siteadmin($row);
}));
$this->add_action_divider();
// Action to confirm users.
$this->add_action((new action(
new moodle_url('/admin/user.php', ['confirmuser' => ':id', 'sesskey' => sesskey()]),
new pix_icon('t/check', ''),
[],
false,
new lang_string('confirmaccount', 'moodle'),
))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
return has_capability('moodle/user:update', $contextsystem) && !$row->confirmed;
}));
// Action to resend email.
$this->add_action((new action(
new moodle_url('/admin/user.php', ['resendemail' => ':id', 'sesskey' => sesskey()]),
new pix_icon('t/email', ''),
[],
false,
new lang_string('resendemail', 'moodle'),
))->add_callback(static function(\stdclass $row): bool {
return !$row->confirmed && !is_mnet_remote_user($row);
}));
}
/**
* Row class
*
* @param \stdClass $row
* @return string
*/
public function get_row_class(\stdClass $row): string {
return $row->suspended ? 'text-muted' : '';
}
}
@@ -1,85 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_admin\table;
use moodle_url;
use stdClass;
use html_writer;
use get_string_manager;
/**
* Availability admin settings.
*
* @package core_admin
* @copyright 2024 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class availability_management_table extends plugin_management_table {
protected function get_table_id(): string {
return 'availabilityconditions_administration_table';
}
protected function get_plugintype(): string {
return 'availability';
}
protected function get_action_url(array $params = []): moodle_url {
return new moodle_url('/admin/tool/availabilityconditions/', $params);
}
public function guess_base_url(): void {
$this->define_baseurl(
new moodle_url('/admin/tool/availabilityconditions/')
);
}
protected function get_column_list(): array {
return [
'name' => get_string('plugin'),
'version' => get_string('version'),
'enabled' => get_string('enabled', 'admin'),
'defaultdisplaymode' => get_string('defaultdisplaymode', 'tool_availabilityconditions'),
];
}
public function setup(): void {
$this->set_attribute('id', 'availabilityconditions_administration_table');
$this->set_attribute('class', 'admintable generaltable');
parent::setup();
}
protected function col_name(stdClass $row): string {
return html_writer::span(
get_string('pluginname', 'availability_' . $row->plugininfo->name)
);
}
protected function col_defaultdisplaymode(stdClass $row): string {
global $OUTPUT, $CFG;
$displaymode = get_config('availability_' . $row->plugininfo->name, 'defaultdisplaymode') ? 'show' : 'hide';
$paramsdisplaymode = [
'sesskey' => sesskey(),
'plugin' => $row->plugininfo->name,
'displaymode' => $displaymode,
];
$urldisplaymode = new moodle_url('/' . $CFG->admin . '/tool/availabilityconditions/', $paramsdisplaymode);
return html_writer::link($urldisplaymode, $OUTPUT->pix_icon('t/' . $displaymode,
get_string($displaymode)), ['class' => 'display-mode-' . $row->plugininfo->name]);
}
}
+8 -4
View File
@@ -91,7 +91,7 @@ class hook_list_table extends flexible_table {
*/
public function out(): void {
// All hook consumers referenced from the db/hooks.php files.
$hookmanager = \core\di::get(\core\hook\manager::class);
$hookmanager = \core\hook\manager::get_instance();
$allhooks = (array)$hookmanager->get_all_callbacks();
// Add any unused hooks.
@@ -154,7 +154,11 @@ class hook_list_table extends flexible_table {
return '';
}
$deprecates = \core\hook\manager::get_replaced_callbacks($row->classname);
$rc = new \ReflectionClass($row->classname);
if (!$rc->implementsInterface(\core\hook\deprecated_callback_replacement::class)) {
return '';
}
$deprecates = call_user_func([$row->classname, 'get_deprecated_plugin_callbacks']);
if (count($deprecates) === 0) {
return '';
}
@@ -232,7 +236,7 @@ class hook_list_table extends flexible_table {
}
if ($tooltip) {
$attributes['data-bs-toggle'] = 'tooltip';
$attributes['data-toggle'] = 'tooltip';
$attributes['title'] = $tooltip;
}
return html_writer::span($tag, "badge badge-{$type}", $attributes);
@@ -269,7 +273,7 @@ class hook_list_table extends flexible_table {
if (is_array($tag)) {
return $this->get_tag(...$tag);
}
return $this->get_tag($tag, 'badge bg-info text-white');
return $this->get_tag($tag, 'badge badge-info');
}, $tags);
return implode("\n", $taglist);
+19 -31
View File
@@ -303,29 +303,28 @@ abstract class plugin_management_table extends flexible_table implements dynamic
global $OUTPUT;
$enabled = $row->plugininfo->is_enabled();
if ($enabled) {
$labelstr = get_string('disableplugin', 'core_admin', $row->plugininfo->displayname);
} else {
$labelstr = get_string('enableplugin', 'core_admin', $row->plugininfo->displayname);
}
$params = [
'id' => 'admin-toggle-' . $row->plugininfo->name,
'checked' => $enabled,
'dataattributes' => [
'name' => 'id',
'value' => $row->plugininfo->name,
'toggle-method' => $this->get_toggle_service(),
'action' => 'togglestate',
'plugin' => $row->plugin,
'state' => $enabled ? 1 : 0,
],
'title' => $labelstr,
'label' => $labelstr,
'labelclasses' => 'visually-hidden',
'sesskey' => sesskey(),
'plugin' => $row->plugininfo->name,
'action' => $enabled ? 'disable' : 'enable',
];
return $OUTPUT->render_from_template('core_admin/setting_configtoggle', $params);
if ($enabled) {
$icon = $OUTPUT->pix_icon('t/hide', get_string('disableplugin', 'core_admin', $row->plugininfo->displayname));
} else {
$icon = $OUTPUT->pix_icon('t/show', get_string('enableplugin', 'core_admin', $row->plugininfo->displayname));
}
return html_writer::link(
$this->get_action_url($params),
$icon,
[
'data-toggle-method' => $this->get_toggle_service(),
'data-action' => 'togglestate',
'data-plugin' => $row->plugin,
'data-state' => $enabled ? 1 : 0,
],
);
}
protected function col_order(stdClass $row): string {
@@ -501,15 +500,4 @@ abstract class plugin_management_table extends flexible_table implements dynamic
protected function supports_ordering(): bool {
return $this->plugininfoclass::plugintype_supports_ordering();
}
/**
* Check if the user has the capability to access this table.
*
* Default implementation for plugin management tables is to require 'moodle/site:config' capability
*
* @return bool Return true if capability check passed.
*/
public function has_capability(): bool {
return has_capability('moodle/site:config', $this->get_context());
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ Example:
exit(0);
}
error_reporting(E_ALL);
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
$content = core_component::get_cache_content();
+7 -23
View File
@@ -33,7 +33,6 @@ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
list($options, $unrecognized) = cli_get_params(array(
'courseid' => false,
'courseshortname' => '',
'cmid' => false,
'destination' => '',
'help' => false,
), array('h' => 'help'));
@@ -43,14 +42,13 @@ if ($unrecognized) {
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
if ($options['help'] || !($options['courseid'] || $options['courseshortname'] || $options['cmid'])) {
if ($options['help'] || !($options['courseid'] || $options['courseshortname'])) {
$help = <<<EOL
Perform backup of the given course or course module.
Perform backup of the given course.
Options:
--courseid=INTEGER Course ID for backup.
--courseshortname=STRING Course shortname for backup. This option is ignored if courseid is set.
--cmid=INTEGER Course module ID for backup. This option is ignored if courseid or courseshortname is set.
--courseshortname=STRING Course shortname for backup.
--destination=STRING Path where to store backup file. If not set the backup
will be stored within the course backup file area.
-h, --help Print out this help.
@@ -78,26 +76,16 @@ if (!empty($dir)) {
}
}
// Check that the course or course module exists.
// Check that the course exists.
if ($options['courseid']) {
$course = $DB->get_record('course', array('id' => $options['courseid']), '*', MUST_EXIST);
} else if ($options['courseshortname']) {
$course = $DB->get_record('course', array('shortname' => $options['courseshortname']), '*', MUST_EXIST);
} else if ($options['cmid']) {
$cm = $DB->get_record('course_modules', ['id' => $options['cmid']], '*', MUST_EXIST);
}
cli_heading('Performing backup...');
if (!empty($course)) {
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
} else if (!empty($cm)) {
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
} else {
throw new \moodle_exception('invalidoption');
}
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
// Set the default filename.
$format = $bc->get_format();
$type = $bc->get_type();
@@ -125,11 +113,7 @@ if (!empty($dir)) {
}
}
} else {
if (!empty($course)) {
mtrace("Backup completed, the new file is listed in the backup area of the given course");
} else {
mtrace("Backup completed, the new file is listed in the backup area of the given module");
}
mtrace("Backup completed, the new file is listed in the backup area of the given course");
}
$bc->destroy();
exit(0);
-132
View File
@@ -1,132 +0,0 @@
<?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/>.
/**
* This script allows to import a course from CLI.
*
* @package core
* @subpackage cli
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', 1);
require(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/clilib.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . "/backup/util/includes/restore_includes.php");
list($options, $unrecognized) = cli_get_params([
'srccourseid' => '',
'srccmid' => '',
'dstcourseid' => '',
'showdebugging' => false,
'help' => false,
], [
's' => 'showdebugging',
'h' => 'help',
]);
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
if ($options['help'] ||
!(($options['srccourseid'] && $options['dstcourseid'])
|| ($options['srccmid'] && $options['dstcourseid']))) {
$help = <<<EOL
Import course or course module into provided course.
Options:
--srccourseid=INT Source course ID to backup.
--srccmid=INT Source course module ID to backup.
--dstcourseid=INT Destination course ID to restore.
-s, --showdebugging Show developer level debugging information
-h, --help Print out this help.
Example1: (Import from a course into another course)
\$sudo -u www-data /usr/bin/php admin/cli/import.php --srccourseid=12 --dstcourseid=13\n
Example2: (Import from a course module into a course)
\$sudo -u www-data /usr/bin/php admin/cli/import.php --srccmid=21 --dstcourseid=11\n
EOL;
echo $help;
exit(0);
}
if ($options['showdebugging']) {
set_debugging(DEBUG_DEVELOPER, true);
}
if (!$admin = get_admin()) {
throw new \moodle_exception('noadmins');
}
if (!empty($options['srccourseid']) && !empty($options['dstcourseid'])) {
// Import from course into course.
if (!$srccourse = $DB->get_record('course', ['id' => $options['srccourseid']], 'id')) {
throw new \moodle_exception('invalidcourseid');
}
if (!$dstcourse = $DB->get_record('course', ['id' => $options['dstcourseid']], 'id')) {
throw new \moodle_exception('invalidcourseid');
}
cli_heading(get_string('importfromcoursetocourse', 'backup', (object)$options));
$bc = new backup_controller(backup::TYPE_1COURSE, $srccourse->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id);
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
cli_heading(get_string('backupthenrestore', 'backup'));
$rc = new restore_controller($backupid, $dstcourse->id, backup::INTERACTIVE_NO,
backup::MODE_SAMESITE, $admin->id, backup::TARGET_EXISTING_ADDING);
$rc->execute_precheck();
$rc->execute_plan();
$rc->destroy();
cli_heading(get_string('restoredcourseid', 'backup', $dstcourse->id));
} else if (!empty($options['srccmid']) && !empty($options['dstcourseid'])) {
// Import from course module into course.
if (!$cm = $DB->get_record('course_modules', ['id' => $options['srccmid']], 'id')) {
throw new \moodle_exception('invalidcoursemoduleid', 'error', '', $options['srccmid']);
}
if (!$course = $DB->get_record('course', ['id' => $options['dstcourseid']], 'id')) {
throw new \moodle_exception('invalidcourseid');
}
cli_heading(get_string('importfromccmidtocourse', 'backup', (object)$options));
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id);
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
cli_heading(get_string('backupthenrestore', 'backup'));
$rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO,
backup::MODE_SAMESITE, $admin->id, backup::TARGET_EXISTING_ADDING);
$rc->execute_precheck();
$rc->execute_plan();
$rc->destroy();
cli_heading(get_string('restoredcourseid', 'backup', $course->id));
} else {
throw new \moodle_exception('invalidoption');
}
exit(0);
+14 -9
View File
@@ -30,7 +30,6 @@
*/
define('CLI_SCRIPT', true);
define('NO_DEBUG_DISPLAY', false);
// extra execution prevention - we can not just require config.php here
if (isset($_SERVER['REMOTE_ADDR'])) {
@@ -166,23 +165,18 @@ $CFG->running_installer = true;
$CFG->early_install_lang = true;
$CFG->ostype = (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) ? 'WINDOWS' : 'UNIX';
$CFG->dboptions = array();
$CFG->debug = (E_ALL);
$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = true;
$CFG->debugdeveloper = true;
$parts = explode('/', str_replace('\\', '/', dirname(__DIR__)));
$CFG->admin = array_pop($parts);
// Point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
// the problem is that we need specific version of quickforms and hacked excel files :-(.
//point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
//the problem is that we need specific version of quickforms and hacked excel files :-(
ini_set('include_path', $CFG->libdir.'/pear' . PATH_SEPARATOR . ini_get('include_path'));
// The core_component class can be used in any scripts, it does not need anything else.
require_once($CFG->libdir.'/classes/component.php');
// Register our classloader.
\core\component::register_autoloader();
require_once($CFG->libdir.'/classes/text.php');
require_once($CFG->libdir.'/classes/string_manager.php');
require_once($CFG->libdir.'/classes/string_manager_install.php');
@@ -196,6 +190,15 @@ require_once($CFG->libdir.'/moodlelib.php');
require_once($CFG->libdir.'/deprecatedlib.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/componentlib.class.php');
require_once($CFG->dirroot.'/cache/lib.php');
// Register our classloader, in theory somebody might want to replace it to load other hacked core classes.
// Required because the database checks below lead to session interaction which is going to lead us to requiring autoloaded classes.
if (defined('COMPONENT_CLASSLOADER')) {
spl_autoload_register(COMPONENT_CLASSLOADER);
} else {
spl_autoload_register('core_component::classloader');
}
require($CFG->dirroot.'/version.php');
$CFG->target_release = $release;
@@ -217,6 +220,7 @@ $databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'n
'auroramysql' => moodle_database::get_driver_instance('auroramysql', 'native'),
'mariadb'=> moodle_database::get_driver_instance('mariadb', 'native'),
'pgsql' => moodle_database::get_driver_instance('pgsql', 'native'),
'oci' => moodle_database::get_driver_instance('oci', 'native'),
'sqlsrv' => moodle_database::get_driver_instance('sqlsrv', 'native'), // MS SQL*Server PHP driver
);
foreach ($databases as $type=>$database) {
@@ -560,6 +564,7 @@ do {
if ($interactive) {
cli_separator();
cli_heading(get_string('dbprefix', 'install'));
//TODO: solve somehow the prefix trouble for oci.
if ($options['prefix'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['prefix']);
} else {
+1 -1
View File
@@ -50,6 +50,6 @@ Example:
exit(0);
}
\core\session\manager::destroy_all();
\core\session\manager::kill_all_sessions();
exit(0);
-4
View File
@@ -24,7 +24,6 @@
*/
define('CLI_SCRIPT', true);
define('IGNORE_COMPONENT_CACHE', true);
require(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/clilib.php');
@@ -32,7 +31,6 @@ require_once($CFG->libdir.'/clilib.php');
$longoptions = [
'help' => false,
'muc' => false,
'courses' => false,
'theme' => false,
'lang' => false,
'js' => false,
@@ -57,8 +55,6 @@ all caches will be purged.
Options:
-h, --help Print out this help
--muc Purge all MUC caches (includes lang cache)
--courses Purge all course caches (or only those specified by a comma-separated list).
e.g. --courses=4,67,145
--theme Purge theme cache
--lang Purge language string cache
--js Purge JavaScript cache
+15 -66
View File
@@ -32,13 +32,11 @@ require_once($CFG->dirroot . "/backup/util/includes/restore_includes.php");
list($options, $unrecognized) = cli_get_params([
'file' => '',
'categoryid' => '',
'courseid' => '',
'showdebugging' => false,
'help' => false,
], [
'f' => 'file',
'c' => 'categoryid',
'C' => 'courseid',
's' => 'showdebugging',
'h' => 'help',
]);
@@ -48,21 +46,18 @@ if ($unrecognized) {
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
if ($options['help'] || !($options['file']) || !($options['categoryid'] || $options['courseid'])) {
if ($options['help'] || !($options['file']) || !($options['categoryid'])) {
$help = <<<EOL
Restore backup into provided category or course.
If courseid is set, course module/s will be added into the course.
Restore backup into provided category.
Options:
-f, --file=STRING Path to the backup file.
-c, --categoryid=INT ID of the course category to restore to. This option is ignored when restoring an activity and courseid is set.
-C, --courseid=INT ID of the course to restore to. This option is ignored when restoring a course and the categoryid is set.
-s, --showdebugging Show developer level debugging information
-h, --help Print out this help.
-f, --file=STRING Path to the backup file.
-c, --categoryid=INT ID of the category to restore too.
-s, --showdebugging Show developer level debugging information
-h, --help Print out this help.
Example:
\$sudo -u www-data /usr/bin/php admin/cli/restore_backup.php --file=/path/to/backup/coursebackup.mbz --categoryid=1\n
\$sudo -u www-data /usr/bin/php admin/cli/restore_backup.php --file=/path/to/backup/activitybackup.mbz --courseid=1\n
\$sudo -u www-data /usr/bin/php admin/cli/restore_backup.php --file=/path/to/backup/file.mbz --categoryid=1\n
EOL;
echo $help;
@@ -81,20 +76,8 @@ if (!file_exists($options['file'])) {
throw new \moodle_exception('filenotfound');
}
if ($options['categoryid']) {
if (!$category = $DB->get_record('course_categories', ['id' => $options['categoryid']], 'id')) {
throw new \moodle_exception('invalidcategoryid');
}
}
if ($options['courseid']) {
if (!$course = $DB->get_record('course', ['id' => $options['courseid']], 'id')) {
throw new \moodle_exception('invalidcourseid');
}
}
if (empty($category) && empty($course)) {
throw new \moodle_exception('invalidoption');
if (!$category = $DB->get_record('course_categories', ['id' => $options['categoryid']], 'id')) {
throw new \moodle_exception('invalidcategoryid');
}
$backupdir = restore_controller::get_tempdir_name(SITEID, $USER->id);
@@ -105,51 +88,17 @@ $fp = get_file_packer('application/vnd.moodle.backup');
$fp->extract_to_pathname($options['file'], $path);
cli_heading(get_string('preprocessingbackupfile'));
try {
// Create a temporary restore controller to determine the restore type.
$tmprc = new restore_controller($backupdir, SITEID, backup::INTERACTIVE_NO,
backup::MODE_GENERAL, $admin->id, backup::TARGET_EXISTING_ADDING);
// Restore the backup into a new course if:
// - It is a course backup and the category is set.
// - It is an activity backup and the course is not set.
$restoreasnewcourse = ($tmprc->get_type() === backup::TYPE_1COURSE && !empty($category)) ||
($tmprc->get_type() !== backup::TYPE_1COURSE && empty($course));
// Make sure to clean up the temporary restore controller.
$tmprc->destroy();
list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'),
get_string('restoringcourseshortname', 'backup'));
if ($restoreasnewcourse) {
list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'),
get_string('restoringcourseshortname', 'backup'));
$courseid = restore_dbops::create_new_course($fullname, $shortname, $category->id);
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
backup::MODE_GENERAL, $admin->id, backup::TARGET_NEW_COURSE);
} else {
$courseid = $course->id;
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
backup::MODE_GENERAL, $admin->id, backup::TARGET_EXISTING_ADDING);
}
$courseid = restore_dbops::create_new_course($fullname, $shortname, $category->id);
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
backup::MODE_GENERAL, $admin->id, backup::TARGET_NEW_COURSE);
$rc->execute_precheck();
$rc->execute_plan();
$rc->destroy();
// Rename the course's full and short names with the backup file's original names if the backup file is an activity backup
// that is restored to a new course.
if ($restoreasnewcourse && $rc->get_type() !== backup::TYPE_1COURSE) {
$course = get_course($courseid);
$backupinfo = $rc->get_info();
$tmpfullname = $backupinfo->original_course_fullname ?? get_string('restoretonewcourse', 'backup');
$tmpshortname = $backupinfo->original_course_shortname ?? get_string('newcourse');
list($fullname, $shortname) = restore_dbops::calculate_course_names(
courseid: 0,
fullname: $tmpfullname,
shortname: $tmpshortname,
);
$course->fullname = $fullname;
$course->shortname = $shortname;
$course->visible = 1;
$DB->update_record('course', $course);
}
} catch (Exception $e) {
cli_heading(get_string('cleaningtempdata'));
fulldelete($path);
+5 -1
View File
@@ -189,7 +189,11 @@ if ($disable = $options['disable']) {
}
$task->set_lock($lock);
$cronlock->release();
if (!$task->is_blocking()) {
$cronlock->release();
} else {
$task->set_cron_lock($cronlock);
}
\core\cron::run_inner_scheduled_task($task);
}
-604
View File
@@ -3923,8 +3923,6 @@
</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">
@@ -3941,7 +3939,6 @@
<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>
@@ -4115,8 +4112,6 @@
</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">
@@ -4133,7 +4128,6 @@
<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>
@@ -4309,8 +4303,6 @@
</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">
@@ -4327,7 +4319,6 @@
<VENDOR name="oracle" version="19" />
</DATABASE>
<PHP version="8.1.0" level="required">
<RESTRICT function="restrict_php_version_84" message="unsupportedphpversion84" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
@@ -4503,601 +4494,6 @@
</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">
<RESTRICT function="restrict_php_version_84" message="unsupportedphpversion84" />
</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>
<MOODLE version="5.0" requires="4.2.3">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="10.11.0" />
<VENDOR name="mysql" version="8.4" />
<VENDOR name="postgres" version="14" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="auroramysql" version="8.0" />
</DATABASE>
<PHP version="8.2.0" level="required">
</PHP>
<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_async_backup" level="recommended">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_aurora_version" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="5.1" requires="4.2.3">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="10.11.0" />
<VENDOR name="mysql" version="8.4" />
<VENDOR name="postgres" version="15" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="auroramysql" version="8.0" />
</DATABASE>
<PHP version="8.2.0" level="required">
</PHP>
<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_SETTING name="zend.exception_ignore_args" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="settingzendexceptionignoreargs" />
</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_async_backup" level="recommended">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_aurora_version" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+1 -1
View File
@@ -30,7 +30,7 @@ require_once($CFG->libdir . '/tablelib.php');
admin_externalpage_setup('hooksoverview');
require_capability('moodle/site:config', \core\context\system::instance());
$hookmanager = \core\di::get(\core\hook\manager::class);
$hookmanager = \core\hook\manager::get_instance();
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('hooksoverview', 'core_admin'));
+7
View File
@@ -40,6 +40,13 @@ if (!function_exists('iconv')) {
die();
}
// Make sure php5-json is available.
if (!function_exists('json_encode') || !function_exists('json_decode')) {
// This also shouldn't happen.
echo 'Moodle requires the json PHP extension. Please install or enable the json extension.';
die();
}
// Make sure xml extension is available.
if (!extension_loaded('xml')) {
echo 'Moodle requires the xml PHP extension. Please install or enable the xml extension.';
+69 -1
View File
@@ -43,6 +43,61 @@ $providers = get_message_providers();
$preferences = get_message_output_default_preferences();
if (($form = data_submitted()) && confirm_sesskey()) {
$newpreferences = array();
// Prepare default message outputs settings.
foreach ($providers as $provider) {
$componentproviderbase = $provider->component.'_'.$provider->name;
$disableprovidersetting = $componentproviderbase.'_disable';
if (!isset($form->$disableprovidersetting)) {
$newpreferences[$disableprovidersetting] = 1;
} else {
$newpreferences[$disableprovidersetting] = 0;
}
$componentprovidersetting = $componentproviderbase.'_locked';
foreach ($processors as $processor) {
$value = 0;
if (isset($form->{$componentprovidersetting}[$processor->name])) {
$value = $form->{$componentprovidersetting}[$processor->name];
if ($value == 'on') {
$value = 1;
}
}
// Record the site preference.
$newpreferences[$processor->name.'_provider_'.$componentprovidersetting] = $value;
}
$componentprovidersetting = $componentproviderbase.'_enabled';
$newsettings = [];
if (isset($form->$componentprovidersetting)) {
// Store defined comma-separated processors as setting value.
// Using array_filter eliminates elements set to 0 above.
$newsettings = array_keys(array_filter($form->{$componentprovidersetting}));
}
// Let's join existing setting values for disabled processors.
$property = 'message_provider_'.$componentprovidersetting;
if (property_exists($preferences, $property)) {
$existingsetting = $preferences->$property;
foreach ($disabledprocessors as $disable) {
if (strpos($existingsetting, $disable->name) > -1) {
$newsettings[] = $disable->name;
}
}
}
$value = join(',', $newsettings);
if (empty($value)) {
$value = null;
}
// Record the site preference.
$newpreferences['message_provider_'.$componentprovidersetting] = $value;
}
// Update database.
$transaction = $DB->start_delegated_transaction();
// Save processors enabled/disabled status.
foreach ($allprocessors as $processor) {
@@ -51,11 +106,24 @@ if (($form = data_submitted()) && confirm_sesskey()) {
$class::enable_plugin($processor->name, $enabled);
}
foreach ($newpreferences as $name => $value) {
$old = isset($preferences->$name) ? $preferences->$name : '';
if ($old != $value) {
add_to_config_log($name, $old, $value, 'core');
}
set_config($name, $value, 'message');
}
$transaction->allow_commit();
core_plugin_manager::reset_caches();
$url = new moodle_url('message.php');
redirect($url);
}
// Page settings.
// Page settings
$PAGE->set_context(context_system::instance());
$renderer = $PAGE->get_renderer('core', 'message');
+1 -2
View File
@@ -91,8 +91,7 @@ if (!empty($hostid) && array_key_exists($hostid, $hosts)) {
// this query is horrible and has to be remapped afterwards, because of the non-uniqueness
// of the remoterep service (it has two plugins so far that use it)
// it's possible to get a unique list back using a subquery with LIMIT but that would break oracle
// so it's best to just do this small query and then remap the results afterwards.
// TODO: Optimise the query, as Oracle-specific constraints no longer apply.
// so it's best to just do this small query and then remap the results afterwards
$sql = '
SELECT DISTINCT
' . $DB->sql_concat('r.plugintype', "'_'", 'r.pluginname', "'_'", 's.name') . ' AS uniqueid,
+7 -4
View File
@@ -29,6 +29,8 @@ require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->libdir . '/filelib.php');
$fetchupdates = optional_param('fetchupdates', false, PARAM_BOOL); // Check for available plugins updates.
$updatesonly = optional_param('updatesonly', false, PARAM_BOOL); // Show updateable plugins only.
$contribonly = optional_param('contribonly', false, PARAM_BOOL); // Show additional plugins only.
$uninstall = optional_param('uninstall', '', PARAM_COMPONENT); // Uninstall the plugin.
$delete = optional_param('delete', '', PARAM_COMPONENT); // Delete the plugin folder after it is uninstalled.
$confirmed = optional_param('confirm', false, PARAM_BOOL); // Confirm the uninstall/delete action.
@@ -45,7 +47,8 @@ require_admin();
$syscontext = context_system::instance();
// URL params we want to maintain on redirects.
$pageurl = new moodle_url('/admin/plugins.php');
$pageparams = array('updatesonly' => $updatesonly, 'contribonly' => $contribonly);
$pageurl = new moodle_url('/admin/plugins.php', $pageparams);
$pluginman = core_plugin_manager::instance();
@@ -54,7 +57,7 @@ $PAGE->set_primary_active_tab('siteadminnode');
if ($uninstall) {
if (!$confirmed) {
admin_externalpage_setup('pluginsoverview');
admin_externalpage_setup('pluginsoverview', '', $pageparams);
} else {
$PAGE->set_url($pageurl);
$PAGE->set_context($syscontext);
@@ -198,7 +201,7 @@ if ($installupdate and $installupdateversion) {
}
}
admin_externalpage_setup('pluginsoverview');
admin_externalpage_setup('pluginsoverview', '', $pageparams);
/** @var core_admin_renderer $output */
$output = $PAGE->get_renderer('core', 'admin');
@@ -211,4 +214,4 @@ if ($fetchupdates) {
redirect($PAGE->url);
}
echo $output->plugin_management_page($pluginman, $checker);
echo $output->plugin_management_page($pluginman, $checker, $pageparams);
-12
View File
@@ -1,12 +0,0 @@
# core_adminpresets (subsystem) Upgrade notes
## 5.0
### Removed
- Remove chat and survey from Adminpresets.
For more information see [MDL-82457](https://tracker.moodle.org/browse/MDL-82457)
- Removed block_mnet_hosts from admin presets
For more information see [MDL-84309](https://tracker.moodle.org/browse/MDL-84309)
+11 -5
View File
@@ -193,12 +193,14 @@ class helper {
// Set Activity chooser tabs to "Starred, Recommended, All".
static::add_item($presetid, 'activitychoosertabmode', '4');
// Modules: Hide database, external tool (lti), IMS content package (imscp), lesson, SCORM, wiki, workshop.
// Modules: Hide chat, database, external tool (lti), IMS content package (imscp), lesson, SCORM, survey, wiki, workshop.
static::add_plugin($presetid, 'mod', 'chat', false);
static::add_plugin($presetid, 'mod', 'data', false);
static::add_plugin($presetid, 'mod', 'lti', false);
static::add_plugin($presetid, 'mod', 'imscp', false);
static::add_plugin($presetid, 'mod', 'lesson', false);
static::add_plugin($presetid, 'mod', 'scorm', false);
static::add_plugin($presetid, 'mod', 'survey', false);
static::add_plugin($presetid, 'mod', 'wiki', false);
static::add_plugin($presetid, 'mod', 'workshop', false);
@@ -207,7 +209,7 @@ class helper {
static::add_plugin($presetid, 'availability', 'profile', false);
// Blocks: Hide Activities, Blog menu, Blog tags, Comments, Course completion status, Courses, Flickr,
// Global search, Latest badges, Learning plans, Logged in user, Login, Main menu, Mentees, Online users,
// Global search, Latest badges, Learning plans, Logged in user, Login, Main menu, Mentees, Network servers, Online users,
// Private files, Recent blog entries, Recently accessed courses, Search forums, Section links, Social activities,
// Starred courses, Tags, YouTube.
// Hidden by default: Course/site summary, RSS feeds, Self completion, Feedback.
@@ -226,6 +228,7 @@ class helper {
static::add_plugin($presetid, 'block', 'login', false);
static::add_plugin($presetid, 'block', 'site_main_menu', false);
static::add_plugin($presetid, 'block', 'mentees', false);
static::add_plugin($presetid, 'block', 'mnet_hosts', false);
static::add_plugin($presetid, 'block', 'private_files', false);
static::add_plugin($presetid, 'block', 'blog_recent', false);
static::add_plugin($presetid, 'block', 'rss_client', false);
@@ -289,7 +292,7 @@ class helper {
static::add_item($presetid, 'enablenotes', '1');
static::add_item($presetid, 'enableblogs', '1');
static::add_item($presetid, 'enablebadges', '1');
static::add_item($presetid, 'enableanalytics', '0');
static::add_item($presetid, 'enableanalytics', '1');
static::add_item($presetid, 'enabled', '1', 'core_competency');
static::add_item($presetid, 'pushcourseratingstouserplans', '1', 'core_competency');
static::add_item($presetid, 'showdataretentionsummary', '1', 'tool_dataprivacy');
@@ -298,12 +301,14 @@ class helper {
// Set Activity chooser tabs to the default value ("Starred, Recommended, All, Activities, Resources").
static::add_item($presetid, 'activitychoosertabmode', '3');
// Modules: Enable database, external tool (lti), IMS content package (imscp), lesson, SCORM, wiki, workshop.
// Modules: Enable chat, database, external tool (lti), IMS content package (imscp), lesson, SCORM, survey, wiki, workshop.
static::add_plugin($presetid, 'mod', 'chat', true);
static::add_plugin($presetid, 'mod', 'data', true);
static::add_plugin($presetid, 'mod', 'lti', true);
static::add_plugin($presetid, 'mod', 'imscp', true);
static::add_plugin($presetid, 'mod', 'lesson', true);
static::add_plugin($presetid, 'mod', 'scorm', true);
static::add_plugin($presetid, 'mod', 'survey', true);
static::add_plugin($presetid, 'mod', 'wiki', true);
static::add_plugin($presetid, 'mod', 'workshop', true);
@@ -312,7 +317,7 @@ class helper {
static::add_plugin($presetid, 'availability', 'profile', true);
// Blocks: Enable Activities, Blog menu, Blog tags, Comments, Course completion status, Courses, Flickr,
// Global search, Latest badges, Learning plans, Logged in user, Login, Main menu, Mentees, Online users,
// Global search, Latest badges, Learning plans, Logged in user, Login, Main menu, Mentees, Network servers, Online users,
// Private files, Recent blog entries, Recently accessed courses, Search forums, Section links, Social activities,
// Starred courses, Tags, YouTube.
// Hidden by default: Course/site summary, RSS feeds, Self completion, Feedback.
@@ -330,6 +335,7 @@ class helper {
static::add_plugin($presetid, 'block', 'login', true);
static::add_plugin($presetid, 'block', 'site_main_menu', true);
static::add_plugin($presetid, 'block', 'mentees', true);
static::add_plugin($presetid, 'block', 'mnet_hosts', true);
static::add_plugin($presetid, 'block', 'private_files', true);
static::add_plugin($presetid, 'block', 'blog_recent', true);
static::add_plugin($presetid, 'block', 'search_forums', true);
+2 -7
View File
@@ -761,8 +761,8 @@ class manager {
public function delete_preset(int $presetid): void {
global $DB;
// Check the preset exists (cannot delete the pre-installed core "Starter" and "Full" presets).
$preset = $DB->get_record('adminpresets', ['id' => $presetid, 'iscore' => self::NONCORE_PRESET]);
// Check the preset exists.
$preset = $DB->get_record('adminpresets', ['id' => $presetid]);
if (!$preset) {
throw new moodle_exception('errordeleting', 'core_adminpresets');
}
@@ -1133,11 +1133,6 @@ class manager {
'visiblevalue' => $visiblevalue,
];
if ($plugininfo && $plugininfo->is_deprecated()) {
$skipped[] = $data;
continue;
}
if ($pluginclass == '\core\plugininfo\orphaned') {
$skipped[] = $data;
continue;
+2 -2
View File
@@ -41,7 +41,7 @@ class provider implements
* @param collection $collection A list of information about this component
* @return collection The collection object filled out with information about this component.
*/
public static function get_metadata(collection $collection): collection {
public static function get_metadata(collection $collection) : collection {
// These tables are really data about site configuration and not user data.
// The adminpresets includes information about which user performed a configuration change using the admin_presets
@@ -74,7 +74,7 @@ class provider implements
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
public static function get_contexts_for_userid(int $userid) : contextlist {
return new contextlist();
}
+2 -2
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<PRESET>
<NAME>Imported preset</NAME>
<COMMENTS>&lt;p dir="ltr" style="text-align:left;"&gt;Porfolios enabled, Emojipicker enabled, Lesson media width set to 900 and maxanswers set to 2 and disabled advanced. Plugins: disabled block_html, mod_database and mod_page plugins and enabled atto_html, block_activity_modules and mod_lesson.&lt;/p&gt;</COMMENTS>
<COMMENTS>&lt;p dir="ltr" style="text-align:left;"&gt;Porfolios enabled, Emojipicker enabled, Lesson media width set to 900 and maxanswers set to 2 and disabled advanced. Plugins: disabled block_html, mod_database and mod_chat plugins and enabled atto_html, block_activity_modules and mod_lesson.&lt;/p&gt;</COMMENTS>
<PRESET_DATE>1631615985</PRESET_DATE>
<SITE_URL>http://demo.moodle</SITE_URL>
<AUTHOR>Ada Lovelace</AUTHOR>
@@ -30,7 +30,7 @@
<ACTIVITY_MODULES>1</ACTIVITY_MODULES>
</BLOCK>
<MOD>
<PAGE>0</PAGE>
<CHAT>0</CHAT>
<DATA>0</DATA>
<LESSON>1</LESSON>
</MOD>
+2 -2
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<PRESET>
<NAME>Starter</NAME>
<COMMENTS>&lt;p dir="ltr" style="text-align:left;"&gt;Badges disabled, Emojipicker enabled, Lesson media width set to 900 and maxanswers set to 2 and disabled advanced. Plugins: disabled block_html, mod_database and mod_page plugins and enabled atto_html and block_activity_modules.&lt;/p&gt;</COMMENTS>
<COMMENTS>&lt;p dir="ltr" style="text-align:left;"&gt;Badges disabled, Emojipicker enabled, Lesson media width set to 900 and maxanswers set to 2 and disabled advanced. Plugins: disabled block_html, mod_database and mod_chat plugins and enabled atto_html and block_activity_modules.&lt;/p&gt;</COMMENTS>
<PRESET_DATE>1631615985</PRESET_DATE>
<SITE_URL>http://demo.moodle</SITE_URL>
<AUTHOR>Ada Lovelace</AUTHOR>
@@ -30,7 +30,7 @@
<ACTIVITY_MODULES>1</ACTIVITY_MODULES>
</BLOCK>
<MOD>
<PAGE>0</PAGE>
<CHAT>0</CHAT>
<DATA>0</DATA>
<LESSON>1</LESSON>
</MOD>
+2
View File
@@ -227,6 +227,7 @@ class core_adminpresets_generator extends \component_generator_base {
public function access_protected($object, string $property) {
$reflection = new ReflectionClass($object);
$property = $reflection->getProperty($property);
$property->setAccessible(true);
return $property->getValue($object);
}
@@ -243,6 +244,7 @@ class core_adminpresets_generator extends \component_generator_base {
// Set method accessibility.
$method = new ReflectionMethod(manager::class, 'get_setting');
$method->setAccessible(true);
// Get the proper adminpresets_setting instance.
$settingpage = $adminroot->locate($category);
+2 -2
View File
@@ -25,7 +25,7 @@ namespace core_adminpresets;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass core_adminpresets_generator
*/
final class generator_test extends \advanced_testcase {
class generator_test extends \advanced_testcase {
/**
* Test the behaviour of create_preset() method.
@@ -180,7 +180,7 @@ final class generator_test extends \advanced_testcase {
*
* @return array
*/
public static function create_preset_provider(): array {
public function create_preset_provider(): array {
return [
'Default values' => [
],
+12 -15
View File
@@ -23,9 +23,9 @@ namespace core_adminpresets;
* @category test
* @copyright 2021 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_adminpresets\helper
* @coversDefaultClass helper
*/
final class helper_test extends \advanced_testcase {
class helper_test extends \advanced_testcase {
/**
* Test the behaviour of create_preset() method.
@@ -84,7 +84,7 @@ final class helper_test extends \advanced_testcase {
*
* @return array
*/
public static function create_preset_provider(): array {
public function create_preset_provider(): array {
return [
'Default values' => [
],
@@ -177,7 +177,7 @@ final class helper_test extends \advanced_testcase {
*
* @return array
*/
public static function add_item_provider(): array {
public function add_item_provider(): array {
return [
'Setting without plugin' => [
'name' => 'settingname',
@@ -239,7 +239,7 @@ final class helper_test extends \advanced_testcase {
*
* @return array
*/
public static function add_plugin_provider(): array {
public function add_plugin_provider(): array {
return [
'Plugin: enabled (using int)' => [
'type' => 'plugintype',
@@ -285,15 +285,12 @@ final class helper_test extends \advanced_testcase {
// We need to change some of the default values; otherwise, the full preset won't be applied, because all the settings
// and plugins are the same.
set_config('enableanalytics', '1');
set_config('enableanalytics', '0');
$generator = $this->getDataGenerator()->get_plugin_generator('core_adminpresets');
$generator->create_preset(['name' => 'Preset 1']);
$invokable = self::get_invokable();
set_error_handler($invokable, E_WARNING);
$presetid = helper::change_default_preset($preset);
restore_error_handler();
if (empty($settings) && empty($plugins)) {
// The preset hasn't been applied.
@@ -324,7 +321,7 @@ final class helper_test extends \advanced_testcase {
*
* @return array
*/
public static function change_default_preset_provider(): array {
public function change_default_preset_provider(): array {
return [
'Starter preset' => [
'preset' => 'starter',
@@ -334,7 +331,7 @@ final class helper_test extends \advanced_testcase {
],
'plugins' => [
'assign' => 1,
'wiki' => 0,
'chat' => 0,
'data' => 0,
'lesson' => 0,
],
@@ -347,7 +344,7 @@ final class helper_test extends \advanced_testcase {
],
'plugins' => [
'assign' => 1,
'book' => 1,
'chat' => 1,
'data' => 1,
'lesson' => 1,
],
@@ -367,20 +364,20 @@ final class helper_test extends \advanced_testcase {
'preset' => 'unexisting',
],
'Valid XML file' => [
'preset' => self::get_fixture_path(__NAMESPACE__, 'import_settings_plugins.xml'),
'preset' => __DIR__ . '/fixtures/import_settings_plugins.xml',
'settings' => [
'allowemojipicker' => 1,
'enableportfolios' => 1,
],
'plugins' => [
'assign' => 1,
'page' => 0,
'chat' => 0,
'data' => 0,
'lesson' => 1,
],
],
'Invalid XML file' => [
'preset' => self::get_fixture_path(__NAMESPACE__, 'invalid_xml_file.xml'),
'preset' => __DIR__ . '/fixtures/invalid_xml_file.xml',
],
'Unexisting XML file' => [
'preset' => __DIR__ . '/fixtures/unexisting.xml',
@@ -25,7 +25,7 @@ namespace core_adminpresets\local\setting;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_adminpresets\local\setting\adminpresets_admin_setting_bloglevel
*/
final class adminpresets_admin_setting_bloglevel_test extends \advanced_testcase {
class adminpresets_admin_setting_bloglevel_test extends \advanced_testcase {
/**
* Test the behaviour of save_value() method.
@@ -72,18 +72,18 @@ final class adminpresets_admin_setting_bloglevel_test extends \advanced_testcase
*
* @return array
*/
public static function save_value_provider(): array {
public function save_value_provider(): array {
return [
'Save the bloglevel and set blog_menu block visibility to true' => [
'settingvalue' => BLOG_USER_LEVEL,
'setttingvalue' => BLOG_USER_LEVEL,
'expectedsaved' => true,
],
'Same value to bloglevel, so it will not be saved' => [
'settingvalue' => BLOG_SITE_LEVEL,
'setttingvalue' => BLOG_SITE_LEVEL,
'expectedsaved' => false,
],
'Save the bloglevel and set blog_menu block visibility to false' => [
'settingvalue' => 0,
'setttingvalue' => 0,
'expectedsaved' => true,
],
];
@@ -25,7 +25,7 @@ namespace core_adminpresets\local\setting;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_adminpresets\local\setting\adminpresets_admin_setting_sitesettext
*/
final class adminpresets_admin_setting_sitesettext_test extends \advanced_testcase {
class adminpresets_admin_setting_sitesettext_test extends \advanced_testcase {
/**
* Test the behaviour of save_value() method.
@@ -66,26 +66,26 @@ final class adminpresets_admin_setting_sitesettext_test extends \advanced_testca
*
* @return array
*/
public static function save_value_provider(): array {
public function save_value_provider(): array {
return [
'Fullname: different value' => [
'settingname' => 'fullname',
'settingvalue' => 'New site fullname',
'setttingvalue' => 'New site fullname',
'expectedsaved' => true,
],
'Fullname: same value' => [
'settingname' => 'fullname',
'settingvalue' => 'PHPUnit test site',
'setttingvalue' => 'PHPUnit test site',
'expectedsaved' => false,
],
'Summary: different value' => [
'settingname' => 'summary',
'settingvalue' => 'This is a new site summary.',
'setttingvalue' => 'This is a new site summary.',
'expectedsaved' => true,
],
'Summary: same value' => [
'settingname' => 'summary',
'settingvalue' => '',
'setttingvalue' => '',
'expectedsaved' => false,
],
];
@@ -25,7 +25,7 @@ namespace core_adminpresets\local\setting;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_adminpresets\local\setting\adminpresets_setting
*/
final class adminpresets_setting_test extends \advanced_testcase {
class adminpresets_setting_test extends \advanced_testcase {
/**
* Test the behaviour of save_value() method.
@@ -77,34 +77,34 @@ final class adminpresets_setting_test extends \advanced_testcase {
*
* @return array
*/
public static function save_value_provider(): array {
public function save_value_provider(): array {
return [
'Core setting with the same value is not saved' => [
'category' => 'optionalsubsystems',
'settingplugin' => 'core',
'settingname' => 'enablebadges',
'settingvalue' => '1',
'setttingvalue' => '1',
'expectedsaved' => false,
],
'Core setting with a different value is saved' => [
'category' => 'optionalsubsystems',
'settingplugin' => 'core',
'settingname' => 'enablebadges',
'settingvalue' => '0',
'setttingvalue' => '0',
'expectedsaved' => true,
],
'Plugin setting with the same value is not saved' => [
'category' => 'modsettinglesson',
'settingplugin' => 'mod_lesson',
'settingname' => 'mediawidth',
'settingvalue' => '640',
'setttingvalue' => '640',
'expectedsaved' => false,
],
'Plugin setting with different value is saved' => [
'category' => 'modsettinglesson',
'settingplugin' => 'mod_lesson',
'settingname' => 'mediawidth',
'settingvalue' => '900',
'setttingvalue' => '900',
'expectedsaved' => true,
],
];
@@ -167,14 +167,14 @@ final class adminpresets_setting_test extends \advanced_testcase {
*
* @return array
*/
public static function save_attributes_values_provider(): array {
public function save_attributes_values_provider(): array {
return [
'Plugin setting with the same value is not saved' => [
'category' => 'modsettinglesson',
'settingplugin' => 'mod_lesson',
'settingname' => 'maxanswers',
'advsettingname' => 'maxanswers_adv',
'advsettingvalue' => '1',
'advsetttingvalue' => '1',
'expectedsaved' => false,
],
'Plugin setting with different value is saved' => [
@@ -182,7 +182,7 @@ final class adminpresets_setting_test extends \advanced_testcase {
'settingplugin' => 'mod_lesson',
'settingname' => 'maxanswers',
'advsettingname' => 'maxanswers_adv',
'advsettingvalue' => '0',
'advsetttingvalue' => '0',
'expectedsaved' => true,
],
'Plugin setting without advanced attributes are not saved' => [
@@ -190,7 +190,7 @@ final class adminpresets_setting_test extends \advanced_testcase {
'settingplugin' => 'mod_lesson',
'settingname' => 'maxanswers',
'advsettingname' => null,
'advsettingvalue' => '0',
'advsetttingvalue' => '0',
'expectedsaved' => false,
],
];
+14 -95
View File
@@ -16,7 +16,6 @@
namespace core_adminpresets;
use moodle_exception;
use stdClass;
/**
@@ -28,16 +27,7 @@ use stdClass;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_adminpresets\manager
*/
final class manager_test extends \advanced_testcase {
/**
* Include required libraries.
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once($CFG->libdir.'/adminlib.php');
parent::setUpBeforeClass();
}
class manager_test extends \advanced_testcase {
/**
* Test the behaviour of protected get_site_settings method.
*
@@ -368,7 +358,7 @@ final class manager_test extends \advanced_testcase {
*
* @return array
*/
public static function export_preset_provider(): array {
public function export_preset_provider(): array {
return [
'Export settings and plugins, excluding sensible' => [
'includesensible' => false,
@@ -424,7 +414,7 @@ final class manager_test extends \advanced_testcase {
* @param string|null $expectedpresetname Expected preset name.
*/
public function test_import_preset(string $filecontents, bool $expectedpreset, bool $expectedsettings = false,
bool $expectedplugins = false, bool $expecteddebugging = false, ?string $expectedexception = null,
bool $expectedplugins = false, bool $expecteddebugging = false, string $expectedexception = null,
string $expectedpresetname = 'Imported preset'): void {
global $DB;
@@ -438,10 +428,7 @@ final class manager_test extends \advanced_testcase {
// Call the method to be tested.
$manager = new manager();
try {
$invokable = self::get_invokable();
set_error_handler($invokable, E_WARNING);
list($xml, $preset, $settingsfound, $pluginsfound) = $manager->import_preset($filecontents);
restore_error_handler();
} catch (\exception $e) {
if ($expectedexception) {
$this->assertInstanceOf($expectedexception, $e);
@@ -509,7 +496,7 @@ final class manager_test extends \advanced_testcase {
'activity_modules' => 1,
],
'mod' => [
'page' => 0,
'chat' => 0,
'data' => 0,
'lesson' => 1,
],
@@ -534,32 +521,26 @@ final class manager_test extends \advanced_testcase {
*
* @return array
*/
public static function import_preset_provider(): array {
public function import_preset_provider(): array {
return [
'Import settings from an empty file' => [
'filecontents' => '',
'expectedpreset' => false,
],
'Import settings and plugins from a valid XML file' => [
'filecontents' => file_get_contents(
filename: self::get_fixture_path(__NAMESPACE__, 'import_settings_plugins.xml')
),
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_settings_plugins.xml'),
'expectedpreset' => true,
'expectedsettings' => true,
'expectedplugins' => true,
],
'Import only settings from a valid XML file' => [
'filecontents' => file_get_contents(
filename: self::get_fixture_path(__NAMESPACE__, 'import_settings.xml')
),
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_settings.xml'),
'expectedpreset' => true,
'expectedsettings' => true,
'expectedplugins' => false,
],
'Import settings and plugins from a valid XML file with Starter name, which will be marked as non-core' => [
'filecontents' => file_get_contents(
filename: self::get_fixture_path(__NAMESPACE__, 'import_starter_name.xml')
),
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_starter_name.xml'),
'expectedpreset' => true,
'expectedsettings' => true,
'expectedplugins' => true,
@@ -568,9 +549,7 @@ final class manager_test extends \advanced_testcase {
'expectedpresetname' => 'Starter',
],
'Import settings from an invalid XML file' => [
'filecontents' => file_get_contents(
filename: self::get_fixture_path(__NAMESPACE__, 'invalid_xml_file.xml')
),
'filecontents' => file_get_contents(__DIR__ . '/fixtures/invalid_xml_file.xml'),
'expectedpreset' => false,
'expectedsettings' => false,
'expectedplugins' => false,
@@ -578,26 +557,20 @@ final class manager_test extends \advanced_testcase {
'expectedexception' => \Exception::class,
],
'Import unexisting settings category' => [
'filecontents' => file_get_contents(
filename: self::get_fixture_path(__NAMESPACE__, 'unexisting_category.xml')
),
'filecontents' => file_get_contents(__DIR__ . '/fixtures/unexisting_category.xml'),
'expectedpreset' => false,
'expectedsettings' => false,
'expectedplugins' => false,
],
'Import unexisting setting' => [
'filecontents' => file_get_contents(
filename: self::get_fixture_path(__NAMESPACE__, 'unexisting_setting.xml')
),
'filecontents' => file_get_contents(__DIR__ . '/fixtures/unexisting_setting.xml'),
'expectedpreset' => false,
'expectedsettings' => false,
'expectedplugins' => false,
'expecteddebugging' => true,
],
'Import valid settings with one unexisting setting too' => [
'filecontents' => file_get_contents(
filename: self::get_fixture_path(__NAMESPACE__, 'import_settings_with_unexisting_setting.xml'),
),
'filecontents' => file_get_contents(__DIR__ . '/fixtures/import_settings_with_unexisting_setting.xml'),
'expectedpreset' => true,
'expectedsettings' => false,
'expectedplugins' => false,
@@ -606,6 +579,7 @@ final class manager_test extends \advanced_testcase {
];
}
/**
* Test the behaviour of delete_preset() method when the preset id doesn't exist.
*
@@ -625,28 +599,10 @@ final class manager_test extends \advanced_testcase {
$manager = new manager();
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage('Error deleting from database');
$this->expectException(\moodle_exception::class);
$manager->delete_preset($unexistingid);
}
/**
* Test trying to delete the core/pre-defined presets
*
* @covers ::delete_preset
*/
public function test_delete_preset_core(): void {
global $DB;
$this->resetAfterTest();
$starterpreset = $DB->get_record('adminpresets', ['iscore' => manager::STARTER_PRESET]);
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage('Error deleting from database');
(new manager())->delete_preset($starterpreset->id);
}
/**
* Test the behaviour of delete_preset() method.
*
@@ -812,41 +768,4 @@ final class manager_test extends \advanced_testcase {
$enabledplugins = \core\plugininfo\qtype::get_enabled_plugins();
$this->assertArrayHasKey('truefalse', $enabledplugins);
}
/**
* Test apply_preset for a deprecated plugin type.
*
* @runInSeparateProcess
*
* @covers ::apply_preset
* @return void
*/
public function test_apply_preset_deprecated_plugintype(): void {
global $CFG;
$this->resetAfterTest();
// Create a preset.
$generator = $this->getDataGenerator()->get_plugin_generator('core_adminpresets');
$presetid = $generator->create_preset();
// Add a mock plugin into the preset. This plugin will be mocked and deprecated below.
helper::add_plugin($presetid, 'fake', 'fullfeatured', true);
// Inject the mock plugin type + plugin, and deprecate it.
$this->add_mocked_plugintype('fake', "$CFG->libdir/tests/fixtures/fakeplugins/fake", true);
$this->add_mocked_plugin('fake', 'fullfeatured', $CFG->libdir . '/tests/fixtures/fakeplugins/fake/fullfeatured');
// Pre-flight check to ensure that the plugin is deprecated.
$pluginman = \core\plugin_manager::instance();
$plugininfo = $pluginman->get_plugin_info('fake_fullfeatured');
// Expected, unit-test-only debugging, since \core\plugin_manager isn't aware of the mock plugininfo class for this type.
$this->assertDebuggingCalled();
$this->assertTrue($plugininfo->is_deprecated());
// Finally, check apply_preset method, verifying the deprecated plugin is skipped.
$manager = new manager();
[$applied, $skipped] = $manager->apply_preset($presetid);
$this->assertdebuggingcalledcount(2); // Expected unit-test-only debugging, as above.
$this->assertContains('fake', array_column($skipped, 'plugin'));
}
}
@@ -30,13 +30,13 @@ use core_privacy\tests\provider_testcase;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_adminpresets\privacy\provider
*/
final class privacy_provider_test extends provider_testcase {
class privacy_provider_test extends provider_testcase {
/**
* Test for provider::get_metadata().
* @covers ::get_metadata
*/
public function test_get_metadata(): void {
public function test_get_metadata() {
$collection = new collection('core_adminpresets');
$newcollection = provider::get_metadata($collection);
$itemcollection = $newcollection->get_collection();
@@ -49,7 +49,7 @@ final class privacy_provider_test extends provider_testcase {
* Test for provider::get_contexts_for_userid() doesn't return any context.
* @covers ::get_contexts_for_userid
*/
public function test_get_contexts_for_userid(): void {
public function test_get_contexts_for_userid() {
global $USER;
$this->resetAfterTest();
@@ -67,7 +67,7 @@ final class privacy_provider_test extends provider_testcase {
* Test for provider::get_users_in_context() doesn't return any user.
* @covers ::get_users_in_context
*/
public function test_get_users_in_context(): void {
public function test_get_users_in_context() {
global $USER;
$this->resetAfterTest();
@@ -88,7 +88,7 @@ final class privacy_provider_test extends provider_testcase {
* Test for provider::export_user_data().
* @covers ::export_user_data
*/
public function test_export_user_data(): void {
public function test_export_user_data() {
global $USER;
$this->resetAfterTest();
@@ -121,7 +121,7 @@ final class privacy_provider_test extends provider_testcase {
* Test for provider::delete_data_for_all_users_in_context().
* @covers ::delete_data_for_all_users_in_context
*/
public function test_delete_data_for_all_users_in_context(): void {
public function test_delete_data_for_all_users_in_context() {
global $DB, $USER;
$this->resetAfterTest();
@@ -146,7 +146,7 @@ final class privacy_provider_test extends provider_testcase {
* Test for provider::delete_data_for_user().
* @covers ::delete_data_for_user
*/
public function test_delete_data_for_user(): void {
public function test_delete_data_for_user() {
global $DB, $USER;
$this->resetAfterTest();
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/php -f
<?php
//error_reporting(0);
//ini_set('display_errors',0);
require_once(__DIR__ . '/../config.php');
$tmp = explode('@',$_ENV['RECIPIENT']);
$address = $tmp[0];
// BOUNCE EMAILS TO NOREPLY
if ($_ENV['RECIPIENT'] == $CFG->noreplyaddress) {
$user = new stdClass();
$user->email = $_ENV['SENDER'];
if (!validate_email($user->email)) {
die();
}
$site = get_site();
$subject = get_string('noreplybouncesubject','moodle',format_string($site->fullname));
$body = get_string('noreplybouncemessage','moodle',format_string($site->fullname))."\n\n";
$fd = fopen('php://stdin','r');
if ($fd) {
while(!feof($fd)) {
$body .= fgets($fd);
}
fclose($fd);
}
$user->id = 0; // to prevent anything annoying happening
$from->firstname = null;
$from->lastname = null;
$from->email = '<>';
$from->maildisplay = true;
email_to_user($user,$from,$subject,$body);
die ();
}
/// ALL OTHER PROCESSING
// we need to split up the address
$prefix = substr($address,0,4);
$mod = substr($address,4,2);
$modargs = substr($address,6,-16);
$hash = substr($address,-16);
if (substr(md5($prefix.$mod.$modargs.$CFG->siteidentifier),0,16) != $hash) {
die("HASH DIDN'T MATCH!\n");
}
list(,$modid) = unpack('C',base64_decode($mod.'=='));
if ($modid == '0') { // special
$modname = 'moodle';
}
else {
$modname = $DB->get_field("modules", "name", array("id"=>$modid));
include_once('mod/'.$modname.'/lib.php');
}
$function = $modname.'_process_email';
if (!function_exists($function)) {
die();
}
$fd = fopen('php://stdin','r');
if (!$fd) {
exit();
}
while(!feof($fd)) {
$body .= fgets($fd);
}
$function($modargs,$body);
fclose($fd);
+1 -7
View File
@@ -23,8 +23,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('IGNORE_COMPONENT_CACHE', true);
require_once('../config.php');
require_once($CFG->libdir.'/adminlib.php');
@@ -44,11 +42,7 @@ if ($data = $form->get_data()) {
purge_caches();
$message = get_string('purgecachesfinished', 'admin');
} else {
// When passing selected options, ensure each is cast to boolean for strict comparison.
purge_caches(array_map(
fn($option) => (bool) $option,
$data->purgeselectedoptions,
));
purge_caches($data->purgeselectedoptions);
$message = get_string('purgeselectedcachesfinished', 'admin');
}
+5 -10
View File
@@ -34,10 +34,8 @@ admin_externalpage_setup('registrationmoodleorg');
$unregistration = optional_param('unregistration', false, PARAM_BOOL);
$confirm = optional_param('confirm', false, PARAM_BOOL);
// Consider the site 'registered' if records exist locally and at the hub.
$siteisregistered = \core\hub\registration::is_registered() && core\hub\api::is_site_registered_in_hub();
if ($unregistration && $siteisregistered) {
if ($unregistration && \core\hub\registration::is_registered()) {
if ($confirm) {
require_sesskey();
\core\hub\registration::unregister(false, false);
@@ -62,7 +60,7 @@ if (!$returnurl = optional_param('returnurl', null, PARAM_LOCALURL)) {
$returnurl = $isinitialregistration ? '/admin/index.php' : '/admin/registration/index.php';
}
$siteregistrationform = new \core\hub\site_registration_form(null, ['registered' => $siteisregistered]);
$siteregistrationform = new \core\hub\site_registration_form();
$siteregistrationform->set_data(['returnurl' => $returnurl]);
if ($fromform = $siteregistrationform->get_data()) {
@@ -88,7 +86,7 @@ echo $OUTPUT->header();
// Current status of registration.
$notificationtype = \core\output\notification::NOTIFY_ERROR;
if ($siteisregistered) {
if (\core\hub\registration::is_registered()) {
$lastupdated = \core\hub\registration::get_last_updated();
if ($lastupdated == 0) {
$registrationmessage = get_string('pleaserefreshregistrationunknown', 'admin');
@@ -106,7 +104,7 @@ if ($siteisregistered) {
}
// Heading.
if ($siteisregistered) {
if (\core\hub\registration::is_registered()) {
echo $OUTPUT->heading(get_string('registerwithmoodleorgupdate', 'core_hub'));
} else if ($isinitialregistration) {
echo $OUTPUT->heading(get_string('registerwithmoodleorgcomplete', 'core_hub'));
@@ -119,7 +117,7 @@ echo $renderer->moodleorg_registration_message();
$siteregistrationform->display();
if ($siteisregistered) {
if (\core\hub\registration::is_registered()) {
// Unregister link.
$unregisterhuburl = new moodle_url("/admin/registration/index.php", ['unregistration' => 1]);
echo html_writer::div(html_writer::link($unregisterhuburl, get_string('unregister', 'hub')), 'unregister mt-2');
@@ -127,7 +125,4 @@ if ($siteisregistered) {
echo html_writer::div(html_writer::link(new moodle_url($returnurl), get_string('skipregistration', 'hub')),
'skipregistration mt-2');
}
$PAGE->requires->js_call_amd('core_admin/expand_hash', 'init');
echo $OUTPUT->footer();
+120 -202
View File
@@ -617,7 +617,7 @@ class core_admin_renderer extends plugin_renderer_base {
* @param bool $croninfrequent
* @return string HTML to output.
*/
public function cron_infrequent_warning(bool $croninfrequent): string {
public function cron_infrequent_warning(bool $croninfrequent) : string {
global $CFG;
if (!$croninfrequent) {
@@ -843,14 +843,14 @@ class core_admin_renderer extends plugin_renderer_base {
$registerbutton = $this->single_button(new moodle_url('/admin/registration/index.php'),
get_string('register', 'admin'));
$str = 'registrationwarning';
$type = 'error alert alert-danger';
} else {
$registerbutton = '';
$str = 'registrationwarningcontactadmin';
$type = 'info';
}
return $this->warning( get_string($str, 'admin') . '&nbsp;' . $registerbutton , $type);
return $this->warning( get_string($str, 'admin')
. '&nbsp;' . $this->help_icon('registration', 'admin') . $registerbutton ,
'error alert alert-danger');
}
return '';
@@ -874,8 +874,8 @@ class core_admin_renderer extends plugin_renderer_base {
protected function mobile_configuration_warning($mobileconfigured) {
$output = '';
if (!$mobileconfigured) {
$settingslink = new moodle_url('/admin/search.php', ['query' => 'enablemobilewebservice']);
$configurebutton = $this->single_button($settingslink, get_string('enablemobilewebservice', 'admin'), 'get');
$settingslink = new moodle_url('/admin/settings.php', ['section' => 'mobilesettings']);
$configurebutton = $this->single_button($settingslink, get_string('enablemobilewebservice', 'admin'));
$output .= $this->warning(get_string('mobilenotconfiguredwarning', 'admin') . '&nbsp;' . $configurebutton);
}
@@ -1004,7 +1004,7 @@ class core_admin_renderer extends plugin_renderer_base {
function upgrade_reload($url) {
return html_writer::empty_tag('br') .
html_writer::tag('div',
html_writer::link($url, $this->pix_icon('i/reload', '', '', ['class' => 'icon']) .
html_writer::link($url, $this->pix_icon('i/reload', '', '', array('class' => 'icon icon-pre')) .
get_string('reload'), array('title' => get_string('reload'))),
array('class' => 'continuebutton')) . html_writer::empty_tag('br');
}
@@ -1024,7 +1024,7 @@ class core_admin_renderer extends plugin_renderer_base {
*/
public function plugins_check_table(core_plugin_manager $pluginman, $version, array $options = array()) {
global $CFG;
$plugininfo = $pluginman->get_plugins(true);
$plugininfo = $pluginman->get_plugins();
if (empty($plugininfo)) {
return '';
@@ -1059,20 +1059,6 @@ class core_admin_renderer extends plugin_renderer_base {
$upgradeabortable = $pluginman->list_restorable_archives();
foreach ($plugininfo as $type => $plugins) {
// Skip deleted plugin types.
if (\core_component::is_deleted_plugin_type($type)) {
continue;
}
// Skip deprecated plugintypes having no installed plugins, in which case there is nothing to report.
if (\core_component::is_deprecated_plugin_type($type)) {
$reportableplugins = array_filter($plugins, function($plugininfo) {
return $plugininfo->versiondb != null;
});
if (empty($reportableplugins)) {
continue;
}
}
$header = new html_table_cell($pluginman->plugintype_name_plural($type));
$header->header = true;
@@ -1121,15 +1107,14 @@ class core_admin_renderer extends plugin_renderer_base {
);
$versiondb = new html_table_cell($plugin->versiondb);
$versiondisk = $plugin->is_deprecated() ? new html_table_cell() : new html_table_cell($plugin->versiondisk);
$versiondisk = new html_table_cell($plugin->versiondisk);
if ($isstandard = $plugin->is_standard()) {
$row->attributes['class'] .= ' standard';
$sourcelabel = html_writer::span(get_string('sourcestd', 'core_plugin'),
'sourcetext badge bg-secondary text-dark');
$sourcelabel = html_writer::span(get_string('sourcestd', 'core_plugin'), 'sourcetext badge badge-secondary');
} else {
$row->attributes['class'] .= ' extension';
$sourcelabel = html_writer::span(get_string('sourceext', 'core_plugin'), 'sourcetext badge bg-info text-white');
$sourcelabel = html_writer::span(get_string('sourceext', 'core_plugin'), 'sourcetext badge badge-info');
}
$coredependency = $plugin->is_core_dependency_satisfied($version);
@@ -1140,41 +1125,26 @@ class core_admin_renderer extends plugin_renderer_base {
$statuscode = $plugin->get_status();
$row->attributes['class'] .= ' status-' . $statuscode;
if ($plugin->is_deprecated()) {
$row->attributes['class'] .= ' deprecatedtype';
}
$statusclass = 'statustext badge ';
switch ($statuscode) {
case core_plugin_manager::PLUGIN_STATUS_NEW:
$statusclass .= $dependenciesok ? 'bg-success text-white' : 'bg-warning text-dark';
$statusclass .= $dependenciesok ? 'badge-success' : 'badge-warning';
break;
case core_plugin_manager::PLUGIN_STATUS_UPGRADE:
$statusclass .= $dependenciesok ? 'bg-info text-white' : 'bg-warning text-dark';
$statusclass .= $dependenciesok ? 'badge-info' : 'badge-warning';
break;
case core_plugin_manager::PLUGIN_STATUS_MISSING:
case core_plugin_manager::PLUGIN_STATUS_DOWNGRADE:
case core_plugin_manager::PLUGIN_STATUS_DELETE:
$statusclass .= 'bg-danger text-white';
$statusclass .= 'badge-danger';
break;
case core_plugin_manager::PLUGIN_STATUS_NODB:
case core_plugin_manager::PLUGIN_STATUS_UPTODATE:
$statusclass .= $dependenciesok ? 'bg-light text-dark' : 'bg-warning text-dark';
$statusclass .= $dependenciesok ? 'badge-light' : 'badge-warning';
break;
}
$status = html_writer::span(get_string('status_' . $statuscode, 'core_plugin'), $statusclass);
$deprecatedstatus = '';
if ($plugin->is_deprecated()) {
// During upgrade, omit the status for deprecated plugin types, unless it's 'missing from disk'.
// Deprecated plugins cannot be installed, upgraded, downgraded, and won't be automatically deleted.
$status = in_array($statuscode,
[core_plugin_manager::PLUGIN_STATUS_MISSING, core_plugin_manager::PLUGIN_STATUS_DELETE]) ? $status : '';
$deprecatedstatus = html_writer::span(
get_string('deprecated_type', 'core_plugin'),
'statustext badge bg-danger text-white'
);
}
if (!empty($installabortable[$plugin->component])) {
$status .= $this->output->single_button(
new moodle_url($this->page->url, array('abortinstall' => $plugin->component, 'confirmplugincheck' => 0)),
@@ -1200,7 +1170,7 @@ class core_admin_renderer extends plugin_renderer_base {
}
}
$status = new html_table_cell($sourcelabel.' '.$status.' '.$deprecatedstatus);
$status = new html_table_cell($sourcelabel.' '.$status);
if ($plugin->pluginsupported != null) {
$requires = new html_table_cell($this->required_column($plugin, $pluginman, $version, $CFG->branch));
} else {
@@ -1208,8 +1178,7 @@ class core_admin_renderer extends plugin_renderer_base {
}
$statusisboring = in_array($statuscode, array(
core_plugin_manager::PLUGIN_STATUS_NODB, core_plugin_manager::PLUGIN_STATUS_UPTODATE))
&& !$plugin->is_deprecated();
core_plugin_manager::PLUGIN_STATUS_NODB, core_plugin_manager::PLUGIN_STATUS_UPTODATE));
if ($options['xdep']) {
// we want to see only plugins with failed dependencies
@@ -1271,7 +1240,7 @@ class core_admin_renderer extends plugin_renderer_base {
new moodle_url($this->page->url, array('installupdatex' => 1)),
get_string('updateavailableinstallall', 'core_admin', count($installableupdates)),
'post',
array('class' => 'singlebutton updateavailableinstallall me-1')
array('class' => 'singlebutton updateavailableinstallall mr-1')
);
}
@@ -1280,7 +1249,7 @@ class core_admin_renderer extends plugin_renderer_base {
new moodle_url($this->page->url, array('abortinstallx' => 1, 'confirmplugincheck' => 0)),
get_string('cancelinstallall', 'core_plugin', count($installabortable)),
'post',
array('class' => 'singlebutton cancelinstallall me-1')
array('class' => 'singlebutton cancelinstallall mr-1')
);
}
@@ -1289,17 +1258,17 @@ class core_admin_renderer extends plugin_renderer_base {
new moodle_url($this->page->url, array('abortupgradex' => 1)),
get_string('cancelupgradeall', 'core_plugin', count($upgradeabortable)),
'post',
array('class' => 'singlebutton cancelupgradeall me-1')
array('class' => 'singlebutton cancelupgradeall mr-1')
);
}
$out .= html_writer::div(html_writer::link(new moodle_url($this->page->url, array('showallplugins' => 0)),
get_string('plugincheckattention', 'core_plugin')).' '.html_writer::span($sumattention, 'badge bg-light text-dark'),
'btn btn-link me-1');
get_string('plugincheckattention', 'core_plugin')).' '.html_writer::span($sumattention, 'badge badge-light'),
'btn btn-link mr-1');
$out .= html_writer::div(html_writer::link(new moodle_url($this->page->url, array('showallplugins' => 1)),
get_string('plugincheckall', 'core_plugin')).' '.html_writer::span($sumtotal, 'badge bg-light text-dark'),
'btn btn-link me-1');
get_string('plugincheckall', 'core_plugin')).' '.html_writer::span($sumtotal, 'badge badge-light'),
'btn btn-link mr-1');
$out .= $this->output->container_end(); // End of .actions container.
$out .= $this->output->container_end(); // End of #plugins-check-info container.
@@ -1318,7 +1287,7 @@ class core_admin_renderer extends plugin_renderer_base {
* @param null|moodle_url $cancel URL for the cancel link, defaults to the current page
* @return string HTML
*/
public function plugins_management_confirm_buttons(?moodle_url $continue=null, ?moodle_url $cancel=null) {
public function plugins_management_confirm_buttons(moodle_url $continue=null, moodle_url $cancel=null) {
$out = html_writer::start_div('plugins-management-confirm-buttons');
@@ -1406,13 +1375,13 @@ class core_admin_renderer extends plugin_renderer_base {
new moodle_url($this->page->url, array('installdepx' => 1)),
get_string('dependencyinstallmissing', 'core_plugin', count($installable)),
'post',
array('class' => 'singlebutton dependencyinstallmissing d-inline-block me-1')
array('class' => 'singlebutton dependencyinstallmissing d-inline-block mr-1')
);
}
$out .= html_writer::div(html_writer::link(new moodle_url('/admin/tool/installaddon/'),
get_string('dependencyuploadmissing', 'core_plugin'), array('class' => 'btn btn-link')),
'dependencyuploadmissing d-inline-block me-1');
'dependencyuploadmissing d-inline-block mr-1');
$out .= $this->output->container_end(); // End of .plugins-check-dependencies-actions container.
@@ -1451,9 +1420,9 @@ class core_admin_renderer extends plugin_renderer_base {
$supportedmoodles = array();
foreach ($plugin->version->supportedmoodles as $moodle) {
if ($CFG->branch == str_replace('.', '', $moodle->release)) {
$supportedmoodles[] = html_writer::span($moodle->release, 'badge bg-success text-white');
$supportedmoodles[] = html_writer::span($moodle->release, 'badge badge-success');
} else {
$supportedmoodles[] = html_writer::span($moodle->release, 'badge bg-light text-dark');
$supportedmoodles[] = html_writer::span($moodle->release, 'badge badge-light');
}
}
@@ -1478,29 +1447,29 @@ class core_admin_renderer extends plugin_renderer_base {
$info .= html_writer::div(
html_writer::link('https://moodle.org/plugins/view.php?plugin='.$plugin->component,
get_string('misdepinfoplugin', 'core_plugin')),
'misdepinfoplugin d-inline-block me-3 mb-1'
'misdepinfoplugin d-inline-block mr-3 mb-1'
);
$info .= html_writer::div(
html_writer::link('https://moodle.org/plugins/pluginversion.php?id='.$plugin->version->id,
get_string('misdepinfoversion', 'core_plugin')),
'misdepinfoversion d-inline-block me-3 mb-1'
'misdepinfoversion d-inline-block mr-3 mb-1'
);
$info .= html_writer::div(html_writer::link($plugin->version->downloadurl, get_string('download')),
'misdepdownload d-inline-block me-3 mb-1');
'misdepdownload d-inline-block mr-3 mb-1');
if ($pluginman->is_remote_plugin_installable($plugin->component, $plugin->version->version, $reason)) {
$info .= $this->output->single_button(
new moodle_url($this->page->url, array('installdep' => $plugin->component)),
get_string('dependencyinstall', 'core_plugin'),
'post',
array('class' => 'singlebutton dependencyinstall me-3 mb-1')
array('class' => 'singlebutton dependencyinstall mr-3 mb-1')
);
} else {
$reasonhelp = $this->info_remote_plugin_not_installable($reason);
if ($reasonhelp) {
$info .= html_writer::div($reasonhelp, 'reasonhelp dependencyinstall d-inline-block me-3 mb-1');
$info .= html_writer::div($reasonhelp, 'reasonhelp dependencyinstall d-inline-block mr-3 mb-1');
}
}
@@ -1559,7 +1528,7 @@ class core_admin_renderer extends plugin_renderer_base {
$label = '';
} else {
$class = 'requires-failed';
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge bg-danger text-white');
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-danger');
}
if ($branch != null && !$plugin->is_core_compatible_satisfied($branch)) {
@@ -1588,10 +1557,8 @@ class core_admin_renderer extends plugin_renderer_base {
} else if ($reqinfo->status == $pluginman::REQUIREMENT_STATUS_MISSING) {
if ($reqinfo->availability == $pluginman::REQUIREMENT_AVAILABLE) {
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'),
'badge bg-warning text-dark');
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'),
'badge bg-warning text-dark');
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'), 'badge badge-warning');
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'), 'badge badge-warning');
$class = 'requires-failed requires-missing requires-available';
$actions[] = html_writer::link(
new moodle_url('https://moodle.org/plugins/view.php', array('plugin' => $reqname)),
@@ -1599,25 +1566,24 @@ class core_admin_renderer extends plugin_renderer_base {
);
} else {
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'), 'badge bg-danger text-white');
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'), 'badge badge-danger');
$label .= ' '.html_writer::span(get_string('dependencyunavailable', 'core_plugin'),
'badge bg-danger text-white');
'badge badge-danger');
$class = 'requires-failed requires-missing requires-unavailable';
}
$displayuploadlink = true;
} else if ($reqinfo->status == $pluginman::REQUIREMENT_STATUS_OUTDATED) {
if ($reqinfo->availability == $pluginman::REQUIREMENT_AVAILABLE) {
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge bg-warning text-dark');
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'),
'badge bg-warning text-dark');
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-warning');
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'), 'badge badge-warning');
$class = 'requires-failed requires-outdated requires-available';
$displayupdateslink = true;
} else {
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge bg-danger text-white');
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-danger');
$label .= ' '.html_writer::span(get_string('dependencyunavailable', 'core_plugin'),
'badge bg-danger text-white');
'badge badge-danger');
$class = 'requires-failed requires-outdated requires-unavailable';
}
$displayuploadlink = true;
@@ -1685,26 +1651,11 @@ class core_admin_renderer extends plugin_renderer_base {
*/
public function plugins_overview_panel(core_plugin_manager $pluginman, array $options = array()) {
$plugininfo = $pluginman->get_plugins(true);
$this->page->requires->js_call_amd('core_admin/plugins_overview', 'init');
$plugininfo = $pluginman->get_plugins();
$numtotal = $numextension = $numupdatable = $numinstallable = $nummissing = $numnew = 0;
$numtotal = $numextension = $numupdatable = $numinstallable = 0;
foreach ($plugininfo as $type => $plugins) {
if (\core_component::is_deleted_plugin_type($type)) {
continue;
}
// Skip deprecated plugintypes having no installed plugins, in which case there is nothing to report.
if (\core_component::is_deprecated_plugin_type($type)) {
$reportableplugins = array_filter($plugins, function($plugininfo) {
return $plugininfo->versiondb != null;
});
if (empty($reportableplugins)) {
continue;
}
}
foreach ($plugins as $name => $plugin) {
if ($res = $plugin->available_updates()) {
$numupdatable++;
@@ -1716,12 +1667,8 @@ class core_admin_renderer extends plugin_renderer_base {
}
}
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
$nummissing++;
continue;
}
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_NEW) {
$numnew++;
}
$numtotal++;
if (!$plugin->is_standard()) {
$numextension++;
@@ -1730,50 +1677,36 @@ class core_admin_renderer extends plugin_renderer_base {
}
$infoall = html_writer::link(
$this->page->url,
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 0)),
get_string('overviewall', 'core_plugin'),
['title' => get_string('filterall', 'core_plugin'), 'data-filterby' => 'all', 'class' => 'active']
).' '.html_writer::span($numtotal, 'badge text-dark number number-all');
array('title' => get_string('filterall', 'core_plugin'))
).' '.html_writer::span($numtotal, 'badge number number-all');
$infoext = html_writer::link(
new moodle_url($this->page->url, [], 'additional'),
new moodle_url($this->page->url, array('contribonly' => 1, 'updatesonly' => 0)),
get_string('overviewext', 'core_plugin'),
['title' => get_string('filtercontribonly', 'core_plugin'), 'data-filterby' => 'additional']
).' '.html_writer::span($numextension, 'badge text-dark number number-additional');
array('title' => get_string('filtercontribonly', 'core_plugin'))
).' '.html_writer::span($numextension, 'badge number number-additional');
if ($numupdatable) {
$infoupdatable = html_writer::link(
new moodle_url($this->page->url, [], 'updatable'),
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 1)),
get_string('overviewupdatable', 'core_plugin'),
['title' => get_string('filterupdatesonly', 'core_plugin'), 'data-filterby' => 'updatable']
).' '.html_writer::span($numupdatable, 'badge bg-info text-white number number-updatable');
array('title' => get_string('filterupdatesonly', 'core_plugin'))
).' '.html_writer::span($numupdatable, 'badge badge-info number number-updatable');
} else {
// No updates, or the notifications disabled.
$infoupdatable = '';
}
if ($numnew) {
$infonew = html_writer::link(
new moodle_url($this->page->url, [], 'newplugin'),
get_string('status_new', 'plugin'),
['title' => get_string('filternewpluginsonly', 'core_plugin'), 'data-filterby' => 'newplugin']
).' '.html_writer::span($numnew, 'badge bg-success text-white number number-newplugin');
} else {
$infonew = '';
}
$out = html_writer::start_div('', array('id' => 'plugins-overview-panel'));
if ($nummissing) {
$infomissing = html_writer::link(
new moodle_url($this->page->url, [], 'missing'),
get_string('status_missing', 'plugin'),
['title' => get_string('filtermissingonly', 'core_plugin'), 'data-filterby' => 'missing']
).' '.html_writer::span($nummissing, 'badge bg-danger text-white number number-missing');
} else {
$infomissing = '';
if (!empty($options['updatesonly'])) {
$out .= $this->output->heading(get_string('overviewupdatable', 'core_plugin'), 3);
} else if (!empty($options['contribonly'])) {
$out .= $this->output->heading(get_string('overviewext', 'core_plugin'), 3);
}
$out = html_writer::start_div('', ['id' => 'plugins-overview-panel']);
if ($numinstallable) {
$out .= $this->output->single_button(
new moodle_url($this->page->url, array('installupdatex' => 1)),
@@ -1784,10 +1717,8 @@ class core_admin_renderer extends plugin_renderer_base {
}
$out .= html_writer::div($infoall, 'info info-all').
html_writer::div($infoext, 'info info-additional').
html_writer::div($infoupdatable, 'info info-updatable').
html_writer::div($infonew, 'info info-newplugin').
html_writer::div($infomissing, 'info info-missing');
html_writer::div($infoext, 'info info-ext').
html_writer::div($infoupdatable, 'info info-updatable');
$out .= html_writer::end_div(); // End of #plugins-overview-panel block.
@@ -1805,7 +1736,39 @@ class core_admin_renderer extends plugin_renderer_base {
*/
public function plugins_control_panel(core_plugin_manager $pluginman, array $options = array()) {
$plugininfo = $pluginman->get_plugins(true);
$plugininfo = $pluginman->get_plugins();
// Filter the list of plugins according the options.
if (!empty($options['updatesonly'])) {
$updateable = array();
foreach ($plugininfo as $plugintype => $pluginnames) {
foreach ($pluginnames as $pluginname => $pluginfo) {
$pluginavailableupdates = $pluginfo->available_updates();
if (!empty($pluginavailableupdates)) {
foreach ($pluginavailableupdates as $pluginavailableupdate) {
$updateable[$plugintype][$pluginname] = $pluginfo;
}
}
}
}
$plugininfo = $updateable;
}
if (!empty($options['contribonly'])) {
$contribs = array();
foreach ($plugininfo as $plugintype => $pluginnames) {
foreach ($pluginnames as $pluginname => $pluginfo) {
if (!$pluginfo->is_standard()) {
$contribs[$plugintype][$pluginname] = $pluginfo;
}
}
}
$plugininfo = $contribs;
}
if (empty($plugininfo)) {
return '';
}
$table = new html_table();
$table->id = 'plugins-control-panel';
@@ -1822,20 +1785,6 @@ class core_admin_renderer extends plugin_renderer_base {
);
foreach ($plugininfo as $type => $plugins) {
if (\core_component::is_deleted_plugin_type($type)) {
continue;
}
// Skip deprecated plugintypes having no installed plugins, in which case there is nothing to report.
if (\core_component::is_deprecated_plugin_type($type)) {
$reportableplugins = array_filter($plugins, function($plugininfo) {
return $plugininfo->versiondb != null;
});
if (empty($reportableplugins)) {
continue;
}
}
$heading = $pluginman->plugintype_name_plural($type);
$pluginclass = core_plugin_manager::resolve_plugininfo_class($type);
if ($manageurl = $pluginclass::get_manage_url()) {
@@ -1878,9 +1827,6 @@ class core_admin_renderer extends plugin_renderer_base {
}
$status = $plugin->get_status();
$row->attributes['class'] .= ' status-'.$status;
if ($plugin->is_deprecated()) {
$row->attributes['class'] .= ' deprecatedtype';
}
$pluginname = html_writer::tag('div', $icon.$plugin->displayname, array('class' => 'displayname')).
html_writer::tag('div', $plugin->component, array('class' => 'componentname'));
$pluginname = new html_table_cell($pluginname);
@@ -1921,30 +1867,18 @@ class core_admin_renderer extends plugin_renderer_base {
$row->attributes['class'] .= ' standard';
$source = '';
} else {
$row->attributes['class'] .= ' additional';
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge me-1 text-bg-info');
$row->attributes['class'] .= ' extension';
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge badge-info');
}
if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
$row->attributes['class'] .= ' missing';
$msg = html_writer::div(get_string('status_missing', 'core_plugin'), 'statusmsg badge text-bg-danger');
$msg = html_writer::div(get_string('status_missing', 'core_plugin'), 'statusmsg badge badge-danger');
} else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
$row->attributes['class'] .= ' newplugin';
$msg = html_writer::div(get_string('status_new', 'core_plugin'), 'statusmsg badge text-bg-success');
$msg = html_writer::div(get_string('status_new', 'core_plugin'), 'statusmsg badge badge-success');
} else {
$msg = '';
}
$deprecatedmsg = '';
if ($plugin->is_deprecated()) {
// Omit the status for deprecated plugin types, unless it's 'missing from disk'.
$msg = $status == core_plugin_manager::PLUGIN_STATUS_MISSING ? $msg : '';
$deprecatedmsg = html_writer::div(
get_string('deprecated_type', 'core_plugin'),
'statusmsg badge bg-danger text-white'
);
}
$requriedby = $pluginman->other_plugins_that_require($plugin->component);
if ($requriedby) {
$requiredby = html_writer::tag('div', get_string('requiredby', 'core_plugin', implode(', ', $requriedby)),
@@ -1955,13 +1889,12 @@ class core_admin_renderer extends plugin_renderer_base {
$updateinfo = '';
if (is_array($plugin->available_updates())) {
$row->attributes['class'] .= ' updatable';
foreach ($plugin->available_updates() as $availableupdate) {
$updateinfo .= $this->plugin_available_update_info($pluginman, $availableupdate);
}
}
$notes = new html_table_cell($source.' '.$msg.' '.$deprecatedmsg.$requiredby.$updateinfo);
$notes = new html_table_cell($source.$msg.$requiredby.$updateinfo);
$row->cells = array(
$pluginname, $version, $availability, $settings, $uninstall, $notes
@@ -2063,7 +1996,7 @@ class core_admin_renderer extends plugin_renderer_base {
get_string('status'),
);
$servertable->colclasses = array('centeralign name', 'centeralign info', 'leftalign report', 'leftalign plugin', 'centeralign status');
$servertable->attributes['class'] = 'table table-striped admintable environmenttable generaltable table-sm';
$servertable->attributes['class'] = 'admintable environmenttable generaltable table-sm';
$servertable->id = 'serverstatus';
$serverdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
@@ -2076,7 +2009,7 @@ class core_admin_renderer extends plugin_renderer_base {
get_string('status'),
);
$othertable->colclasses = array('aligncenter info', 'alignleft report', 'alignleft plugin', 'aligncenter status');
$othertable->attributes['class'] = 'table table-striped admintable environmenttable generaltable table-sm';
$othertable->attributes['class'] = 'admintable environmenttable generaltable table-sm';
$othertable->id = 'otherserverstatus';
$otherdata = array('ok'=>array(), 'warn'=>array(), 'error'=>array());
@@ -2118,10 +2051,8 @@ class core_admin_renderer extends plugin_renderer_base {
// We are checking installed & enabled things
if ($environment_result->getLevel() == 'required') {
$stringtouse = 'environmentrequirecustomcheck';
} else if ($environment_result->getLevel() == 'optional') {
$stringtouse = 'environmentrecommendcustomcheck';
} else {
$stringtouse = 'environmentshouldfixcustomcheck';
$stringtouse = 'environmentrecommendcustomcheck';
}
} else if ($environment_result->getPart() == 'php_setting') {
@@ -2152,8 +2083,7 @@ class core_admin_renderer extends plugin_renderer_base {
if ($status) { //Handle ok result (ok)
$status = get_string('statusok');
} else {
// Handle check result (warning).
if (in_array($environment_result->getLevel(), ['optional', 'recommended'])) {
if ($environment_result->getLevel() == 'optional') {//Handle check result (warning)
$status = get_string('check');
$warningline = true;
} else { //Handle error result (error)
@@ -2177,33 +2107,29 @@ class core_admin_renderer extends plugin_renderer_base {
} else {
$report = $this->doc_link(join('/', $linkparts), get_string($stringtouse, 'admin', $rec), true);
}
// Enclose report text in div so feedback text will be displayed underneath it.
$report = html_writer::div($report);
// Format error or warning line
if ($errorline) {
$messagetype = 'error';
$statusclass = 'bg-danger text-white';
$feedbackclass = 'alert-danger';
$statusclass = 'badge-danger';
} else if ($warningline) {
$messagetype = 'warn';
$statusclass = 'bg-warning text-dark';
$feedbackclass = 'alert-warning';
$statusclass = 'badge-warning';
} else {
$messagetype = 'ok';
$statusclass = 'bg-success text-white';
$feedbackclass = 'alert-success';
$statusclass = 'badge-success';
}
$status = html_writer::span($status, 'badge ' . $statusclass);
// Here we'll store all the feedback found
$feedbacktext = '';
// Append the feedback if there is some
$feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(),
"alert {$feedbackclass} px-2 py-1 m-1");
$feedbacktext .= $environment_result->strToReport($environment_result->getFeedbackStr(), $messagetype);
//Append the bypass if there is some
$feedbacktext .= $environment_result->strToReport($environment_result->getBypassStr(),
'alert alert-warning px-2 py-1 m-1');
$feedbacktext .= $environment_result->strToReport($environment_result->getBypassStr(), 'warn');
//Append the restrict if there is some
$feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(),
'alert alert-danger px-2 py-1 m-1');
$feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
$report .= $feedbacktext;
@@ -2247,11 +2173,10 @@ class core_admin_renderer extends plugin_renderer_base {
$output = '';
$output .= $this->header();
$output .= $this->container_start('upgradekeyreq');
$output .= $this->heading(get_string('upgradekeyreq', 'core_admin'));
$output .= $this->container_start('upgradekeyreq w-25');
$output .= html_writer::start_tag('form', array('method' => 'POST', 'action' => $url));
$output .= html_writer::empty_tag('input', [
'id' => 'upgradekey',
'name' => 'upgradekey',
'type' => 'password',
'class' => 'form-control w-auto',
@@ -2274,10 +2199,14 @@ class core_admin_renderer extends plugin_renderer_base {
* @return string
*/
public function moodleorg_registration_message() {
$a = new stdClass();
$a->moreinformation = '#id_sitestats'; // More information anchor.
$a->moodleapp = HUB_MOODLEORGHUBURL . '/solutions/moodle-app/';
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub', $a), FORMAT_MARKDOWN);
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub'), FORMAT_MARKDOWN);
$out .= html_writer::link(
new moodle_url('/admin/settings.php', ['section' => 'moodleservices']),
$this->output->pix_icon('i/info', '').' '.get_string('registerwithmoodleorginfoapp', 'core_hub'),
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
);
$out .= html_writer::link(
HUB_MOODLEORGHUBURL,
@@ -2324,15 +2253,4 @@ class core_admin_renderer extends plugin_renderer_base {
return $this->warning($xmlrpcwarning);
}
/**
* Renders the theme selector list.
*
* @param core_admin\output\theme_selector $themeselector
* @return string HTML
*/
public function theme_selector_list(core_admin\output\theme_selector $themeselector): string {
$renderable = $themeselector->export_for_template($this);
return $this->render_from_template('core_admin/themeselector/theme_selector', $renderable);
}
}
+43 -32
View File
@@ -49,11 +49,26 @@ if ($action == 'newon') {
admin_externalpage_setup($pagename);
// The URL used for redirection, and that all edit related URLs will be based off.
$baseurl = new moodle_url('/admin/repository.php');
$sesskeyurl = $CFG->wwwroot.'/'.$CFG->admin.'/repository.php?sesskey=' . sesskey();
$baseurl = $CFG->wwwroot.'/'.$CFG->admin.'/repository.php';
$configstr = get_string('manage', 'repository');
$return = true;
if (!empty($action)) {
require_sesskey();
}
/**
* Helper function that generates a moodle_url object
* relevant to the repository
*/
function repository_action_url($repository) {
global $baseurl;
return new moodle_url($baseurl, array('sesskey'=>sesskey(), 'repos'=>$repository));
}
if (($action == 'edit') || ($action == 'new')) {
$pluginname = '';
if ($action == 'edit') {
@@ -167,12 +182,16 @@ if (($action == 'edit') || ($action == 'new')) {
}
}
} else if ($action == 'show') {
require_sesskey();
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
$class = \core_plugin_manager::resolve_plugininfo_class('repository');
$class::enable_plugin($repository, 1);
$return = true;
} else if ($action == 'hide') {
require_sesskey();
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
$class = \core_plugin_manager::resolve_plugininfo_class('repository');
$class::enable_plugin($repository, 0);
$return = true;
@@ -180,7 +199,9 @@ if (($action == 'edit') || ($action == 'new')) {
$repositorytype = repository::get_type_by_typename($repository);
if ($sure) {
$PAGE->set_pagetype('admin-repository-' . $repository);
require_sesskey();
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
if ($repositorytype->delete($downloadcontents)) {
// Include this information into config changes table.
@@ -199,15 +220,20 @@ if (($action == 'edit') || ($action == 'new')) {
$output = $OUTPUT->box_start('generalbox', 'notice');
$output .= html_writer::tag('p', $message);
$removeurl = new moodle_url($baseurl, [
$removeurl = new moodle_url($sesskeyurl);
$removeurl->params(array(
'action' =>'delete',
'repos' => $repository,
'sure' => 'yes',
]);
));
$removeanddownloadurl = new moodle_url($removeurl, [
$removeanddownloadurl = new moodle_url($sesskeyurl);
$removeanddownloadurl->params(array(
'action' =>'delete',
'repos'=> $repository,
'sure' => 'yes',
'downloadcontents' => 1,
]);
));
$output .= $OUTPUT->single_button($removeurl, get_string('continueuninstall', 'repository'));
$output .= $OUTPUT->single_button($removeanddownloadurl, get_string('continueuninstallanddownload', 'repository'));
@@ -219,11 +245,9 @@ if (($action == 'edit') || ($action == 'new')) {
$return = false;
}
} else if ($action == 'moveup') {
require_sesskey();
$repositorytype = repository::get_type_by_typename($repository);
$repositorytype->move_order('up');
} else if ($action == 'movedown') {
require_sesskey();
$repositorytype = repository::get_type_by_typename($repository);
$repositorytype->move_order('down');
} else {
@@ -315,7 +339,8 @@ if (($action == 'edit') || ($action == 'new')) {
$userinstancenumbertext = "";
}
$settings = html_writer::link(new moodle_url($baseurl, ['action' => 'edit', 'repos' => $typename]), $settingsstr);
$settings .= '<a href="' . $sesskeyurl . '&amp;action=edit&amp;repos=' . $typename . '">' . $settingsstr .'</a>';
$settings .= $OUTPUT->container_start('mdl-left');
$settings .= '<br/>';
$settings .= $admininstancenumbertext;
@@ -332,34 +357,22 @@ if (($action == 'edit') || ($action == 'new')) {
$currentaction = 'hide';
}
// Active toggle.
$selectaction = new moodle_url($baseurl, ['sesskey' => sesskey(), 'repos' => $typename]);
$select = new single_select($selectaction, 'action', $actionchoicesforexisting, $currentaction, null,
'applyto' . basename($typename));
$select = new single_select(repository_action_url($typename, 'repos'), 'action', $actionchoicesforexisting, $currentaction, null, 'applyto' . basename($typename));
$select->set_label(get_string('action'), array('class' => 'accesshide'));
// Display up/down link
$updown = '';
$spacer = $OUTPUT->spacer(array('height'=>15, 'width'=>15)); // should be done with CSS instead
if ($updowncount > 1) {
$moveupaction = new moodle_url($baseurl, [
'sesskey' => sesskey(),
'action' => 'moveup',
'repos' => $typename,
]);
$updown .= html_writer::link($moveupaction, $OUTPUT->pix_icon('t/up', get_string('moveup'))) . '&nbsp;';
$updown .= "<a href=\"$sesskeyurl&amp;action=moveup&amp;repos=".$typename."\">";
$updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . "</a>&nbsp;";
}
else {
$updown .= $spacer;
}
if ($updowncount < $totalrepositorytypes) {
$movedownaction = new moodle_url($baseurl, [
'sesskey' => sesskey(),
'action' => 'movedown',
'repos' => $typename,
]);
$updown .= html_writer::link($movedownaction, $OUTPUT->pix_icon('t/down', get_string('movedown'))) . '&nbsp;';
$updown .= "<a href=\"$sesskeyurl&amp;action=movedown&amp;repos=".$typename."\">";
$updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . "</a>&nbsp;";
}
else {
$updown .= $spacer;
@@ -387,9 +400,7 @@ if (($action == 'edit') || ($action == 'new')) {
foreach ($plugins as $plugin => $dir) {
// Check that it has not already been listed
if (!in_array($plugin, $alreadyplugins)) {
$selectaction = new moodle_url($baseurl, ['sesskey' => sesskey(), 'repos' => $plugin]);
$select = new single_select($selectaction, 'action', $actionchoicesfornew, 'delete', null,
'applyto' . basename($plugin));
$select = new single_select(repository_action_url($plugin, 'repos'), 'action', $actionchoicesfornew, 'delete', null, 'applyto' . basename($plugin));
$select->set_label(get_string('action'), array('class' => 'accesshide'));
$uninstall = '';
if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('repository_' . $plugin, 'manage')) {
+10 -9
View File
@@ -18,6 +18,8 @@ require_once(__DIR__ . '/../config.php');
require_once($CFG->dirroot . '/repository/lib.php');
require_once($CFG->libdir . '/adminlib.php');
require_sesskey();
// id of repository
$edit = optional_param('edit', 0, PARAM_INT);
$new = optional_param('new', '', PARAM_PLUGIN);
@@ -41,8 +43,12 @@ if ($edit){
admin_externalpage_setup($pagename, '', null, new moodle_url('/admin/repositoryinstance.php'));
// The URL used for redirection, and that all edit related URLs will be based off.
$parenturl = new moodle_url('/admin/repository.php', ['action' => 'edit']);
$baseurl = new moodle_url("/$CFG->admin/repositoryinstance.php", array('sesskey'=>sesskey()));
$parenturl = new moodle_url("/$CFG->admin/repository.php", array(
'sesskey'=>sesskey(),
'action'=>'edit',
));
if ($new) {
$parenturl->param('repos', $new);
@@ -110,7 +116,6 @@ if (!empty($edit) || !empty($new)) {
$return = false;
}
} else if (!empty($hide)) {
require_sesskey();
$instance = repository::get_type_by_typename($hide);
$instance->hide();
core_plugin_manager::reset_caches();
@@ -124,7 +129,6 @@ if (!empty($edit) || !empty($new)) {
throw new repository_exception('nopermissiontoaccess', 'repository');
}
if ($sure) {
require_sesskey();
if ($instance->delete($downloadcontents)) {
$deletedstr = get_string('instancedeleted', 'repository');
core_plugin_manager::reset_caches();
@@ -137,17 +141,14 @@ if (!empty($edit) || !empty($new)) {
echo $OUTPUT->header();
echo $OUTPUT->box_start('generalbox', 'notice');
$continueurl = new moodle_url($PAGE->url, [
$continueurl = new moodle_url($baseurl, array(
'type' => $type,
'delete' => $delete,
'sure' => 'yes',
]);
));
$continueanddownloadurl = new moodle_url($continueurl, array(
'downloadcontents' => 1
));
$message = get_string('confirmdelete', 'repository', $instance->name);
echo html_writer::tag('p', $message);
-22
View File
@@ -1,22 +0,0 @@
# core_role (subsystem) Upgrade notes
## 4.5
### Added
- All session management has been moved to the `\core\session\manager` class.
This removes the dependancy to use the `sessions` table.
Session management plugins (like Redis) should now inherit
the base `\core\session\handler` class, which implements
`SessionHandlerInterface`, and override methods as required.
The following methods in `\core\session\manager` have been deprecated:
| Old method name | New method name |
| --- | --- |
| `kill_all_sessions` | `destroy_all` |
| `kill_session` | `destroy` |
| `kill_sessions_for_auth_plugin` | `destroy_by_auth_plugin` |
| `kill_user_sessions` | `destroy_user_sessions` |
For more information see [MDL-66151](https://tracker.moodle.org/browse/MDL-66151)
-13
View File
@@ -38,19 +38,6 @@ if (!is_siteadmin()) {
}
$admisselector = new core_role_admins_existing_selector();
if (array_key_exists('siteadmins', $CFG->config_php_settings)) {
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('manageadmins', 'core_role'), 3);
echo $OUTPUT->notification(get_string('siteadministratorsconfigphp', 'core_role'), \core\output\notification::NOTIFY_INFO);
echo $OUTPUT->box_start();
echo $OUTPUT->paragraph(get_string('existingadmins', 'core_role'));
$admisselector->display();
echo $OUTPUT->box_end();
echo $OUTPUT->footer();
die();
}
$potentialadmisselector = new core_role_admins_potential_selector();
if ($addusersaction) {

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