Compare commits

...

299 Commits

Author SHA1 Message Date
Jun Pataleta e591ddc4e8 Moodle release 4.1.8 2023-12-22 10:01:56 +08:00
Sara Arjona e1767fa80f weekly release 4.1.7+ 2023-12-21 13:34:33 +01:00
Huong Nguyen e25033e42d Merge branch 'MDL-80151-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-12-20 15:40:30 +07:00
Huong Nguyen dd2c19eb09 MDL-80151 behat: Improve viewing site calendar step 2023-12-20 16:32:55 +08:00
Angelia Dela Cruz 3b8b8d7d84 MDL-80151 mod_chat: Behat for chat repeat times and past session view 2023-12-20 16:32:55 +08:00
Simey Lameze 5378285a9a MDL-80151 behat: add calendar step to navigate to specific view 2023-12-20 16:32:55 +08:00
Jun Pataleta 3a098250f1 Merge branch 'MDL-68124-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-12-20 15:01:28 +08:00
Jun Pataleta 148ddd26e7 Merge branch 'MDL-80393-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-12-19 17:24:56 +08:00
Paul Holden cdf432ee17 MDL-80393 lang: expose current page language to JS modules.
Since 4be39296 the root HTML element contains the iso6391 language
code of the current language pack. However various JS modules need
access to the original Moodle language pack code, so expose that
in the `M.cfg` structure.
2023-12-19 09:08:12 +00:00
Sara Arjona b1e918edd8 MDL-68124 lesson: Fix random behat error with Oracle
This random error was caused because when reviewing an attempt,
the first pageid to display is calculated based on the timeseen
field in the lesson_attempst table.
In Oracle, sometimes it returned the second pageid (because both
had the same timeseen), so which led to these random failures.

Apart from adding 1-second wait when the attempt is created (to
guarantee the first page is returned properly), a few more
improvements have been also made, like removing manual steps for
setting the lesson activity or adding some extra checks to
confirm the information displayed is expected.
2023-12-18 09:49:05 +01:00
Huong Nguyen f58df6fa88 weekly release 4.1.7+ 2023-12-15 08:44:56 +07:00
Huong Nguyen eae562817d Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-12-15 08:44:52 +07:00
Huong Nguyen 033a59f9c5 Merge branch 'MDL-79828-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-12-14 10:16:22 +07:00
Huong Nguyen a34a8636be Merge branch 'MDL-79050-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-12-14 09:57:28 +07:00
Huong Nguyen 08d2e7c136 Merge branch 'MDL-80394-401' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-12-14 09:23:23 +07:00
Ilya Tregubov 886bbcbdb8 Merge branch 'MDL-79938-m401' of https://github.com/sammarshallou/moodle into MOODLE_401_STABLE 2023-12-14 08:51:51 +08:00
AMOS bot 0c3e759d98 Automatically generated installer lang files 2023-12-14 00:11:04 +00:00
sam marshall 607a452a74 MDL-79938 Behat: Add steps to support multiple tabs 2023-12-13 11:45:51 +00:00
Angelia Dela Cruz 3ef6e04f3c MDL-79050 Behat: Add behat coverage to retake lesson activity 2023-12-13 16:27:02 +08:00
Ilya Tregubov dfc7dcd062 MDL-80394 core_grades: Revert changes to graded_users_iterator
Third party plugins are using it and change was breaking it
2023-12-13 09:38:29 +08:00
Sara Arjona 80d773868c Merge branch 'MDL-80315-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-12-12 14:20:03 +01:00
Sara Arjona eb127cc93b Merge branch 'MDL-79835_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-12-12 14:11:51 +01:00
Huong Nguyen 86716dc120 Moodle release 4.1.7 2023-12-08 09:21:51 +07:00
Sara Arjona 3a1b615718 weekly release 4.1.6+ 2023-12-07 16:22:14 +01:00
Paul Holden 54a8516cd2 MDL-79980 mod_survey: remove responses link if user cannot access. 2023-12-07 12:09:27 +01:00
Brendan Heywood 628e7ecc4c MDL-80309 tasks: Clean up mtrace logs 2023-12-07 12:07:45 +08:00
Huong Nguyen 7762fc61e8 Merge branch 'MDL-80341_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-12-07 10:28:55 +07:00
Huong Nguyen 3ffafd9232 Merge branch 'MDL-79061-401-2' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-12-07 09:53:18 +07:00
Huong Nguyen 7c82e1206d Merge branch 'MDL-68652-401-1' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-12-07 09:33:33 +07:00
Jun Pataleta 0152978745 Merge branch 'MDL-80281_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-12-07 10:16:38 +08:00
Huong Nguyen 2302bb7e2b Merge branch 'MDL-77136-401' of https://github.com/lucaboesch/moodle into MOODLE_401_STABLE 2023-12-07 08:37:07 +07:00
Paul Holden d1ccb8702c MDL-79980 mod_survey: respect activity group mode getting report. 2023-12-07 09:31:39 +08:00
Ilya Tregubov 4cb1509e35 MDL-68652 core_grades: Only return allowed fields.
Grade iterator should check user permissions.
2023-12-07 09:14:03 +08:00
Ilya Tregubov f764c332e7 Merge branch 'MOODLE_401_MDL-73378' of https://github.com/danielneis/moodle into MOODLE_401_STABLE 2023-12-07 09:02:59 +08:00
Tim Hunt 67f869a081 MDL-79835 behat_core_generator: remove misleading error obfuscation 2023-12-06 18:14:09 +00:00
Jun Pataleta 467a28d921 Merge branch 'MDL-80127_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-12-06 22:11:56 +08:00
Jun Pataleta 3e4c1c959f Merge branch 'MDL-80079-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-12-06 21:57:54 +08:00
Luca Bösch 69d235426a MDL-77136 qtype_randomsamatch: Newest versions for shortanswer questions 2023-12-06 14:12:02 +01:00
Luca Bösch bbb8622464 MDL-77136 core_question: Newest versions get_questions_from_categories 2023-12-06 14:12:02 +01:00
Tim Hunt 7311dff041 MDL-80127 question engine: prevent fatal errors from old bad data 2023-12-06 11:45:26 +00:00
Tim Hunt 8f2f95bd28 MDL-80127 question engine: don't convert null to '' before storing 2023-12-06 11:43:30 +00:00
Tim Hunt 579a5061ef 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:20:06 +00:00
Angelia Dela Cruz a11a9ca5aa MDL-80315 block_comments: Removed duplicate Behat test
Deleted the duplicated course comments block behat test. The file
blocks/comments/tests/behat/add_comment.feature is deleted as a
result.
2023-12-06 18:26:23 +08:00
Ilya Tregubov 2ff0e12b24 Merge branch 'MDL-76109-401' of https://github.com/meirzamoodle/moodle into MOODLE_401_STABLE 2023-12-06 15:43:39 +08:00
Huong Nguyen 3f163e8330 Merge branch 'MDL-50650-401' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-12-06 14:07:51 +07:00
Ilya Tregubov f5ada5cb0b Merge branch 'MDL-80043-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-12-06 15:01:46 +08:00
Stephan Robotta 63b2ceee5f MDL-41465 reports: In separate group mode, limit to same 2023-12-06 13:50:57 +07:00
Jun Pataleta ee9d740ccf MDL-80043 gradereport_grader: No need to pass header parameter
No need to pass a value for the header parameter in
print_grade_page_head() because:
1. The default grade plugin name will be used anyway.
2. It will allow the use of the editingmode_title lang string for the
grader report so the page title can reflect the display mode of the
grader report, especially when editing mode is turned on.
2023-12-06 13:55:15 +08:00
Ilya Tregubov 13c201fbc7 MDL-68652 mod_forum: Only include gradable users in grading 2023-12-06 12:47:29 +08:00
Jun Pataleta b9fd956e73 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-06 11:54:11 +08:00
David Woloszyn 2f216f5bdb 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 04:00:22 +01:00
Stevani Andolo fcebf9b8a6 MDL-80174 tool_log: ensure "other" data is safely unserialized. 2023-12-06 04:00:22 +01:00
Paul Holden 7ace11d396 MDL-80268 badges: require capability to view awarded badges. 2023-12-06 04:00:22 +01:00
Paul Holden ad377e3085 MDL-79797 backup: ensure block config is safely unserialized. 2023-12-06 04:00:22 +01:00
Ilya Tregubov 9b65beb7ed Merge branch 'MDL-79692-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-12-06 10:50:55 +08:00
Huong Nguyen 4afd2390e2 Merge branch 'MDL-80180-401' of https://github.com/jbs1/moodle into MOODLE_401_STABLE 2023-12-06 09:17:28 +07:00
Ilya Tregubov 184aa8d8ca Merge branch 'MDL-74248-MOODLE_401_STABLE-fix_mp3_mod_folder' of https://github.com/praxisdigital/moodle into MOODLE_401_STABLE 2023-12-06 10:02:13 +08:00
Sara Arjona 9e1916ba09 Merge branch 'MDL-80286-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-12-05 16:42:24 +01:00
Sara Arjona 7d01008384 Merge branch 'MDL-79979-401' of https://github.com/aanabit/moodle into MOODLE_401_STABLE 2023-12-05 16:16:58 +01:00
Jun Pataleta 4ca230b174 Merge branch 'MDL-73974-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-12-05 23:08:56 +08:00
Karen Holland 07e0b64822 MDL-73974 tool_brickfield: Processing alpha fontsizes 2023-12-05 23:08:17 +08:00
Sara Arjona 9acc242e13 Merge branch 'MDL-55640-m401' of https://github.com/sammarshallou/moodle into MOODLE_401_STABLE 2023-12-05 16:02:06 +01:00
Sara Arjona fbdd721f0b Merge branch 'MDL-80015-401' of https://github.com/roland04/moodle into MOODLE_401_STABLE 2023-12-05 15:51:40 +01:00
Sara Arjona 66ae31ecb5 Merge branch 'MDL-80254-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-12-05 15:45:49 +01:00
Jun Pataleta 43b30054ef Merge branch 'MDL-79806_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-12-05 12:48:09 +08:00
Huong Nguyen 066edbb11b Merge branch 'MDL-71336-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-12-05 11:20:59 +07:00
Jun Pataleta 0076a8fe5c Merge branch 'm41-mdl64431' of https://github.com/danmarsden/moodle into MOODLE_401_STABLE 2023-12-05 12:13:24 +08:00
Huong Nguyen 534cf6e3d4 Merge branch 'MOODLE_401_STABLE' of https://github.com/dragos5436/moodle into MOODLE_401_STABLE 2023-12-05 10:41:10 +07:00
Ferran Recio 771f183646 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-05 11:19:30 +08:00
Ilya Tregubov 348efc1495 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:53:31 +08:00
Ilya Tregubov 7b9191508f MDL-68652 core_grades: Add a webservice to retrive only gradable users. 2023-12-05 10:53:31 +08:00
Ilya Tregubov 67f08a792f Merge branch 'MDL-80043-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-12-05 09:11:51 +08:00
Ilya Tregubov 36a5a2d4d1 Merge branch 'MDL-80131-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-12-05 09:01:33 +08:00
Sara Arjona 52818283fe Merge branch 'MDL-80185-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-12-04 11:00:56 +01:00
Paul Holden 053aa290b2 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:47 +00:00
Jun Pataleta 1b1a661a03 Merge branch 'MDL-80221-401-enfix' of https://github.com/mudrd8mz/moodle into MOODLE_401_STABLE 2023-12-04 16:10:39 +08:00
Ilya Tregubov ad3e6363f1 MDL-50650 core_grades: Validate minimum grade when importing grades
Also add behat test for max and min grade validation.
2023-12-04 16:01:49 +08:00
Kyle Matter 2627f5ba2c MDL-50650 core_grades: allow grade imports larger than 100 points 2023-12-04 12:55:50 +08:00
Jun Pataleta 223948bf93 MDL-80131 mod_data: Make page titles unique 2023-12-02 07:20:53 +08:00
dragos5436 6b5485a0b0 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:22:23 +00:00
David Mudrák b16e808764 MDL-80221 lang: Use fixed strings in the tests, too 2023-12-01 06:23:33 +01:00
Ilya Tregubov 76ff7600e4 weekly release 4.1.6+ 2023-12-01 13:12:36 +08:00
Jun Pataleta a2ddc55f69 MDL-80043 grade: Add upgrade.txt notes 2023-12-01 09:15:04 +08:00
Jun Pataleta cc6b3c161f MDL-80043 gradereport_grader: Define editingmode_title lang string 2023-12-01 09:15:04 +08:00
Jun Pataleta 18e5768332 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:15:04 +08:00
Jun Pataleta fe3ff7dd69 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:15:03 +08:00
Jun Pataleta 816fadfcd7 MDL-80043 grade: Use heading parameter for the page title if provided 2023-12-01 09:15:03 +08:00
Helen Foster b136e428d0 MDL-80221 lang: Import fixed English strings (en_fix) 2023-11-30 19:14:48 +01:00
sam marshall f02437f915 MDL-55640 setuplib: test_localcachedir fails sporadically 2023-11-30 13:59:08 +00:00
Paul Holden de6c7d2975 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:13 +00:00
Mark Johnson 7fe281cc45 MDL-79806 quiz_statistics: Queue recalculation on attempt_deleted 2023-11-30 08:53:56 +00:00
Huong Nguyen a410d35ef4 Merge branch 'MDL-79816-401' of https://github.com/rmady/moodle into MOODLE_401_STABLE 2023-11-30 10:41:20 +07:00
Meirza f16b42d013 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:28 +07:00
Huong Nguyen c26e73fc9f Merge branch 'MDL-74378_401' of https://github.com/PhilippImhof/moodle into MOODLE_401_STABLE 2023-11-30 10:12:31 +07:00
Ilya Tregubov 752c0c77f8 Merge branch 'MDL-78849-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-11-30 10:38:54 +08:00
Ilya Tregubov b49d7c974f Merge branch 'MDL-79325-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-11-30 09:35:45 +08:00
Jun Pataleta 1773a1926d Merge branch 'MDL-79841-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-11-29 23:48:21 +08:00
Sara Arjona 0d056137bb Merge branch 'MDL-79275-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-11-29 16:44:20 +01:00
Jun Pataleta 6822ab321b Merge branch 'MDL-80234-401' of https://github.com/aanabit/moodle into MOODLE_401_STABLE 2023-11-29 23:40:47 +08:00
Sara Arjona d8f055240a Merge branch 'MDL-80150-401' of https://github.com/lucaboesch/moodle into MOODLE_401_STABLE 2023-11-29 13:34:52 +01:00
Jun Pataleta 7527ca4c49 Merge branch 'MDL-79148-401' of https://github.com/ssj365/moodle into MOODLE_401_STABLE 2023-11-29 15:20:11 +08:00
Huong Nguyen e6023839c9 Merge branch 'MDL-80002-401' of https://github.com/roland04/moodle into MOODLE_401_STABLE 2023-11-29 09:36:52 +07:00
Huong Nguyen 2cf4f88b52 Merge branch 'MDL-79267-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-11-29 09:18:34 +07:00
Huong Nguyen 08be365c2c Merge branch 'MDL-79512-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-11-29 09:09:21 +07:00
Huong Nguyen 5c13475743 Merge branch 'MDL-76694-401' of https://github.com/snake/moodle into MOODLE_401_STABLE 2023-11-29 09:01:52 +07:00
Jun Pataleta a2524d8caf Merge branch 'MDL-80181-401' of https://github.com/srobotta/moodle into MOODLE_401_STABLE 2023-11-29 09:37:35 +08:00
Sara Arjona aec53e3547 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 02:17:56 +01:00
Jeremy Schulz a272c2289f MDL-80180 admin: added readonly support for multicheckbox 2023-11-28 14:36:19 -05:00
Mark Johnson 055d5cb3b0 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:24:17 +00:00
Shamiso.Jaravaza 3d7b5d51b9 MDL-79148 mod_bigbluebuttonbn: fix recording link 2023-11-28 00:40:31 -07:00
Ilya Tregubov 6109f4dfd4 Merge branch 'MDL-79603-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-11-28 13:54:09 +08:00
Laurent David 57791ecd61 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:39:04 +01:00
Simey Lameze 3f83ea2456 MDL-79275 behat: add one second wait to avoid random 2023-11-28 11:07:20 +08:00
Amaia Anabitarte 471838be7b MDL-80234 mod_scorm: Saved passed status requirement 2023-11-27 17:41:15 +01:00
Stephan Robotta dfb2be423b MDL-80181 wiki: fix width of left column in comments 2023-11-24 11:18:01 +01:00
Philipp Imhof b554580520 MDL-74378 mod_quiz: make sure timer is initialized 2023-11-24 08:52:35 +01:00
Huong Nguyen b1bb5671df weekly release 4.1.6+ 2023-11-24 12:23:36 +07:00
Huong Nguyen cd5d14d73b Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-11-24 12:23:29 +07:00
Eloy Lafuente (stronk7) 98a58ef44c Merge branch 'MDL-79966-m401' of https://github.com/sammarshallou/moodle into MOODLE_401_STABLE 2023-11-23 23:41:28 +01:00
sam marshall 95646071ae MDL-79966 core_task: NEVER_RUN_TIME did not fit in 10 digits 2023-11-23 16:25:10 +00:00
Rodrigo Mady 29d68c07af MDL-79816 block_recentlyaccesseditems: Fix modname type in WS 2023-11-23 11:52:44 -03:00
Paul Holden 773bfa9272 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:38 +00:00
Andrew Nicols 2995721139 Merge branch 'MDL-79966-m401' of https://github.com/sammarshallou/moodle into MOODLE_401_STABLE 2023-11-23 10:32:05 +08:00
Huong Nguyen 6488fc6a20 Merge branch 'MDL-78496-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-11-23 09:23:46 +07:00
Ilya Tregubov 8c678d2b9a Merge branch 'MDL-80157_storedfileproblem-401' of https://github.com/leonstr/moodle into MOODLE_401_STABLE 2023-11-23 09:47:47 +08:00
Ilya Tregubov cedb8a92cb Merge branch 'MDL-79185-MOODLE_401_STABLE' of https://github.com/dmitriim/moodle into MOODLE_401_STABLE 2023-11-22 11:49:15 +08:00
Dmitrii Metelkin 402153c46e MDL-79185 cache: don't throw exception if cache data is corrupted 2023-11-22 12:47:57 +11:00
Ilya Tregubov 8337ef3be5 Merge branch 'MDL-78670-401' of https://github.com/rmady/moodle into MOODLE_401_STABLE 2023-11-22 09:11:44 +08:00
Paul Holden ecf396842f 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:33 +00:00
Mikel Martín bde37edf3c MDL-80015 course: Fix category 'More' dropdown alignment
Co-authored-by: Stephen Sharpe <stephen.sharpe@synergy-learning.com>
2023-11-21 14:15:45 +01:00
Angelia Dela Cruz 6aeecbdf46 MDL-79828 core_badges: Delete already awarded course badges 2023-11-21 17:45:47 +08:00
Amaia Anabitarte b7ebcf7e2c MDL-79979 tool_generator: Use auto-enrol admin in new courses setting 2023-11-21 08:44:05 +01:00
sam marshall 47a38fd827 MDL-79966 core_task: Scheduled task timing (crontab fields) is wrong 2023-11-20 17:08:55 +00:00
Rodrigo Mady 3089485e57 MDL-78670 mod_quiz: Add offline atempt rule for sequential navigation 2023-11-20 12:10:18 -03:00
Juan Olivares 15a9f6f119 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:28:33 +01:00
Laurent David dbd62ed4ea 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:29 +01:00
Laurent David 2dc6ab3567 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:29 +01:00
AMOS bot 8e31874a3a Automatically generated installer lang files 2023-11-20 00:10:36 +00:00
Leon Stringer f48edc0cd1 MDL-80157 file: Exception message spelling 2023-11-17 14:36:58 +00:00
Paul Holden b2bfd0dcad MDL-79325 group: consistent name formatting in overview page/export. 2023-11-17 09:48:17 +00:00
Sara Arjona 94ad185d09 weekly release 4.1.6+ 2023-11-16 16:22:47 +01:00
Sara Arjona f0dae38957 Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-11-16 16:22:42 +01:00
Paul Holden 9390787a34 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:32:04 +00:00
Huong Nguyen e302d0d902 Merge branch 'MDL-76457-401' of https://github.com/roland04/moodle into MOODLE_401_STABLE 2023-11-16 10:58:57 +07:00
Huong Nguyen 2f04edf37f Merge branch 'MDL-80066-401' of https://github.com/elza3ym/moodle into MOODLE_401_STABLE 2023-11-16 10:53:41 +07:00
Ilya Tregubov ded87e8d92 Merge branch 'MDL-80121-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-11-16 10:01:43 +08:00
Ilya Tregubov 8ddb933f5c Merge branch 'MDL-75616-401' of https://github.com/davewoloszyn/moodle into MOODLE_401_STABLE 2023-11-16 09:23:58 +08:00
Mohamed Shehata d09a390a05 MDL-80066 form: fix date picker on small screens 2023-11-15 23:46:48 +02:00
Sara Arjona e31d7bdb81 Merge branch 'MDL-79981-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-11-15 17:15:31 +01:00
Sara Arjona 246ad70ba9 Merge branch 'MDL-76937-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-11-15 16:34:21 +01:00
Sara Arjona 3f40af87e4 Merge branch 'MDL-80045-401' of https://github.com/roland04/moodle into MOODLE_401_STABLE 2023-11-15 13:47:21 +01:00
Sara Arjona a4a654348d Merge branch 'MDL-79956-401' of https://github.com/ssj365/moodle into MOODLE_401_STABLE 2023-11-15 13:32:03 +01:00
Jun Pataleta ed48b54542 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:30:26 +08:00
Jun Pataleta d8dbfe5d25 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:30:26 +08:00
Dan Marsden 9dd88a4988 MDL-64431 assignfeedback_editpdf: Flatten PDF files 2023-11-15 19:30:11 +13:00
Angelia Dela Cruz 358386e4f4 MDL-79603 mod_scorm: Behat coverage for Scorm package display options 2023-11-15 11:08:14 +08:00
Huong Nguyen 6724e13007 Merge branch 'MDL-79648-401' of https://github.com/SergioComeron/moodle into MOODLE_401_STABLE 2023-11-15 09:14:19 +07:00
Huong Nguyen 56276d997f Merge branch 'MDL-79601-401' of https://github.com/roland04/moodle into MOODLE_401_STABLE 2023-11-15 09:10:06 +07:00
Andrew Nicols ca345f8853 MDL-80079 core: Correct incorrect arg to format_text::$options 2023-11-15 09:59:55 +08:00
Mikel Martín b372929c9b MDL-76457 tool_usertours: Fix tour popovers in RTL 2023-11-14 21:27:48 +01:00
Mikel Martín 559db765b3 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:48 +01:00
Andrew Nicols 6179d2ba24 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 23:31:58 +08:00
Sara Arjona ba18db3c7c MDL-77581 lesson: Fix behat random failures 2023-11-14 16:25:02 +01:00
Andrew Nicols 61b5850a8c MDL-80079 core: Correct incorrect arg to format_string::$options 2023-11-14 22:50:36 +08:00
Mikel Martín 24f85416a7 MDL-80045 block_starredcourses: Fix cards alignment in small screens 2023-11-14 14:36:34 +01:00
Sara Arjona 69ce4b683d Merge branch 'MDL-77581-401' of https://github.com/dpalou/moodle into MOODLE_401_STABLE 2023-11-14 11:53:02 +01:00
Dani Palou f7ea9dd9cc MDL-77581 lesson: Fix generator unit tests in oracle 2023-11-14 11:08:01 +01:00
SergioComeron bce9cfdfd5 MDL-79648 core_course: 29 October repeating day 2023-11-13 18:35:44 +01:00
Sara Arjona 9ed81d3a8a Merge branch 'MDL-77581-401' of https://github.com/dpalou/moodle into MOODLE_401_STABLE 2023-11-13 17:20:44 +01:00
Mark Johnson 077465cf90 MDL-79806 mod_quiz: Implement new quiz_attempt_deleted callback 2023-11-13 15:13:15 +00:00
Paul Holden c7b9f2980e MDL-79692 enrol_meta: check for group validity when adding members. 2023-11-13 10:23:26 +00:00
Huong Nguyen 2524184384 Merge branch 'MDL-74129-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-11-13 10:43:55 +07:00
David Saylor 0a6b4bb15c MDL-75616 backup: Use standard temp directory for extraction 2023-11-13 11:08:33 +11:00
AMOS bot 99f9513b05 Automatically generated installer lang files 2023-11-12 00:10:04 +00:00
Sara Arjona 8974392cfa 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:34:23 +01:00
Ilya Tregubov 8ad9114050 weekly release 4.1.6+ 2023-11-10 09:17:25 +08:00
Dani Palou 5bda7cac35 MDL-77581 behat: Test create lesson pages using UI 2023-11-09 11:33:57 +01:00
Dani Palou 13ef307253 MDL-77581 behat: Apply new lesson cluster generators to existing tests 2023-11-09 11:33:57 +01:00
Dani Palou 0577c54bc0 MDL-77581 behat: Create generators for lesson clusters 2023-11-09 11:33:57 +01:00
Dani Palou cac02a15b6 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:09:55 +01:00
Dani Palou a979e0ce97 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:09:04 +01:00
Dani Palou cad097a605 MDL-77581 behat: Apply new lesson generators to existing tests 2023-11-09 11:09:01 +01:00
Dani Palou 40b918348c MDL-77581 behat: Create generators for lesson pages and answers 2023-11-09 11:08:08 +01:00
Dani Palou 1184d3c20f MDL-77581 behat: Support finish_generate_ in generators 2023-11-09 11:08:08 +01:00
Laurent David 711a9c0df5 MDL-76937 core_course: Delete button can be used twice
* Disable the Delete button in the confirmation dialog once
clicked so we avoid running the process twice by mistake.
2023-11-09 09:25:40 +01:00
Shamiso.Jaravaza e9b513d561 MDL-79956 mod_bigbluebuttonbn: fix import links 2023-11-08 21:46:29 -07:00
Michael Aherne d8e0799023 MDL-80030 filelib: Fix info type. 2023-11-09 11:14:07 +08:00
Ilya Tregubov 33fa895d25 Merge branch 'MDL-79992-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-11-09 09:58:03 +08:00
Sara Arjona 93ce6e33d9 Merge branch 'MDL-79827-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-11-08 18:10:55 +01:00
Sara Arjona 4018e95a55 Merge branch 'MDL-79781-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-11-08 17:20:15 +01:00
Sara Arjona 9667e96d89 Merge branch 'MDL-76555-401' of https://github.com/pmaneggia/moodle into MOODLE_401_STABLE 2023-11-08 17:06:31 +01:00
Mikel Martín a919969ae7 MDL-79601 report_completion: Fix RTL First/Last name alignment 2023-11-08 16:23:35 +01:00
Mikel Martín 71f3a87583 MDL-79601 report_progress: Fix RTL First/Last name alignment 2023-11-08 16:23:35 +01:00
Andrew Nicols a09ecad620 MDL-79981 core: Add ability to manually run and filter phpunit on GHA 2023-11-08 12:54:03 +08:00
Ilya Tregubov e0d1d8e469 Merge branch 'MDL-79615_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-11-08 10:44:27 +08:00
Paul Holden d3070236ac MDL-79267 mod_forum: define report subplugin type language strings. 2023-11-07 09:50:08 +00:00
Huong Nguyen 8d77dd09b8 Merge branch 'MDL-78953-401' of https://github.com/pedrojordao/moodle into MOODLE_401_STABLE 2023-11-07 15:34:25 +07:00
Jun Pataleta 1ea9832c3e Merge branch 'MDL-80019-401' of https://github.com/call-learning/moodle into MOODLE_401_STABLE 2023-11-07 13:13:48 +08:00
Laurent David 9eee64d0d5 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:12:13 +01:00
Sara Arjona 46f1b71c20 Merge branch 'mdl-78650-401-stable' of https://github.com/Fragonite/moodle into MOODLE_401_STABLE 2023-11-06 09:51:11 +01:00
Huong Nguyen 2ee8296efc Merge branch 'MDL-79062-401-2' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-11-06 10:52:57 +07:00
Paola Maneggia e2a7040d94 MDL-76555 mod_resource: Fix PHP8.0 type error (legacy resources) 2023-11-05 13:48:00 +01:00
Mikel Martín 06798fb898 MDL-80002 forumreport_summary: Fix summary report date filtering 2023-11-03 15:47:38 +01:00
Sara Arjona 8737f16faa 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:31 +01:00
Jake Dallimore 776319ac8e 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:26:49 +08:00
Pedro Jordao 76f66df235 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 19:05:33 -03:00
Jun Pataleta 4b302063c3 MDL-79062 behat: Add number type when guessing field type 2023-11-02 22:19:54 +08:00
Jun Pataleta ad4736b5ec 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 21:45:26 +08:00
Jun Pataleta 6637197d97 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 21:45:26 +08:00
Sara Arjona dd115e572a weekly release 4.1.6+ 2023-11-02 13:41:27 +01:00
Huong Nguyen 663eed6780 Merge branch 'MDL-78903-401' of https://github.com/meirzamoodle/moodle into MOODLE_401_STABLE 2023-11-02 10:19:12 +07:00
Huong Nguyen 883f98a3f2 Merge branch 'MDL-79801-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-11-02 10:15:40 +07:00
Huong Nguyen 0562c4d552 Merge branch 'MDL-79617_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-11-02 09:04:02 +07:00
Jun Pataleta 15409cfa41 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 09:51:39 +08:00
Alexander Van der Bellen 116eef7a25 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:28:00 +08:00
Alexander Van der Bellen 686d9cfd4c MDL-78650 mod_assign: Add Behat test 2023-11-01 20:27:54 +08:00
Alexander Van der Bellen dec259a249 MDL-78650 mod_assign: Add unit test 2023-11-01 20:27:47 +08:00
Daniel Neis Araujo f92753a3d8 MDL-73378 gradebook: Category grade null when all subgrades are null 2023-10-31 13:55:47 -03:00
Sara Arjona 3d5fa73133 Merge branch 'MDL-79856-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-10-31 13:26:45 +01:00
Huong Nguyen e4989b529a Merge branch 'MDL-79807_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-10-31 12:51:56 +08:00
Meirza f7b2fb473b MDL-78903 tiny_h5p: show repositories if editor filepicker type set 2023-10-31 08:03:26 +07:00
Meirza dc7da1a416 MDL-78903 tiny_media: show repositories if editor filepicker type set 2023-10-31 08:02:47 +07:00
Jun Pataleta c819926529 Merge branch 'MDL-77274-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-10-30 17:13:12 +08:00
Laurent David d20f1024d8 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:18:48 +01:00
Huong Nguyen 07c200438c Merge branch 'MDL-79639_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE 2023-10-30 11:24:02 +07:00
Jun Pataleta e146a9d8cd Merge branch 'MDL-79194-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-10-27 23:31:46 +08:00
Mark Johnson ef964137ce 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:52:09 +01:00
Jun Pataleta 650474c0a7 Merge branch 'MDL-77364-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-10-27 16:32:36 +08:00
Sara Arjona 6bc193134a 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:37 +02:00
Jun Pataleta 9dbbd31076 weekly release 4.1.6+ 2023-10-27 09:43:13 +08:00
Jun Pataleta 8317aa9b32 Merge branch 'MDL-79768-401-fix' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-10-26 23:05:33 +08:00
Mark Johnson 1944b58bd6 MDL-79807 quiz_statistics: Clear stats cache when question added/removed 2023-10-26 13:43:22 +01:00
Paul Holden 512670fe94 MDL-79781 course: detect invalid creator role for pending courses. 2023-10-26 13:18:41 +01:00
Paul Holden fc93938f23 MDL-79856 roles: use role localname when confirming deletion. 2023-10-26 12:01:05 +01:00
Ferran Recio 7d158f18d9 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 12:37:00 +02:00
Andrew Lyons c22b7c2f82 MDL-79194 core: Wrap combobox debounce in pending Promise
This addresses a random failure with the combobox search results where
the debounce causes the results to be shown, and then the same search
result is returned again, re-rendered, and replaced after Behat has
moved on.

Partly cherry-picked from MDL-78779.
2023-10-26 12:33:36 +02:00
Angelia Dela Cruz 8fa9f27c7e MDL-79768 fix: Fixed random failing Behat due to time difference 2023-10-26 18:00:00 +08:00
Jun Pataleta 91e083981d Merge branch 'MDL-79768-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-10-26 10:47:39 +08:00
Jun Pataleta 06ebac56c6 Merge branch 'MDL-42189-401' of https://github.com/davewoloszyn/moodle into MOODLE_401_STABLE 2023-10-26 10:32:02 +08:00
Jun Pataleta 9d9e03ef10 Merge branch 'MDL-75572-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-10-25 13:44:20 +08:00
Sara Arjona 05fdc29294 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:20 +02:00
Laurent David 38708efb50 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-24 15:11:17 +02:00
Paul Holden 39ffe174e7 MDL-79827 tiny_link: show repositories only if editor filepicker set. 2023-10-23 22:20:38 +01:00
Mark Johnson fa23ae702c MDL-79617 repository_upload: Close session early in upload process 2023-10-23 15:32:58 +01:00
Angelia Dela Cruz 2d9a6fa760 MDL-79768 mod_data: Test for student can see upcoming data activity 2023-10-23 08:47:04 +08:00
David Woloszyn 1652b5281e MDL-42189 gradereport_overview: Report hidden from suspended enrolments 2023-10-23 09:47:19 +11:00
Paul Holden d8e3c31b32 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:29 +01:00
Sara Arjona a985ac2e0c weekly release 4.1.6+ 2023-10-20 12:16:34 +02:00
Mark Johnson ec5d7c3227 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:58:41 +01:00
Huong Nguyen ccae21a639 Merge branch 'MDL-79689-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-10-19 10:05:37 +07:00
Sara Arjona 345d483ee2 Merge branch 'MDL-77651-401' of https://github.com/junpataleta/moodle into MOODLE_401_STABLE 2023-10-18 15:47:24 +02:00
Sara Arjona 4d9b533fd4 Merge branch 'MDL-79747_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-10-18 10:29:08 +02:00
Angelia Dela Cruz 174fc341d6 MDL-79689 mod_assign: Behat for enabling anonymous submissions 2023-10-18 10:58:34 +08:00
Huong Nguyen bcbab5517b Merge branch 'MDL-79705-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-10-18 09:37:35 +07:00
Huong Nguyen 0848bf8687 Merge branch 'MDL-78979-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-10-18 09:24:21 +07:00
Jun Pataleta 11e0579540 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:53:05 +08:00
Jun Pataleta cc9a537bd2 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:53:05 +08:00
Paul Holden 38909cd64e 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:34 +01:00
Paul Holden 62aa37f70d MDL-79705 profilefield_checkbox: display data as simple Yes/No. 2023-10-17 16:51:34 +01:00
Tim Hunt f6e7eb2ebd 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:43:48 +01:00
Sara Arjona c0fefd0a4d Merge branch 'MDL-78481-401' of https://github.com/iarenaza/moodle into MOODLE_401_STABLE 2023-10-17 17:15:21 +02:00
Ilya Tregubov b5599f33d0 Merge branch 'MDL-79691-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-10-17 10:29:08 +08:00
Huong Nguyen 5e1588ba63 Merge branch 'MDL-79367-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-10-17 09:18:33 +07:00
Paul Holden 50618adb13 MDL-78979 mod_data: correct restore decode rule for data view page. 2023-10-16 20:53:59 +01:00
Angelia Dela Cruz 1c023e7fbb MDL-79691 mod_h5pactivity: Behat coverage for H5P activity grades reset 2023-10-16 17:06:13 +08:00
Sara Arjona c6d8aca226 Merge branch 'MDL-79566-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2023-10-16 09:54:01 +02:00
Ilya Tregubov c4c514ee46 Merge branch 'MDL-79365-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-10-16 14:08:45 +08:00
Sara Arjona 6c956d3408 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:55:12 +02:00
Huong Nguyen 052e646cdc Merge branch 'MDL-79698-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-10-16 11:40:32 +07:00
Huong Nguyen f1166fe9e6 Merge branch 'MDL-79653-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-10-16 10:55:23 +07:00
Huong Nguyen 0d45970bd3 MDL-79566 tiny_autosave: Add Javascript submission handler 2023-10-16 09:29:28 +07:00
Paul Holden d160890cab MDL-79365 customfield_textarea: ensure field uniqueness is correct. 2023-10-13 14:30:08 +01:00
Andrew Nicols d6fdd2f5f9 MDL-79698 core: Correct duplicate array key in test provider 2023-10-13 15:44:33 +08:00
Ilya Tregubov d188d58ce9 weekly release 4.1.6+ 2023-10-13 13:39:33 +08:00
Angelia Dela Cruz 1f602aabec MDL-79653 gradingform_guide: Behat coverage for deleting marking guide 2023-10-13 11:11:08 +08:00
Huong Nguyen 0257e1696f Merge branch 'MDL-79638-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-10-12 11:08:05 +07:00
Ilya Tregubov 891b6f796c Merge branch 'MDL-79160-401' of https://github.com/danghieu1407/moodle into MOODLE_401_STABLE 2023-10-12 09:44:23 +08:00
Ilya Tregubov 9fbdf21341 Merge branch 'MDL-79422-401' of https://github.com/andrewnicols/moodle into MOODLE_401_STABLE 2023-10-12 09:10:23 +08:00
Jun Pataleta 7e39cee0f6 Merge branch 'MDL-79633-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-10-11 13:41:36 +08:00
Huong Nguyen 82488f901d Merge branch 'MDL-78103-401' of https://github.com/HuongNV13/moodle into MOODLE_401_STABLE 2023-10-11 11:26:02 +07:00
chen levy 7744cf2f28 MDL-78103 theme: Fixed dropdown menu size by the content 2023-10-11 11:15:44 +07:00
Andrew Nicols e7263580b9 MDL-79638 phpunit: Add unit test for increment resets 2023-10-11 00:10:33 +08:00
Andrew Nicols a74443a342 MDL-79638 phpunit: Adjust version checks for 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 the patch from there.

Moodle has required MySQL 5.7, and MariaDB 10.2.29 since Moodle 3.11 and
it is therefore safe to remove these hacks for these versions.
2023-10-11 00:02:12 +08:00
Sara Arjona 7ff2d18b93 Merge branch 'MDL-79623-MOODLE_401_STABLE' of https://github.com/PhMemmel/moodle into MOODLE_401_STABLE 2023-10-10 16:19:30 +02:00
Sara Arjona 66ff0a871f Merge branch 'MDL-77816-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-10-10 09:21:39 +02:00
Ilya Tregubov 11fe89c08e Merge branch 'MDL-79501-MOODLE_401_STABLE' of https://github.com/geichelberger/moodle into MOODLE_401_STABLE 2023-10-10 15:07:24 +08:00
Ilya Tregubov 76bd425474 Merge branch 'MDL-79279_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-10-10 11:24:47 +08:00
Ilya Tregubov aeeaea6840 Merge branch 'MDL-71414-401' of https://github.com/laurentdavid/moodle into MOODLE_401_STABLE 2023-10-10 10:19:44 +08:00
Paul Holden 5c30074d99 MDL-79633 mod_data: correct timemodified tag replacement attribute. 2023-10-09 19:41:12 +01:00
Laurent David 8e53ef0162 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:45:11 +02:00
Laurent David 2a2d3c85bb 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:45:10 +02:00
Philipp Memmel 805577664b MDL-79623 tool_componentlibrary: Fix correct modal content attribute 2023-10-09 09:14:48 +00:00
danghieu1407 cf9373c4dd MDL-79160 Question: Answer field overflows parent width with large size 2023-10-05 11:06:21 +07:00
Paul Holden 52ada82da2 MDL-77816 mod_bigbluebuttonbn: feedback when validating completion. 2023-10-04 21:07:31 +01:00
Yannick Schillinger fca2456e1f MDL-77816 mod_bigbluebuttonbn: Fix completionValidate argument format 2023-10-04 20:27:34 +01:00
Tim Hunt 848fd484d5 MDL-79279 qformat_xml: multianswer import should support idnumber
Fix suggested by Georg Maißer.
2023-10-03 20:43:15 +01:00
Gregor Eichelberger 0b28c6a254 MDL-79501 tiny: Fix editing multiple videos
The selection gets lost while opening the modal dialogue to update an
embedded media. Caching the current selection allows us to update the
previously selected node instead of updating the first embedded media.

Signed-off-by: Gregor Eichelberger <gregor.eichelberger@tuwien.ac.at>
2023-09-29 14:11:43 +02:00
Andrew Nicols b8f45d20fd MDL-79422 ajax: Allow no-login WS calls to fetch after a failure 2023-09-20 16:02:31 +08:00
Iñaki Arenaza e7817d0d55 MDL-78481 mod_lesson: matching pagetype applying filters fix 2023-07-31 16:25:59 +02:00
370 changed files with 8028 additions and 3233 deletions
+7 -1
View File
@@ -7,6 +7,12 @@ on:
- MOODLE_[0-9]+_STABLE
tags-ignore:
- v[0-9]+.[0-9]+.[0-9]+*
workflow_dispatch:
inputs:
phpunit_extra_options:
description: Additional options to apply to PHPUnit
required: false
default: ''
env:
php: 8.1
@@ -113,4 +119,4 @@ jobs:
env:
dbtype: ${{ matrix.db }}
phpunit_options: ${{ secrets.phpunit_options }}
run: vendor/bin/phpunit $phpunit_options
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
+2 -2
View File
@@ -80,8 +80,8 @@ if (!$category = $DB->get_record('course_categories', ['id' => $options['categor
throw new \moodle_exception('invalidcategoryid');
}
$backupdir = "restore_" . uniqid();
$path = $CFG->tempdir . DIRECTORY_SEPARATOR . "backup" . DIRECTORY_SEPARATOR . $backupdir;
$backupdir = restore_controller::get_tempdir_name(SITEID, $USER->id);
$path = make_backup_temp_directory($backupdir);
cli_heading(get_string('extractingbackupfileto', 'backup', $path));
$fp = get_file_packer('application/vnd.moodle.backup');
+191
View File
@@ -4305,4 +4305,195 @@
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="4.4" requires="4.1.2">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="10.6.7" />
<VENDOR name="mysql" version="8.0" />
<VENDOR name="postgres" version="13" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="oracle" version="19" />
</DATABASE>
<PHP version="8.1.0" level="required">
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<ON_CHECK message="pcreunicodewarning" />
</FEEDBACK>
</PCREUNICODE>
<PHP_EXTENSIONS>
<PHP_EXTENSION name="iconv" level="required">
<FEEDBACK>
<ON_ERROR message="iconvrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="mbstring" level="required">
<FEEDBACK>
<ON_ERROR message="mbstringrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="curl" level="required">
<FEEDBACK>
<ON_ERROR message="curlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="openssl" level="required">
<FEEDBACK>
<ON_ERROR message="opensslrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="tokenizer" level="optional">
<FEEDBACK>
<ON_CHECK message="tokenizerrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="soap" level="optional">
<FEEDBACK>
<ON_CHECK message="soaprecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="ctype" level="required">
<FEEDBACK>
<ON_ERROR message="ctyperequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zip" level="required">
<FEEDBACK>
<ON_ERROR message="ziprequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zlib" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="gd" level="required">
<FEEDBACK>
<ON_ERROR message="gdrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="simplexml" level="required">
<FEEDBACK>
<ON_ERROR message="simplexmlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="spl" level="required">
<FEEDBACK>
<ON_ERROR message="splrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="pcre" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="dom" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xml" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlreader" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="intl" level="required">
<FEEDBACK>
<ON_ERROR message="intlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="json" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="hash" level="required"/>
<PHP_EXTENSION name="fileinfo" level="required"/>
<PHP_EXTENSION name="sodium" level="required"/>
<PHP_EXTENSION name="exif" level="optional"/>
</PHP_EXTENSIONS>
<PHP_SETTINGS>
<PHP_SETTING name="memory_limit" value="96M" level="required">
<FEEDBACK>
<ON_ERROR message="settingmemorylimit" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="file_uploads" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="settingfileuploads" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="opcache.enable" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="opcacherecommended" />
</FEEDBACK>
</PHP_SETTING>
</PHP_SETTINGS>
<CUSTOM_CHECKS>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbstorageengine" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
<FEEDBACK>
<ON_ERROR message="quizattemptsupgradedmessage" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
<FEEDBACK>
<ON_CHECK message="slashargumentswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
<FEEDBACK>
<ON_CHECK message="unsupporteddbtablerowformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
<FEEDBACK>
<ON_CHECK message="unoconvwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
<FEEDBACK>
<ON_CHECK message="libcurlwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfileformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfilepertable" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddblargeprefix" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
<FEEDBACK>
<ON_CHECK message="ishttpswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
<FEEDBACK>
<ON_CHECK message="incompleteunicodesupport" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="required">
<FEEDBACK>
<ON_ERROR message="sixtyfourbitsrequired" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
<FEEDBACK>
<ON_CHECK message="iscustomadminwarnings" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+1 -1
View File
@@ -76,7 +76,7 @@ switch ($action) {
$optionsyes = array('action'=>'delete', 'roleid'=>$roleid, 'sesskey'=>sesskey(), 'confirm'=>1);
$a = new stdClass();
$a->id = $roleid;
$a->name = $roles[$roleid]->name;
$a->name = $roles[$roleid]->localname;
$a->shortname = $roles[$roleid]->shortname;
$a->count = $DB->count_records_select('role_assignments',
'roleid = ?', array($roleid), 'COUNT(DISTINCT userid)');
@@ -23,12 +23,15 @@
* name - form element name
* hasoptions - there are some options
* options - list of options containing name, key, id, checked, label
* readonly - bool
Example context (json):
{
"name": "test",
"hasoptions": true,
"options": [ { "name": "Option", "key": "k1", "id": "id0", "checked": true, "label": "Option label"} ]
"options": [ { "name": "Option", "key": "k1", "id": "id0", "checked": true, "label": "Option label"} ],
"readonly": false
}
}}
<div class="form-multicheckbox">
@@ -37,7 +40,7 @@
<ul>
{{#options}}
<li>
<input type="checkbox" name="{{name}}[{{key}}]" value="1" id="{{id}}_{{key}}" {{#checked}}checked{{/checked}}>
<input type="checkbox" name="{{name}}[{{key}}]" value="1" id="{{id}}_{{key}}" {{#readonly}}disabled{{/readonly}} {{#checked}}checked{{/checked}}>
<label for="{{id}}_{{key}}">{{{label}}}</label>
</li>
{{/options}}
+34
View File
@@ -0,0 +1,34 @@
@tool_behat
Feature: Confirm that we can open multiple browser tabs
In order to use multiple browser tabs
As a test writer
I need the relevant Behat steps to work
@javascript @_switch_window
Scenario: Open multiple browser tabs
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
| Course 3 | C3 |
And I am on the "C1" "Course" page logged in as "admin"
# Open a new tab on the same page.
When I open a tab named "CourseViewer1" on the current page
And I should see "Course 1" in the "h1" "css_element"
And I am on the "C2" "Course" page
# Open new tab for specified page with identifier.
And I open a tab named "CourseViewer2" on the "C3" "Course" page
# And for a specified page without identifier.
And I open a tab named "CourseViewer4" on the "My courses" page
# Switch between all the tabs and confirm their different contents.
Then I should see "No courses"
And I switch to "CourseViewer2" tab
And I should see "Course 3" in the "h1" "css_element"
And I switch to "CourseViewer1" tab
And I should see "Course 2" in the "h1" "css_element"
And I switch to the main tab
And I should see "Course 1" in the "h1" "css_element"
@@ -170,6 +170,19 @@ class brickfield_accessibility_color_test extends brickfield_accessibility_test
'yellowgreen' => '9acd32'
];
/** @var string[] Define estimated relative font-size codes to pt values. */
public $fontsizenames = [
'xx-small' => 9,
'x-small' => 10,
'small' => 11,
'smaller' => 11,
'medium' => 12,
'large' => 14,
'larger' => 14,
'x-large' => 18,
'xx-large' => 24,
];
/**
* Helper method that finds the luminosity between the provided
* foreground and background parameters.
@@ -227,7 +240,8 @@ class brickfield_accessibility_color_test extends brickfield_accessibility_test
$l2 = (.2126 * $r4 + 0.7152 * $g4 + 0.0722 * $b4);
}
$luminosity = round(($l1 + 0.05) / ($l2 + 0.05), 2);
// Increase round to 4 to avoid a 4.49 contrast being round up to a false pass of 4.5.
$luminosity = round(($l1 + 0.05) / ($l2 + 0.05), 4);
return $luminosity;
}
@@ -362,4 +376,35 @@ class brickfield_accessibility_color_test extends brickfield_accessibility_test
: $backrgb['b'] - $forergb['b'];
return ['red' => $reddiff, 'green' => $greendiff, 'blue' => $bluediff];
}
/**
* Helper method that finds the estimated font-size for the provided
* string font-size parameter.
* @param string $fontsize The css font-size, in various formats
* @return int The estimated font-size
*/
public function get_fontsize(string $fontsize): int {
$newfontsize = 12; // Default value, in pt, equivalent to 16px.
// Search for rem, em, and px initially, typical font-size values.
$pos1 = stripos($fontsize, 'rem');
$pos2 = stripos($fontsize, 'em');
$pos3 = stripos($fontsize, 'px');
if ($pos1 !== false) {
$rem = substr($fontsize, 0, -3);
$newfontsize = $newfontsize * $rem;
} else if ($pos2 !== false) {
$em = substr($fontsize, 0, -2);
$newfontsize = $newfontsize * $em;
} else if ($pos3 !== false) {
$px = substr($fontsize, 0, -2);
$newfontsize = 0.75 * $px;
} else if (in_array($fontsize, array_keys($this->fontsizenames))) {
$newfontsize = $this->fontsizenames[$fontsize];
} else {
preg_match_all('!\d+!', $fontsize, $matches);
$newfontsize = $matches[0][0] ?? $newfontsize;
}
return (int) $newfontsize;
}
}
@@ -92,8 +92,7 @@ class css_text_has_contrast extends brickfield_accessibility_color_test {
$italic = false;
if (isset($style['font-size'])) {
preg_match_all('!\d+!', $style['font-size'], $matches);
$fontsize = $matches[0][0];
$fontsize = $this->get_fontsize($style['font-size']);
}
if (isset($style['font-weight'])) {
@@ -178,6 +178,54 @@ EOD;
</html>
EOD;
/** @var string HTML with px18 fail colour values. */
private $px18 = <<<EOD
<body><p style="color:#EF0000; background-color:white; font-size: 18px">
This is not contrasty enough.</p></body>
EOD;
/** @var string HTML with px19bold pass colour values. */
private $px19bold = <<<EOD
<body><p style="color:#EF0000; background-color:white; font-size: 19px; font-weight: bold;">
This is contrasty enough.</p></body>
EOD;
/** @var string HTML with px18 pass colour values. */
private $px18pass = <<<EOD
<body><p style="color:#E60000; background-color:white; font-size: 18px">
This is contrasty enough.</p></body>
EOD;
/** @var string HTML with medium size colour values. */
private $mediumfail = <<<EOD
<body><p style="color:#EF0000; background-color:white; font-size: medium">
This is not contrasty enough.</p></body>
EOD;
/** @var string HTML with px18 colour values. */
private $mediumpass = <<<EOD
<body><p style="color:#E60000; background-color:white; font-size: medium">
This is contrasty enough.</p></body>
EOD;
/** @var string HTML with larger fail colour values. */
private $largerfail = <<<EOD
<body><p style="color:#FF6161; background-color:white; font-size: larger">
This is not contrasty enough.</p></body>
EOD;
/** @var string HTML with px18 colour values. */
private $largerpass = <<<EOD
<body><p style="color:#FF5C5C; background-color:white; font-size: larger;">
This is contrasty enough.</p></body>
EOD;
/** @var string HTML with px18 colour values. */
private $largerboldpass = <<<EOD
<body><p style="color:#FF5C5C; background-color:white; font-size: larger; font-weight: bold;">
This is contrasty enough.</p></body>
EOD;
/**
* Test for the area assign intro
*/
@@ -235,4 +283,68 @@ EOD;
$results = $this->get_checker_results($this->emptyvalue);
$this->assertEmpty($results);
}
/**
* Test for text px18 with insufficient contrast of 4.49.
*/
public function test_check_for_px18_fail() {
$results = $this->get_checker_results($this->px18);
$this->assertTrue($results[0]->element->tagName == 'p');
}
/**
* Test for text px19 bold with sufficient contrast of 4.49.
*/
public function test_check_for_px19bold_pass() {
$results = $this->get_checker_results($this->px19bold);
$this->assertEmpty($results);
}
/**
* Test for text px18 with sufficient contrast of 4.81.
*/
public function test_check_for_px18_pass() {
$results = $this->get_checker_results($this->px18pass);
$this->assertEmpty($results);
}
/**
* Test for medium (12pt) text with insufficient contrast of 4.49.
*/
public function test_check_for_medium_fail() {
$results = $this->get_checker_results($this->mediumfail);
$this->assertTrue($results[0]->element->tagName == 'p');
}
/**
* Test for medium (12pt) text with sufficient contrast of 4.81.
*/
public function test_check_for_medium_pass() {
$results = $this->get_checker_results($this->mediumpass);
$this->assertEmpty($results);
}
/**
* Test for larger (14pt) text with insufficient contrast of 2.94.
*/
public function test_check_for_larger_fail() {
$results = $this->get_checker_results($this->largerfail);
$this->assertTrue($results[0]->element->tagName == 'p');
}
/**
* Test for larger (14pt) text with insufficient contrast of 3.02.
*/
public function test_check_for_larger_pass() {
$results = $this->get_checker_results($this->largerpass);
$this->assertTrue($results[0]->element->tagName == 'p');
}
/**
* Test for larger (14pt) bold text with sufficient contrast of 3.02.
*/
public function test_check_for_largerbold_pass() {
$results = $this->get_checker_results($this->largerboldpass);
$this->assertEmpty($results);
}
}
@@ -16,7 +16,7 @@ to the element that will trigger the confirmation modal.
## Source files
* `lib/amd/src/confirm.js` ({{< jsdoc module="core/confirm" >}})
* `lib/amd/src/utility.js` ({{< jsdoc module="core/utility" >}})
* `lib/templates/modal.mustache`
## Usage
@@ -24,7 +24,7 @@ The confirmation AMD module is loaded automatically, so the only thing you need
to the target element:
{{< highlight html >}}
<button type="button" class="btn btn-primary" data-confirmation="modal" data-confirmation-title-str='["delete", "core"]'
data-confirmation-question-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]'>Show confirmation modal</button>
data-confirmation-content-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]'>Show confirmation modal</button>
{{< /highlight >}}
You can also use it on PHP, you just need to set the attributes parameter to any moodle output component that takes attributes:
@@ -32,7 +32,7 @@ You can also use it on PHP, you just need to set the attributes parameter to any
echo $OUTPUT->single_button('#', get_string('delete'), 'get', [
'data-confirmation' => 'modal',
'data-confirmation-title-str' => json_encode(['delete', 'core']),
'data-confirmation-question-str' => json_encode(['areyousure']),
'data-confirmation-content-str' => json_encode(['areyousure']),
'data-confirmation-yes-button-str' => json_encode(['delete', 'core'])
]);
{{< / php >}}
@@ -56,8 +56,8 @@ echo $OUTPUT->single_button('#', get_string('delete'), 'get', [
<td>The modal title language string identifier, must be provided in JSON encoded format.</td>
</tr>
<tr>
<td>data-confirmation-question-str</td>
<td>The confirmation question language string identifier, must be provided in JSON encoded format.</td>
<td>data-confirmation-content-str</td>
<td>The confirmation modal main content language string identifier, must be provided in JSON encoded format.</td>
</tr>
<tr>
<td>data-confirmation-yes-button-str</td>
@@ -84,14 +84,14 @@ echo $OUTPUT->single_button('#', get_string('delete'), 'get', [
{{< example >}}
<button type="button" class="btn btn-primary" data-confirmation="modal" data-confirmation-title-str='["delete", "core"]'
data-confirmation-question-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]'>Show confirmation modal</button>
data-confirmation-content-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]'>Show confirmation modal</button>
{{< /example >}}
### Confirmation modal with a toast
{{< example >}}
<button type="button" class="btn btn-primary" data-confirmation="modal" data-confirmation-title-str='["delete", "core"]'
data-confirmation-question-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]' data-confirmation-toast="true"
data-confirmation-content-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]' data-confirmation-toast="true"
data-confirmation-toast-confirmation-str='["deleteblockinprogress", "block", "Online users"]'>Show confirmation modal</button>
{{< /example >}}
@@ -99,6 +99,6 @@ data-confirmation-toast-confirmation-str='["deleteblockinprogress", "block", "On
{{< example >}}
<button type="button" class="btn btn-primary" data-confirmation="modal" data-confirmation-title-str='["delete", "core"]'
data-confirmation-question-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]'
data-confirmation-content-str='["areyousure"]' data-confirmation-yes-button-str='["delete", "core"]'
data-confirmation-destination="http://moodle.com">Show confirmation modal</button>
{{< /example >}}
@@ -205,7 +205,7 @@ class tool_generator_course_backend extends tool_generator_backend {
* @return int Course id
*/
public function make() {
global $DB, $CFG;
global $DB, $CFG, $USER;
require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
raise_memory_limit(MEMORY_EXTRA);
@@ -231,6 +231,12 @@ class tool_generator_course_backend extends tool_generator_backend {
$this->create_users();
$this->create_forum();
// We are checking 'enroladminnewcourse' setting to decide to enrol admins or not.
if (!empty($CFG->creatornewroleid) && !empty($CFG->enroladminnewcourse) && is_siteadmin($USER->id)) {
// Deal with course creators - enrol them internally with default role.
enrol_try_internal_enrol($this->course->id, $USER->id, $CFG->creatornewroleid);
}
// Log total time.
$this->log('coursecompleted', round(microtime(true) - $entirestart, 1));
@@ -0,0 +1,33 @@
@core @core_course @tool_generator
Feature: Admins can create test courses
In order to create testing information
As an admin
I need to create testing courses quickly
@javascript
Scenario: 'Auto-enrol admin in new courses' setting when creating a test course as admin
Given I log in as "admin"
And the following config values are set as admin:
| enroladminnewcourse | 0 |
And I navigate to "Development > Make test course" in site administration
And I set the following fields to these values:
| Size of course | XS |
| Course full name | Fake course for testing |
| Course short name | fake |
And I press "Create course"
And I click on "Continue" "link"
And I navigate to course participants
Then I should not see "Teacher"
And I should not see "Nothing to display"
And the following config values are set as admin:
| enroladminnewcourse | 1 |
And I navigate to "Courses > Add a new course" in site administration
And I navigate to "Development > Make test course" in site administration
And I set the following fields to these values:
| Size of course | XS |
| Course full name | New fake course for testing |
| Course short name | newfake |
And I press "Create course"
And I click on "Continue" "link"
And I navigate to course participants
And I should see "Teacher"
@@ -68,9 +68,14 @@ class maketestcourse_test extends \advanced_testcase {
$this->assertEquals(2, count($modinfo->get_section_info_all()));
// Check user is enrolled.
// enroladminnewcourse is enabled by default, so admin is also enrolled as teacher.
$users = get_enrolled_users($context);
$this->assertEquals(1, count($users));
$this->assertEquals('tool_generator_000001', reset($users)->username);
$this->assertEquals(2, count($users));
$usernames = array_map(function($user) {
return $user->username;
}, $users);
$this->assertTrue(in_array('admin', $usernames));
$this->assertTrue(in_array('tool_generator_000001', $usernames));
// Check there's a page on the course.
$pages = $modinfo->get_instances_of('page');
+1 -1
View File
@@ -75,7 +75,7 @@ trait reader {
*/
public static function decode_other(?string $other) {
if ($other === 'N;' || preg_match('~^.:~', $other ?? '')) {
return unserialize($other);
return unserialize($other, ['allowed_classes' => [stdClass::class]]);
} else {
return json_decode($other ?? '', true);
}
+10
View File
@@ -37,3 +37,13 @@ function tool_task_status_checks() : array {
];
}
/**
* Function used to handle mtrace by outputting the text to normal browser window.
*
* @param string $message Message to output
* @param string $eol End of line character
*/
function tool_task_mtrace_wrapper(string $message, string $eol): void {
$message = s($message);
echo $message . $eol;
}
+1 -10
View File
@@ -30,16 +30,6 @@ require('../../../config.php');
require_once($CFG->libdir.'/cronlib.php');
/**
* Function used to handle mtrace by outputting the text to normal browser window.
*
* @param string $message Message to output
* @param string $eol End of line character
*/
function tool_task_mtrace_wrapper($message, $eol) {
echo s($message . $eol);
}
// Allow execution of single task. This requires login and has different rules.
$taskname = required_param('task', PARAM_RAW_TRIMMED);
@@ -97,6 +87,7 @@ require_sesskey();
// Prepare to handle output via mtrace.
echo html_writer::start_tag('pre');
require('lib.php');
$CFG->mtrace_wrapper = 'tool_task_mtrace_wrapper';
// Run the specified task (this will output an error if it doesn't exist).
+5 -1
View File
@@ -236,6 +236,8 @@ class form_test extends \advanced_testcase {
$checker->set_day_of_week('6');
$this->assertTrue($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('7');
$this->assertTrue($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('8');
$this->assertFalse($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('20');
$this->assertFalse($checker->is_valid($checker::FIELD_DAYOFWEEK));
@@ -247,7 +249,7 @@ class form_test extends \advanced_testcase {
$checker->set_day_of_week('*/6');
$this->assertTrue($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('*/7');
$this->assertFalse($checker->is_valid($checker::FIELD_DAYOFWEEK));
$this->assertTrue($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('*/13');
$this->assertFalse($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('*/35');
@@ -271,6 +273,8 @@ class form_test extends \advanced_testcase {
$checker->set_day_of_week('65-2');
$this->assertFalse($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('3-7');
$this->assertTrue($checker->is_valid($checker::FIELD_DAYOFWEEK));
$checker->set_day_of_week('3-8');
$this->assertFalse($checker->is_valid($checker::FIELD_DAYOFWEEK));
}
}
+1 -1
View File
@@ -89,7 +89,7 @@ class login implements renderable, templatable {
$languagedata = new \core\output\language_menu($PAGE);
$this->languagemenu = $languagedata->export_for_action_menu($OUTPUT);
$this->canloginasguest = $CFG->guestloginbutton and !isguestuser();
$this->canloginasguest = $CFG->guestloginbutton && !isguestuser();
$this->canloginbyemail = !empty($CFG->authloginviaemail);
$this->cansignup = $CFG->registerauth == 'email' || !empty($CFG->registerauth);
if ($CFG->rememberusername == 0) {
@@ -149,15 +149,23 @@ class condition extends \core_availability\condition {
$allow = true;
if ($this->expectedcompletion == COMPLETION_COMPLETE) {
// Complete also allows the pass, fail states.
// Complete also allows the pass state.
switch ($completiondata->completionstate) {
case COMPLETION_COMPLETE:
case COMPLETION_COMPLETE_FAIL:
case COMPLETION_COMPLETE_PASS:
break;
default:
$allow = false;
}
} else if ($this->expectedcompletion == COMPLETION_INCOMPLETE) {
// Incomplete also allows the fail state.
switch ($completiondata->completionstate) {
case COMPLETION_INCOMPLETE:
case COMPLETION_COMPLETE_FAIL:
break;
default:
$allow = false;
}
} else {
// Other values require exact match.
if ($completiondata->completionstate != $this->expectedcompletion) {
@@ -74,3 +74,48 @@ Feature: availability_completion
And I click on "forum 1" "link" in the "region-main" "region"
And I am on "Course 1" course homepage
And I should see "Page 2" in the "region-main" "region"
@javascript
Scenario Outline: Restrict access for activity completion should display correctly
Given the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
And the following "activities" exist:
| activity | name | course | idnumber | gradepass | completion | completionpassgrade | completionusegrade |
| quiz | Test quiz name | C1 | quiz1 | 5.00 | 2 | 1 | 1 |
And quiz "Test quiz name" contains the following questions:
| question | page |
| First question | 1 |
And I am on the "Page 2" "page activity editing" page logged in as "teacher1"
And I expand all fieldsets
And I press "Add restriction..."
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I click on ".availability-item .availability-eye img" "css_element"
And I set the following fields to these values:
| Required completion status | <condition> |
| cm | quiz |
And I press "Save and return to course"
And I am on the "Course 1" "course" page logged in as "student1"
And I <shouldornot> see "Page 2" in the "region-main" "region"
# Failed grade for quiz.
When user "student1" has attempted "Test quiz name" with responses:
| slot | response |
| 1 | <answer1> |
And I reload the page
And I <shouldornotanswer1> see "Page 2" in the "region-main" "region"
# Passing grade for quiz.
But user "student1" has attempted "Test quiz name" with responses:
| slot | response |
| 1 | <answer2> |
And I reload the page
And I <shouldornotanswer2> see "Page 2" in the "region-main" "region"
Examples:
| condition | answer1 | answer2 | shouldornot | shouldornotanswer1 | shouldornotanswer2 |
| must be marked complete | False | True | should not | should not | should |
| must not be marked complete | False | True | should | should | should not |
| must be complete with pass grade | False | True | should not | should not | should |
| must be complete with fail grade | False | True | should not | should | should not |
@@ -301,14 +301,14 @@ class condition_test extends \advanced_testcase {
$cond = new condition((object)[
'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE
]);
$this->assertFalse($cond->is_available(false, $info, true, $user->id));
$this->assertTrue($cond->is_available(true, $info, true, $user->id));
$this->assertTrue($cond->is_available(false, $info, true, $user->id));
$this->assertFalse($cond->is_available(true, $info, true, $user->id));
$cond = new condition((object)[
'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE
]);
$this->assertTrue($cond->is_available(false, $info, true, $user->id));
$this->assertFalse($cond->is_available(true, $info, true, $user->id));
$this->assertFalse($cond->is_available(false, $info, true, $user->id));
$this->assertTrue($cond->is_available(true, $info, true, $user->id));
$cond = new condition((object)[
'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS
@@ -531,10 +531,10 @@ class condition_test extends \advanced_testcase {
],
// Depending on assign with grade.
'Previous complete condition with previous fail grade' => [
40, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~'
40, COMPLETION_COMPLETE, '', 'page3', false, true, '~Assign!.*is marked complete~',
],
'Previous incomplete condition with previous fail grade' => [
40, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~'
40, COMPLETION_INCOMPLETE, '', 'page3', true, false, '~Assign!.*is incomplete~',
],
'Previous complete pass condition with previous fail grade' => [
40, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~'
+7 -1
View File
@@ -1602,7 +1602,13 @@ class backup_block_instance_structure_step extends backup_structure_step {
// Transform configdata information if needed (process links and friends)
$blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
$configdata = (array)unserialize(base64_decode($blockrec->configdata));
$configdata = array_filter(
(array) unserialize_object(base64_decode($blockrec->configdata)),
static function($value): bool {
return !($value instanceof __PHP_Incomplete_Class);
}
);
foreach ($configdata as $attribute => $value) {
if (in_array($attribute, $attrstotransform)) {
$configdata[$attribute] = $this->contenttransformer->process($value);
+7 -1
View File
@@ -4340,7 +4340,13 @@ class restore_block_instance_structure_step extends restore_structure_step {
// Let's look for anything within configdata neededing processing
// (nulls and uses of legacy file.php)
if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
$configdata = (array) unserialize_object(base64_decode($data->configdata));
$configdata = array_filter(
(array) unserialize_object(base64_decode($data->configdata)),
static function($value): bool {
return !($value instanceof __PHP_Incomplete_Class);
}
);
foreach ($configdata as $attribute => $value) {
if (in_array($attribute, $attrstotransform)) {
$configdata[$attribute] = $this->contentprocessor->process_cdata($value);
+5 -1
View File
@@ -160,12 +160,16 @@ if ($context->contextlevel == CONTEXT_MODULE) {
$renderer = $PAGE->get_renderer('core', 'backup');
echo $renderer->backup_files_viewer($treeview_options);
echo $OUTPUT->container_end();
// Update the course context with the proper value, because $context contains the module context.
$coursecontext = \context_course::instance($course->id);
} else {
$coursecontext = $context;
}
echo $OUTPUT->heading_with_help(get_string('choosefilefromcoursebackup', 'backup'), 'choosefilefromcoursebackup', 'backup');
echo $OUTPUT->container_start();
$treeview_options = array();
$treeview_options['filecontext'] = $context;
$treeview_options['filecontext'] = $coursecontext;
$treeview_options['currentcontext'] = $context;
$treeview_options['component'] = 'backup';
$treeview_options['context'] = $context;
+5 -8
View File
@@ -14,20 +14,17 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
use \core_badges\badge;
/**
* Local stuff for category enrolment plugin.
* Event observer for badges.
*
* @package core_badges
* @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \core_badges\badge;
/**
* Event observer for badges.
*/
class core_badges_observer {
/**
* Triggered when 'course_module_completion_updated' event is triggered.
+9 -2
View File
@@ -188,8 +188,6 @@ class award_criteria_activity extends award_criteria {
* @return bool Whether criteria is complete
*/
public function review($userid, $filtered = false) {
$completionstates = array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL);
if ($this->course->startdate > time()) {
return false;
}
@@ -209,6 +207,15 @@ class award_criteria_activity extends award_criteria {
$check_date = ($date <= $param['bydate']);
}
// Successfull completion states depend on the completion settings.
if (isset($data->passgrade)) {
// Passing grade is required. Don't issue a badge when state is COMPLETION_COMPLETE_FAIL.
$completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS];
} else {
// Any grade is required. Issue a badge even when state is COMPLETION_COMPLETE_FAIL.
$completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL];
}
if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
if (in_array($data->completionstate, $completionstates) && $check_date) {
$overall = true;
+2
View File
@@ -54,6 +54,8 @@ $badge = new badge($badgeid);
$context = $badge->get_context();
$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
require_capability('moodle/badges:viewawarded', $context);
if ($badge->type == BADGE_TYPE_COURSE) {
if (empty($CFG->badges_allowcoursebadges)) {
throw new \moodle_exception('coursebadgesdisabled', 'badges');
+78 -71
View File
@@ -1,14 +1,14 @@
@mod @mod_quiz @core @core_badges @javascript
@mod @mod_quiz @core @core_badges @core_completion @javascript
Feature: Award badges based on activity completion
In order to ensure a student has learned the material before being marked complete
As a teacher
I need to set a quiz to award a badge when upon completion when the student receives a passing grade, or completed_fail if they use all attempts without passing
I need to configure an activity to grant a badge only when the student achieves a passing grade upon completion.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 1 | student2@example.com |
| student2 | Student | 2 | student2@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion |
@@ -27,96 +27,103 @@ Feature: Award badges based on activity completion
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
And the following "activities" exist:
| activity | name | course | idnumber | attempts | gradepass | completion | completionattemptsexhausted | completionpassgrade | completionusegrade |
| quiz | Test quiz name | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 | 1 |
And quiz "Test quiz name" contains the following questions:
| activity | name | course | idnumber | attempts | gradepass | completion | completionpassgrade | completionusegrade |
| quiz | Test quiz name 1 | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 |
| quiz | Test quiz name 2 | C1 | quiz2 | 2 | 5.00 | 2 | 0 | 1 |
And quiz "Test quiz name 1" contains the following questions:
| question | page |
| First question | 1 |
And quiz "Test quiz name 2" contains the following questions:
| question | page |
| First question | 1 |
And user "student1" has attempted "Test quiz name" with responses:
| slot | response |
| 1 | False |
And user "student2" has attempted "Test quiz name" with responses:
| slot | response |
| 1 | False |
And the following "core_badges > Badge" exists:
| name | Course Badge |
| name | Course Badge 1 |
| status | 0 |
| type | 2 |
| course | C1 |
| description | Course badge description |
| description | Course badge 1 description |
| image | badges/tests/behat/badge.png |
And the following "core_badges > Badge" exists:
| name | Course Badge 2 |
| status | 0 |
| type | 2 |
| course | C1 |
| description | Course badge 2 description |
| image | badges/tests/behat/badge.png |
And I am on the "Course 1" course page logged in as teacher1
Scenario: Student earns a badge using activity completion, but does not get passing grade
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Badges" in current page administration
Scenario: Student does not earn a badge using activity completion when does not get passing grade
Given I navigate to "Badges" in current page administration
And I press "Manage badges"
And I follow "Course Badge"
And I follow "Course Badge 1"
And I select "Criteria" from the "jump" singleselect
And I set the field "type" to "Activity completion"
And I set the field "Quiz - Test quiz name" to "1"
And I set the field "Quiz - Test quiz name 1" to "1"
And I press "Save"
And I press "Enable access"
And I press "Continue"
And I should see "Recipients (0)"
And I log out
And I am on the "Course 1" course page logged in as student1
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test quiz name" is displayed as "failed"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "todo"
When I am on the "Test quiz name" "quiz activity" page
And I press "Re-attempt quiz"
And I set the field "False" to "1"
And I press "Finish attempt ..."
And I press "Submit all and finish"
And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue"
And I log out
And I am on the "Course 1" course page logged in as teacher1
# Pass grade for student1. Activity is considered complete because student1 got a passing grade.
And user "student1" has attempted "Test quiz name 1" with responses:
| slot | response |
| 1 | True |
# Fail grade for student2. Activity is considered incomplete because student2 got a failing grade.
And user "student2" has attempted "Test quiz name 1" with responses:
| slot | response |
| 1 | False |
And I navigate to "Badges > Manage badges" in current page administration
And I follow "Course Badge"
And I follow "Course Badge 1"
Then I should see "Recipients (1)"
And I select "Recipients (1)" from the "jump" singleselect
And I should see "Student 1"
And I should not see "Student 2"
Scenario Outline: Previously graded pass/fail students should earn a badge after enabling a badge
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Badges" in current page administration
Scenario: Students with any grades in an activity will receive a badge if the completion condition is set to receive any grade
Given I navigate to "Badges" in current page administration
And I press "Manage badges"
And I follow "Course Badge"
And I follow "Course Badge 2"
And I select "Criteria" from the "jump" singleselect
And I set the field "type" to "Activity completion"
And I click on "Expand all" "link"
And I set the field "Quiz - Test quiz name" to "1"
And I set the field "<aggregationcriteria>" to "1"
And I set the field "Quiz - Test quiz name 2" to "1"
And I press "Save"
# Fail grade with student2
And I am on the "Course 1" course page logged in as student2
And I am on the "Test quiz name" "quiz activity" page
And I press "Re-attempt quiz"
And I set the field "False" to "1"
And I press "Finish attempt ..."
And I press "Submit all and finish"
And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue"
And I log out
# Pass grade with student1
And I am on the "Course 1" course page logged in as student1
And I am on the "Test quiz name" "quiz activity" page
And I press "Re-attempt quiz"
And I set the field "False" to "0"
And I press "Finish attempt ..."
And I press "Submit all and finish"
And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue"
And I log out
# Enable badge access once all students have completed an activity.
And I am on the "Course 1" course page logged in as teacher1
And I press "Enable access"
And I press "Continue"
# Pass grade for student1.
And user "student1" has attempted "Test quiz name 2" with responses:
| slot | response |
| 1 | True |
# Fail grade for student2. Activity is considered complete even if student2 got a failing grade.
And user "student2" has attempted "Test quiz name 2" with responses:
| slot | response |
| 1 | False |
And I navigate to "Badges > Manage badges" in current page administration
And I follow "Course Badge"
And I follow "Course Badge 2"
Then I should see "Recipients (2)"
And I select "Recipients (2)" from the "jump" singleselect
And I should see "Student 1"
And I should see "Student 2"
Scenario: Previously graded pass/fail students should earn a badge after enabling a badge
# Pass grade for student1.
Given user "student1" has attempted "Test quiz name 1" with responses:
| slot | response |
| 1 | True |
# Fail grade for student2.
And user "student2" has attempted "Test quiz name 1" with responses:
| slot | response |
| 1 | False |
And I navigate to "Badges" in current page administration
And I press "Manage badges"
And I follow "Course Badge 1"
And I select "Criteria" from the "jump" singleselect
And I set the field "type" to "Activity completion"
And I set the field "Quiz - Test quiz name 1" to "1"
And I press "Save"
# Enable badge access once students have completed the activity.
When I press "Enable access"
And I press "Continue"
Then I should see "Recipients (2)"
Examples:
| aggregationcriteria |
| Any of the selected activities is complete |
| All of the selected activities are complete |
# Only student1 should earn the badge because student2 did not pass the quiz.
Then I should see "Recipients (1)"
And I select "Recipients (1)" from the "jump" singleselect
And I should see "Student 1"
And I should not see "Student 2"
@@ -0,0 +1,52 @@
@core @core_badges
Feature: Delete course badge already awarded
As a teacher
I can delete awarded course badge
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
@javascript
Scenario Outline: Delete course badge already awarded to student
Given the following "core_badges > Badges" exist:
| name | course | description | image | status | type |
| <badgename> | C1 | <badgename> description | badges/tests/behat/badge.png | active | 2 |
And the following "core_badges > Criterias" exist:
| badge | role |
| <badgename> | editingteacher |
And the following "core_badges > Issued badges" exist:
| badge | user |
| <badgename> | student1 |
When I am on the "Course 1" "enrolled users" page logged in as "teacher1"
And I click on "Student 1" "link"
Then I should see "<badgename>"
And I am on the "Course 1" course page
# Navigate to Manage Badges page in order to delete the badge
And I navigate to "Badges > Manage badges" in current page administration
# Delete the badge
And I click on "Delete" "icon" in the "<badgename>" "table_row"
And I press "<deleteoption>"
And I am on the "Course 1" "enrolled users" page
And I click on "Student 1" "link"
# Confirm that Badge 1 is retained as awarded badge but Badge 2 is not
And I <visibility> see "<badgename>"
And I am on the "Course 1" course page
# Navigate to Badges page to confirm that no badges exist, hence, Manage badges would not exist
And I navigate to "Badges" in current page administration
# Confirm that badges are sucessfully deleted
And I should see "There are currently no badges available for users to earn."
Examples:
| badgename | deleteoption | visibility |
| Badge 1 | Delete and keep existing issued badges | should |
| Badge 2 | Delete and remove existing issued badges | should not |
@@ -25,11 +25,16 @@ Feature: Add a comment to the comments block
Scenario: Add a comment with Javascript enabled
When I add "I'm a comment from student1" comment to comments block
Then I should see "I'm a comment from student1"
And I am on the "Course 1" course page logged in as teacher1
And I should see "I'm a comment from student1"
Scenario: Add a comment with Javascript disabled
When I follow "Show comments"
And I add "I'm a comment from student1" comment to comments block
Then I should see "I'm a comment from student1"
And I am on the "Course 1" course page logged in as teacher1
And I follow "Show comments"
And I should see "I'm a comment from student1"
@javascript
Scenario: Test comment block pagination
@@ -1,27 +0,0 @@
@block @block_comments
Feature: Enable Block comments on a course page and view comments
In order to enable the comments block on a course page
As a teacher
I can add the comments block to the course page
Scenario: Add the comments block on the course page and add comments
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Frist | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| comments | Course | C1 | course-view-* | side-pre |
And I am on the "Course 1" course page logged in as teacher1
And I follow "Show comments"
And I add "I'm a comment from the teacher" comment to comments block
When I am on the "Course 1" course page logged in as student1
And I follow "Show comments"
Then I should see "I'm a comment from the teacher"
@@ -1,4 +1,4 @@
@block @block_completionstatus
@block @block_completionstatus @core_completion
Feature: Enable Block Completion in a course using activity completion
In order to view the completion block in a course
As a teacher
@@ -17,117 +17,69 @@ Feature: Enable Block Completion in a course using activity completion
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name | intro |
| page | C1 | page1 | Test page name | Test page description |
| assign | C1 | assign1 | Test assign name | Test page description |
| activity | course | idnumber | name | gradepass | completion | completionview | completionusegrade | completionpassgrade |
| page | C1 | page1 | Test page name | | 2 | 1 | 0 | 0 |
| assign | C1 | assign1 | Test assign name | 50 | 2 | 0 | 1 | 1 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| completionstatus | Course | C1 | course-view-* | side-pre |
Scenario: Add the block to a the course and add course completion items
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "Test page name"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| Require view | 1 |
And I press "Save and return to course"
And I add the "Course completion status" block
Scenario: Completion status block when student has not started any activities
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Test page name | 1 |
And I press "Save changes"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
When I am on the "Course 1" course page logged in as student1
Then I should see "Status: Not yet started" in the "Course completion status" "block"
And I should see "0 of 1" in the "Activity completion" "table_row"
Scenario: Add the block to a the course and add course completion items
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "Test page name"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| Require view | 1 |
And I press "Save and return to course"
And I add the "Course completion status" block
Scenario: Completion status block when student has completed a page
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Test page name | 1 |
And I press "Save changes"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Test page name"
When I am on the "Test page name" "page activity" page logged in as student1
And I am on "Course 1" course homepage
Then I should see "Status: Complete" in the "Course completion status" "block"
And I should see "1 of 1" in the "Activity completion" "table_row"
And I follow "More details"
And I should see "Yes" in the "Activity completion" "table_row"
@javascript
Scenario: Add the block to a the course and add course completion items with passing grade
Given I am on the "Test assign name" "assign activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| completionusegrade | 1 |
| completionpassgrade | 1 |
| gradepass | 50 |
And I press "Save and return to course"
And I am on the "Test assign name" "assign activity" page
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student" "table_row"
And I set the field "Grade out of 100" to "53"
And I set the field "Notify student" to "0"
And I press "Save changes"
And I am on "Course 1" course homepage with editing mode on
And I add the "Course completion status" block
Scenario: Completion status block with items with passing grade
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Test assign name | 1 |
And I press "Save changes"
And I log out
When I am on the "Test assign name" "assign activity" page logged in as student1
And I am on "Course 1" course homepage
Then I should see "Status: Pending" in the "Course completion status" "block"
And I should see "0 of 1" in the "Activity completion" "table_row"
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assign name | student1 | 53 |
When I am on the "Course 1" course page logged in as student1
Then I should see "Status: Complete" in the "Course completion status" "block"
And I should see "1 of 1" in the "Activity completion" "table_row"
And I trigger cron
And I am on "Course 1" course homepage
And I follow "More details"
And I should see "Achieving grade, Achieving passing grade" in the "Activity completion" "table_row"
And I should see "Yes" in the "Activity completion" "table_row"
@javascript
Scenario: Add the block to a the course and add course completion items with failing grade.
Given I am on the "Test assign name" "assign activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| completionusegrade | 1 |
| completionpassgrade | 1 |
| gradepass | 50 |
And I press "Save and return to course"
And I am on the "Test assign name" "assign activity" page
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student" "table_row"
And I set the field "Grade out of 100" to "49"
And I set the field "Notify student" to "0"
And I press "Save changes"
And I am on "Course 1" course homepage with editing mode on
And I add the "Course completion status" block
Scenario: Completion status block with items with failing grade
Given I am on the "Course 1" course page logged in as teacher1
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assign name | student1 | 49 |
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Test assign name | 1 |
And I press "Save changes"
And I log out
When I am on the "Test assign name" "assign activity" page logged in as student1
And I am on "Course 1" course homepage
Then I should see "Status: Pending" in the "Course completion status" "block"
When I am on the "Course 1" course page logged in as student1
Then I should see "Status: Not yet started" in the "Course completion status" "block"
And I should see "0 of 1" in the "Activity completion" "table_row"
And I trigger cron
And I am on "Course 1" course homepage
@@ -30,7 +30,7 @@ $string['aria:allcoursesincludinghidden'] = 'Show all courses';
$string['aria:allcourses'] = 'Show all courses except courses removed from view';
$string['aria:card'] = 'Switch to card view';
$string['aria:controls'] = 'Course overview controls';
$string['aria:courseactions'] = 'Actions for current course';
$string['aria:courseactions'] = 'Actions for course';
$string['aria:coursesummary'] = 'Course summary text:';
$string['aria:courseprogress'] = 'Course progress:';
$string['aria:customfield'] = 'Show {$a} courses';
@@ -26,7 +26,7 @@
"name": "Assignment due 1",
"viewurl": "https://moodlesite/course/view.php?id=2",
"courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
"fullname": "course 3",
"fullname": "Course 3 for \"Statistical and Computational Tools\" ",
"hasprogress": true,
"progress": 10,
"coursecategory": "Category 1",
@@ -50,8 +50,11 @@
{{/hasprogress}}
{{/progress}}
{{$coursename}}
<span class="multiline">
{{#shortentext}}50, {{{fullname}}} {{/shortentext}}
<span class="multiline" title="{{fullname}}">
<span class="sr-only">{{{fullname}}}</span>
<span aria-hidden="true">
{{#shortentext}}55, {{{fullname}}} {{/shortentext}}
</span>
</span>
{{/coursename}}
{{$coursecategory}}
@@ -94,6 +94,7 @@ Feature: The my overview block allows users to hide their courses
When I click on "All" "link" in the "Course overview" "block"
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
And I wait "1" seconds
Then I should not see "Course 2" in the "Course overview" "block"
Scenario: Test a course is never hidden with "All (including removed from view)" courses
@@ -86,7 +86,7 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
'type' => PARAM_INT,
),
'modname' => array(
'type' => PARAM_TEXT,
'type' => PARAM_PLUGIN,
),
'name' => array(
'type' => PARAM_TEXT,
@@ -108,7 +108,7 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
public static function define_other_properties() {
return array(
'viewurl' => array(
'type' => PARAM_TEXT,
'type' => PARAM_RAW,
),
'courseviewurl' => array(
'type' => PARAM_URL,
@@ -117,7 +117,7 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
'type' => PARAM_RAW,
),
'purpose' => array(
'type' => PARAM_TEXT,
'type' => PARAM_ALPHA,
)
);
}
+14 -4
View File
@@ -441,8 +441,17 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
} while (!feof($handle));
$this->lastiobytes = strlen($data);
if ($this->lastiobytes == 0) {
// Potentially statcache is stale. File can be deleted, let's clear cache and recheck.
clearstatcache(true, $file);
if (!file_exists($file)) {
// It's a completely normal condition. Just ignore and keep going.
return false;
}
}
// Return it unserialised.
return $this->prep_data_after_read($data);
return $this->prep_data_after_read($data, $file);
}
/**
@@ -548,13 +557,14 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
* Prepares the data it has been read from the cache. Undoing what was done in prep_data_before_save.
*
* @param string $data
* @param string $path
* @return mixed
* @throws coding_exception
*/
protected function prep_data_after_read($data) {
protected function prep_data_after_read($data, $path) {
$result = @unserialize($data);
if ($result === false && $data != serialize(false)) {
throw new coding_exception('Failed to unserialise data from file. Either failed to read, or failed to write.');
debugging('Failed to unserialise data from cache file: ' . $path . '. Data: ' . $data, DEBUG_DEVELOPER);
return false;
}
return $result;
}
+2 -2
View File
@@ -41,7 +41,7 @@
}} data-month="{{previousperiod.mon}}"{{!
}} data-day="{{previousperiod.mday}}"{{!
}}>
<span class="arrow">{{{larrow}}}</span>
<span class="arrow" aria-hidden="true">{{{larrow}}}</span>
&nbsp;
<span class="arrow_text">{{previousperiodname}}</span>
</a>
@@ -58,7 +58,7 @@
}}>
<span class="arrow_text">{{nextperiodname}}</span>
&nbsp;
<span class="arrow">{{{rarrow}}}</span>
<span class="arrow" aria-hidden="true">{{{rarrow}}}</span>
</a>
</div>
</div>
+2 -2
View File
@@ -41,7 +41,7 @@
}} data-month="{{previousperiod.mon}}"{{!
}} data-drop-zone="nav-link" {{!
}}>
<span class="arrow">{{{larrow}}}</span>
<span class="arrow" aria-hidden="true">{{{larrow}}}</span>
&nbsp;
<span class="arrow_text">{{previousperiodname}}</span>
</a>
@@ -63,7 +63,7 @@
}}>
<span class="arrow_text">{{nextperiodname}}</span>
&nbsp;
<span class="arrow">{{{rarrow}}}</span>
<span class="arrow" aria-hidden="true">{{{rarrow}}}</span>
</a>
</div>
</div>
+17 -1
View File
@@ -176,7 +176,23 @@ class behat_calendar extends behat_base {
* @return void
*/
public function i_am_viewing_site_calendar() {
$url = new moodle_url('/calendar/view.php', ['view' => 'month']);
$this->i_am_viewing_calendar_in_view('month');
}
/**
* Navigate to a specific view in the calendar.
*
* @Given /^I am viewing calendar in "([^"]+)" view$/
* @param string $view The calendar view ('month', 'day' and 'upcoming') to navigate to.
* @return void
*/
public function i_am_viewing_calendar_in_view(string $view): void {
if (!in_array($view, ['month', 'day', 'upcoming'])) {
throw new Exception("Invalid calendar view. Allowed values are: 'month', 'day' and 'upcoming'");
}
$url = new moodle_url('/calendar/view.php', ['view' => $view]);
$this->execute('behat_general::i_visit', [$url]);
}
}
+5
View File
@@ -41,6 +41,11 @@ M.core_comment = {
this.courseid = args.courseid;
this.contextid = args.contextid;
this.autostart = (args.autostart);
// Fail fast if the comments element cannot be found, such as in embedded-type views where blocks may be loaded
// then discarded.
if (!Y.one('#comment-ctrl-'+this.client_id)) {
return;
}
// expand comments?
if (this.autostart) {
this.view(args.page);
@@ -155,8 +155,8 @@ class completion_criteria_activity extends completion_criteria {
$data = $info->get_data($cm, false, $completion->userid);
// If the activity is complete
if (in_array($data->completionstate, array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL))) {
// If the activity is complete.
if (in_array($data->completionstate, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS])) {
if ($mark) {
$completion->mark_complete();
}
@@ -1,4 +1,4 @@
@block @block_completionstatus @javascript
@block @block_completionstatus @core_completion @javascript
Feature: Course completion state should match completion criteria
In order to understand the configuration or status of an course's completion
As a user
@@ -8,7 +8,6 @@ Feature: Course completion state should match completion criteria
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | T1 |
| teacher2 | Teacher | 2 | teacher1@example.com | T2 |
| student1 | Student | 1 | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion | showcompletionconditions |
@@ -16,16 +15,18 @@ Feature: Course completion state should match completion criteria
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | teacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment name |
| completion | 1 |
| assignsubmission_onlinetext_enabled | 1 |
| grade[modgrade_type] | Point |
| grade[modgrade_point] | 100 |
| grade[modgrade_type] | Point |
| grade[modgrade_point] | 100 |
| gradepass | 70 |
| completion | 2 |
| completionusegrade | 1 |
| completionpassgrade | 1 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| completionstatus | Course | C1 | course-view-* | side-pre |
@@ -34,58 +35,36 @@ Feature: Course completion state should match completion criteria
And I click on "Condition: Activity completion" "link"
And I set the field "Assignment - Test assignment name" to "1"
And I press "Save changes"
And I am on the "Test assignment name" "assign activity editing" page
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| completionusegrade | 1 |
| completionpassgrade | 1 |
| gradepass | 70 |
And I press "Save and return to course"
Scenario: Completion status show match completion criteria when passgrage condition is set.
Given I am on the "Course 1" course page logged in as "student1"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "todo"
And I should see "Status: Not yet started" in the "Course completion status" "block"
And I am on the "Test assignment name" "assign activity" page
And I press "Add submission"
And I set the following fields to these values:
| Online text | I'm the student1 submission |
And I press "Save changes"
And I press "Submit assignment"
And I press "Continue"
And I am on the "Test assignment name" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values:
| Grade out of 100 | 50.0 |
And I press "Save changes"
And I am on the "Course 1" course page
And I navigate to "Reports" in current page administration
And I click on "Activity completion" "link"
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports" in current page administration
And I click on "Course completion" "link" in the "region-main" "region"
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Not completed" "icon" should exist in the "Student 1" "table_row"
When I am on the "Course 1" course page logged in as "student1"
And I should see "Status: Pending" in the "Course completion status" "block"
When the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Test assignment name | student1 | This is a submission for assignment |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 50 |
And I reload the page
Then I should see "Status: Not yet started" in the "Course completion status" "block"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
And I am on the "My courses" page
And I should not see "100%" in the "Course overview" "block"
And I am on the "Test assignment name" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values:
| Grade out of 100 | 75.0 |
And I press "Save changes"
And I am on the "Course 1" course page
And I navigate to "Reports" in current page administration
And I click on "Activity completion" "link"
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Not completed" "icon" should exist in the "Student 1" "table_row"
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 75 |
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports" in current page administration
And I click on "Course completion" "link" in the "region-main" "region"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row"
And I am on the "Course 1" course page logged in as "student1"
@@ -93,62 +72,46 @@ Feature: Course completion state should match completion criteria
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"
And I am on the "My courses" page
Then I should see "100%" in the "Course overview" "block"
And I should see "100%" in the "Course overview" "block"
Scenario: Completion status show match completion criteria when passgrage condition is not set.
Given I am on the "Test assignment name" "assign activity editing" page logged in as teacher1
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| completionusegrade | 1 |
| completionpassgrade | 0 |
| gradepass | 70 |
| completionpassgrade | 0 |
And I press "Save and return to course"
And I am on the "Course 1" course page logged in as "student1"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo"
And I should see "Status: Not yet started" in the "Course completion status" "block"
And I am on the "Test assignment name" "assign activity" page
And I press "Add submission"
And I set the following fields to these values:
| Online text | I'm the student1 submission |
And I press "Save changes"
And I press "Submit assignment"
And I press "Continue"
And I am on the "Test assignment name" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values:
| Grade out of 100 | 50.0 |
And I press "Save changes"
And I am on the "Course 1" course page
And I navigate to "Reports" in current page administration
And I click on "Activity completion" "link"
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports" in current page administration
And I click on "Course completion" "link" in the "region-main" "region"
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row"
When I am on the "Course 1" course page logged in as "student1"
And I should see "Status: Complete" in the "Course completion status" "block"
When the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Test assignment name | student1 | I'm the student1 submission |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 50 |
And I reload the page
# TODO: Expected status is Complete but activity is marked as completed with a failed icon.
# Then I should see "Status: Complete" in the "Course completion status" "block"
Then I should see "Status: Pending" in the "Course completion status" "block"
# Once MDL-75582 is fixed "failed" should be changed to "done"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "failed"
And I am on the "My courses" page
And I should see "100%" in the "Course overview" "block"
And I am on the "Test assignment name" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student 1" "table_row"
And I set the following fields to these values:
| Grade out of 100 | 75.0 |
And I press "Save changes"
And I am on the "Course 1" course page
And I navigate to "Reports" in current page administration
And I click on "Activity completion" "link"
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row"
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 75 |
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports" in current page administration
And I click on "Course completion" "link" in the "region-main" "region"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row"
And I am on the "Course 1" course page logged in as "student1"
And I should see "Status: Complete" in the "Course completion status" "block"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And I am on the "My courses" page
Then I should see "100%" in the "Course overview" "block"
And I should see "100%" in the "Course overview" "block"
@@ -48,3 +48,37 @@ Feature: Allow teachers to edit the default activity completion rules in a cours
And I should see "Student must receive a grade to complete this activity" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Assignments']]" "xpath_element"
And I should see "Student must make a submission" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Assignments']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Assignments']]" "xpath_element"
@javascript
Scenario: Course activity completion default rules for SCORM
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Frist | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I am on the "Course 1" course page logged in as teacher1
When I navigate to "Course completion" in current page administration
And I select "Default activity completion" from the "Course completion tertiary navigation" singleselect
And I click on "SCORM packages" "checkbox"
And I click on "Edit" "button"
# Check Passed option.
And I set the following fields to these values:
| completion | Show activity as complete when conditions are met|
| completionstatusrequired[2] | 1 |
And I click on "Save changes" "button"
Then I should see "Changes saved"
And I should see "Student must achieve at least one of the following statuses: Passed, Completed"
And I navigate to "Course completion" in current page administration
And I select "Default activity completion" from the "Course completion tertiary navigation" singleselect
And I click on "SCORM packages" "checkbox"
And I click on "Edit" "button"
# Uncheck Pass option.
And I set the following fields to these values:
| completionstatusrequired[2] | 0 |
And I click on "Save changes" "button"
Then I should see "Changes saved"
And I should see "Student must achieve at least one of the following statuses: Completed"
+1 -1
View File
@@ -5,6 +5,6 @@ define("core_course/repository",["exports","core/ajax"],(function(_exports,_ajax
* @module core_course/repository
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};var _default={getEnrolledCoursesByTimelineClassification:(classification,limit,offset,sort)=>{const args={classification:classification};void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:args};return _ajax.default.call([request])[0]},getLastAccessedCourses:(userid,limit,offset,sort)=>{const args={};void 0!==userid&&(args.userid=userid),void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_recent_courses",args:args};return _ajax.default.call([request])[0]},getUsersFromCourseModuleID:function(cmid,groupID){let onlyActive=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var request={methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:cmid,groupid:groupID,onlyactive:onlyActive}};return _ajax.default.call([request])[0]},toggleManualCompletion:(cmid,completed)=>{const request={methodname:"core_completion_update_activity_completion_status_manually",args:{cmid:cmid,completed:completed}};return _ajax.default.call([request])[0]},getEnrolledCoursesWithEventsByTimelineClassification:function(classification){let limit=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,offset=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,sort=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,searchValue=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,eventsFrom=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,eventsTo=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null;const args={classification:classification,limit:limit,offset:offset,sort:sort,eventsfrom:eventsFrom,eventsto:eventsTo,searchvalue:searchValue},request={methodname:"core_course_get_enrolled_courses_with_action_events_by_timeline_classification",args:args};return _ajax.default.call([request])[0]}};return _exports.default=_default,_exports.default}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};var _default={getEnrolledCoursesByTimelineClassification:(classification,limit,offset,sort)=>{const args={classification:classification};void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:args};return _ajax.default.call([request])[0]},getLastAccessedCourses:(userid,limit,offset,sort)=>{const args={};void 0!==userid&&(args.userid=userid),void 0!==limit&&(args.limit=limit),void 0!==offset&&(args.offset=offset),void 0!==sort&&(args.sort=sort);const request={methodname:"core_course_get_recent_courses",args:args};return _ajax.default.call([request])[0]},getUsersFromCourseModuleID:function(cmid,groupID){let onlyActive=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var request={methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:cmid,groupid:groupID,onlyactive:onlyActive}};return _ajax.default.call([request])[0]},getGradableUsersFromCourseID:function(courseid,groupID){let onlyActive=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const request={methodname:"core_grades_get_gradable_users",args:{courseid:courseid,groupid:groupID,onlyactive:onlyActive}};return _ajax.default.call([request])[0]},toggleManualCompletion:(cmid,completed)=>{const request={methodname:"core_completion_update_activity_completion_status_manually",args:{cmid:cmid,completed:completed}};return _ajax.default.call([request])[0]},getEnrolledCoursesWithEventsByTimelineClassification:function(classification){let limit=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,offset=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,sort=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,searchValue=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,eventsFrom=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,eventsTo=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null;const args={classification:classification,limit:limit,offset:offset,sort:sort,eventsfrom:eventsFrom,eventsto:eventsTo,searchvalue:searchValue},request={methodname:"core_course_get_enrolled_courses_with_action_events_by_timeline_classification",args:args};return _ajax.default.call([request])[0]}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=repository.min.js.map
File diff suppressed because one or more lines are too long
+22
View File
@@ -150,6 +150,27 @@ const getEnrolledUsersFromCourseModuleID = (cmid, groupID, onlyActive = false) =
return Ajax.call([request])[0];
};
/**
* Get the list of gradable users enrolled in this course.
*
* @param {Number} courseid Course ID from which the users will be obtained
* @param {Number} groupID Group ID from which the users will be obtained
* @param {Boolean} onlyActive Whether to fetch only the active enrolled users or all enrolled users in the course.
* @returns {Promise} Promise containing a list of users
*/
const getGradabaleUsersFromCourseID = (courseid, groupID, onlyActive = false) => {
const request = {
methodname: 'core_grades_get_gradable_users',
args: {
courseid: courseid,
groupid: groupID,
onlyactive: onlyActive,
},
};
return Ajax.call([request])[0];
};
/**
* Toggle the completion state of an activity with manual completion.
*
@@ -172,6 +193,7 @@ export default {
getEnrolledCoursesByTimelineClassification,
getLastAccessedCourses,
getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID,
getGradableUsersFromCourseID: getGradabaleUsersFromCourseID,
toggleManualCompletion,
getEnrolledCoursesWithEventsByTimelineClassification,
};
+15 -3
View File
@@ -349,7 +349,11 @@ class core_course_list_element implements IteratorAggregate {
* @return string
*/
public function get_formatted_name() {
return format_string(get_course_display_name_for_list($this), true, $this->get_context());
return format_string(
get_course_display_name_for_list($this),
true,
['context' => $this->get_context()]
);
}
/**
@@ -357,7 +361,11 @@ class core_course_list_element implements IteratorAggregate {
* @return string
*/
public function get_formatted_fullname() {
return format_string($this->__get('fullname'), true, $this->get_context());
return format_string(
$this->__get('fullname'),
true,
['context' => $this->get_context()]
);
}
/**
@@ -365,7 +373,11 @@ class core_course_list_element implements IteratorAggregate {
* @return string
*/
public function get_formatted_shortname() {
return format_string($this->__get('shortname'), true, $this->get_context());
return format_string(
$this->__get('shortname'),
true,
['context' => $this->get_context()]
);
}
/**
+18 -1
View File
@@ -87,8 +87,25 @@ if (!async_helper::is_async_pending($id, 'course', 'backup')) {
$message = "{$strdeletecoursecheck}<br /><br />{$coursefullname} ({$courseshortname})";
$continueurl = new moodle_url('/course/delete.php', array('id' => $course->id, 'delete' => md5($course->timemodified)));
$continuebutton = new single_button($continueurl, get_string('delete'), 'post');
$continuebutton = new single_button(
$continueurl,
get_string('delete'), 'post', false, ['data-action' => 'delete']
);
echo $OUTPUT->confirm($message, $continuebutton, $categoryurl);
// In the following script, we need to use setTimeout as disabling the
// button in the event listener script prevent the click to be taken into account.
$jsscript = <<<EOF
const button = document.querySelector('button[data-action="delete"]');
if (button) {
button.addEventListener('click', () => {
setTimeout(() => {
button.disabled = true;
}, 0);
});
}
EOF;
$PAGE->requires->js_amd_inline($jsscript);
} else {
// Async backup is pending, don't let user delete course.
echo $OUTPUT->notification(get_string('pendingasyncerror', 'backup'), 'error');
+2
View File
@@ -495,6 +495,8 @@ class dndupload_ajax_processor {
throw new moodle_exception('errornouploadrepo', 'moodle');
}
$repo = reset($repo); // Get the first (and only) upload repo.
// Pre-emptively purge the navigation cache so the upload repo can close the session.
navigation_cache::destroy_volatile_caches();
$details = $repo->process_upload(null, $maxbytes, $types, '/', $draftitemid);
if (empty($this->displayname)) {
$this->displayname = $this->display_name_from_file($details['file']);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+73 -7
View File
@@ -23,6 +23,7 @@
*/
import {BaseComponent} from 'core/reactive';
import {debounce} from 'core/utils';
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
import inplaceeditable from 'core/inplace_editable';
import Section from 'core_courseformat/local/content/section';
@@ -33,6 +34,8 @@ import DispatchActions from 'core_courseformat/local/content/actions';
import * as CourseEvents from 'core_course/events';
// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.
import jQuery from 'jquery';
import Pending from 'core/pending';
import log from 'core/log';
export default class Component extends BaseComponent {
@@ -75,6 +78,7 @@ export default class Component extends BaseComponent {
this.cms = {};
// The page section return.
this.sectionReturn = descriptor.sectionReturn ?? 0;
this.debouncedReloads = new Map();
}
/**
@@ -230,6 +234,8 @@ export default class Component extends BaseComponent {
{watch: `transaction:start`, handler: this._startProcessing},
{watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},
{watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},
// Section visibility.
{watch: `section.visible:updated`, handler: this._reloadSection},
// Reindex sections and cms.
{watch: `state:updated`, handler: this._indexContents},
// State changes thaty require to reload course modules.
@@ -506,14 +512,65 @@ export default class Component extends BaseComponent {
* @param {object} param0.element the state object
*/
_reloadCm({element}) {
const cmitem = this.getElement(this.selectors.CM, element.id);
if (cmitem) {
const promise = courseActions.refreshModule(cmitem, element.id);
if (!this.getElement(this.selectors.CM, element.id)) {
return;
}
const debouncedReload = this._getDebouncedReloadCm(element.id);
debouncedReload();
}
/**
* Generate or get a reload CM debounced function.
* @param {Number} cmId
* @returns {Function} the debounced reload function
*/
_getDebouncedReloadCm(cmId) {
const pendingKey = `courseformat/content:reloadCm_${cmId}`;
let debouncedReload = this.debouncedReloads.get(pendingKey);
if (debouncedReload) {
return debouncedReload;
}
const reload = () => {
const pendingReload = new Pending(pendingKey);
this.debouncedReloads.delete(pendingKey);
const cmitem = this.getElement(this.selectors.CM, cmId);
if (!cmitem) {
return pendingReload.resolve();
}
const promise = courseActions.refreshModule(cmitem, cmId);
promise.then(() => {
this._indexContents();
return;
}).catch();
return true;
}).catch((error) => {
log.debug(error);
}).finally(() => {
pendingReload.resolve();
});
return pendingReload;
};
debouncedReload = debounce(
reload,
200,
{
cancel: true, pending: true
}
);
this.debouncedReloads.set(pendingKey, debouncedReload);
return debouncedReload;
}
/**
* Cancel the active reload CM debounced function, if any.
* @param {Number} cmId
*/
_cancelDebouncedReloadCm(cmId) {
const pendingKey = `courseformat/content:reloadCm_${cmId}`;
const debouncedReload = this.debouncedReloads.get(pendingKey);
if (!debouncedReload) {
return;
}
debouncedReload.cancel();
this.debouncedReloads.delete(pendingKey);
}
/**
@@ -526,13 +583,22 @@ export default class Component extends BaseComponent {
* @param {object} param0.element the state object
*/
_reloadSection({element}) {
const pendingReload = new Pending(`courseformat/content:reloadSection_${element.id}`);
const sectionitem = this.getElement(this.selectors.SECTION, element.id);
if (sectionitem) {
// Cancel any pending reload because the section will reload cms too.
for (const cmId of element.cmlist) {
this._cancelDebouncedReloadCm(cmId);
}
const promise = courseActions.refreshSection(sectionitem, element.id);
promise.then(() => {
this._indexContents();
return;
}).catch();
return true;
}).catch((error) => {
log.debug(error);
}).finally(() => {
pendingReload.resolve();
});
}
}
@@ -14,6 +14,15 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
import ajax from 'core/ajax';
import {get_string as getString} from "core/str";
import SRLogger from "core/local/reactive/srlogger";
/**
* Flag to determine whether the screen reader-only logger has already been set, so we only need to set it once.
*
* @type {boolean}
*/
let isLoggerSet = false;
/**
* Default mutation manager
@@ -65,6 +74,11 @@ export default class {
* @param {number} targetCmId optional target cm id (for moving actions)
*/
async _sectionBasicAction(stateManager, action, sectionIds, targetSectionId, targetCmId) {
const logEntry = this._getLoggerEntry(stateManager, action, sectionIds, {
targetSectionId,
targetCmId,
itemType: 'section',
});
const course = stateManager.get('course');
this.sectionLock(stateManager, sectionIds, true);
const updates = await this._callEditWebservice(
@@ -76,6 +90,7 @@ export default class {
);
stateManager.processUpdates(updates);
this.sectionLock(stateManager, sectionIds, false);
stateManager.addLoggerEntry(await logEntry);
}
/**
@@ -87,6 +102,11 @@ export default class {
* @param {number} targetCmId optional target cm id (for moving actions)
*/
async _cmBasicAction(stateManager, action, cmIds, targetSectionId, targetCmId) {
const logEntry = this._getLoggerEntry(stateManager, action, cmIds, {
targetSectionId,
targetCmId,
itemType: 'cm',
});
const course = stateManager.get('course');
this.cmLock(stateManager, cmIds, true);
const updates = await this._callEditWebservice(
@@ -98,6 +118,56 @@ export default class {
);
stateManager.processUpdates(updates);
this.cmLock(stateManager, cmIds, false);
stateManager.addLoggerEntry(await logEntry);
}
/**
* Get log entry for the current action.
* @param {StateManager} stateManager the current state manager
* @param {string} action the action name
* @param {int[]|null} itemIds the element ids
* @param {Object|undefined} data extra params for the log entry
* @param {string|undefined} data.itemType the element type (will be taken from action if none)
* @param {int|null|undefined} data.targetSectionId the target section id
* @param {int|null|undefined} data.targetCmId the target cm id
* @param {String|null|undefined} data.component optional component (for format plugins)
* @return {Object} the log entry
*/
async _getLoggerEntry(stateManager, action, itemIds, data = {}) {
if (!isLoggerSet) {
// In case the logger has not been set from init(), ensure we set the logger.
stateManager.setLogger(new SRLogger());
isLoggerSet = true;
}
const feedbackParams = {
action,
itemType: data.itemType ?? action.split('_')[0],
};
let batch = '';
if (itemIds.length > 1) {
feedbackParams.count = itemIds.length;
batch = '_batch';
} else if (itemIds.length === 1) {
const itemInfo = stateManager.get(feedbackParams.itemType, itemIds[0]);
feedbackParams.name = itemInfo.title ?? itemInfo.name;
// Apply shortener for modules like label.
}
if (data.targetSectionId) {
feedbackParams.targetSectionName = stateManager.get('section', data.targetSectionId).title;
}
if (data.targetCmId) {
feedbackParams.targetCmName = stateManager.get('cm', data.targetCmId).name;
}
const message = await getString(
`${action.toLowerCase()}_feedback${batch}`,
data.component ?? 'core_courseformat',
feedbackParams
);
return {
feedbackMessage: message,
};
}
/**
@@ -108,10 +178,13 @@ export default class {
* @param {StateManager} stateManager the state manager
*/
init(stateManager) {
// Add a method to prepare the fields when some update is comming from the server.
// Add a method to prepare the fields when some update is coming from the server.
stateManager.addUpdateTypes({
prepareFields: this._prepareFields,
});
// Use the screen reader-only logger (SRLogger) to handle the feedback messages from the mutations.
stateManager.setLogger(new SRLogger());
isLoggerSet = true;
}
/**
+6 -1
View File
@@ -416,8 +416,13 @@ class stateactions {
$allowstealth = !empty($CFG->allowstealth) && $format->allow_stealth_module_visibility($cm, $section);
$coursevisible = ($allowstealth) ? 0 : 1;
}
set_coursemodule_visible($cm->id, $visible, $coursevisible);
set_coursemodule_visible($cm->id, $visible, $coursevisible, false);
course_module_updated::create_from_cm($cm, $modcontext)->trigger();
}
course_modinfo::purge_course_modules_cache($course->id, $ids);
rebuild_course_cache($course->id, false, true);
foreach ($cms as $cm) {
$updates->add_cm_put($cm->id);
}
}
+1
View File
@@ -409,6 +409,7 @@ class format_weeks extends core_courseformat\base {
// Create a DateTime object for the start date.
$startdateobj = new DateTime("@$startdate");
$startdateobj->setTimezone(core_date::get_user_timezone_object());
// Calculate the interval for one week.
$oneweekinterval = new DateInterval('P7D');
+15 -6
View File
@@ -709,12 +709,17 @@ function set_downloadcontent(int $id, bool $downloadcontent): bool {
* has been moved to {@link set_section_visible()} which was the only place from which
* the parameter was used.
*
* If $rebuildcache is set to false, the calling code is responsible for ensuring the cache is purged
* and rebuilt as appropriate. Consider using this if set_coursemodule_visible is called multiple times
* (e.g. in a loop).
*
* @param int $id of the module
* @param int $visible state of the module
* @param int $visibleoncoursepage state of the module on the course page
* @param bool $rebuildcache If true (default), perform a partial cache purge and rebuild.
* @return bool false when the module was not found, true otherwise
*/
function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1) {
function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1, bool $rebuildcache = true) {
global $DB, $CFG;
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/calendar/lib.php');
@@ -771,8 +776,10 @@ function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1) {
}
}
\course_modinfo::purge_course_module_cache($cm->course, $cm->id);
rebuild_course_cache($cm->course, false, true);
if ($rebuildcache) {
\course_modinfo::purge_course_module_cache($cm->course, $cm->id);
rebuild_course_cache($cm->course, false, true);
}
return true;
}
@@ -1457,20 +1464,22 @@ function course_update_section($course, $section, $data) {
// If section visibility was changed, hide the modules in this section too.
if ($changevisibility && !empty($section->sequence)) {
$modules = explode(',', $section->sequence);
$cmids = [];
foreach ($modules as $moduleid) {
if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
$cmids[] = $cm->id;
if ($data['visible']) {
// As we unhide the section, we use the previously saved visibility stored in visibleold.
set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage);
set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage, false);
} else {
// We hide the section, so we hide the module but we store the original state in visibleold.
set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage);
set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage, false);
$DB->set_field('course_modules', 'visibleold', $cm->visible, ['id' => $moduleid]);
\course_modinfo::purge_course_module_cache($cm->course, $cm->id);
}
\core\event\course_module_updated::create_from_cm($cm)->trigger();
}
}
\course_modinfo::purge_course_modules_cache($courseid, $cmids);
rebuild_course_cache($courseid, false, true);
}
}
+8 -2
View File
@@ -110,8 +110,14 @@ if (empty($pending)) {
echo $OUTPUT->heading(get_string('nopendingcourses'));
} else {
echo $OUTPUT->heading(get_string('coursespending'));
$role = $DB->get_record('role', array('id' => $CFG->creatornewroleid), '*', MUST_EXIST);
echo $OUTPUT->notification(get_string('courserequestwarning', 'core', role_get_name($role)), 'notifyproblem');
$role = $DB->get_record('role', ['id' => $CFG->creatornewroleid]);
if ($role) {
echo $OUTPUT->notification(get_string('courserequestwarning', 'core', role_get_name($role)), 'notifyproblem');
} else {
$userpoliciesurl = new moodle_url('/admin/settings.php', ['section' => 'userpolicies']);
echo $OUTPUT->notification(get_string('courserequestroleerror', 'core', (string) $userpoliciesurl), 'notifyerror');
}
/// Build a table of all the requests.
$table = new html_table();
+4 -4
View File
@@ -70,14 +70,14 @@
"options": [
{
"url": "\/course\/index.php\/?categoryid=281",
"string": "Action 1"
"string": "Action 1",
"attributes": [
{
"name": "data-id",
"value": "1"
},
{
"name": "aria-role",
"name": "data-action",
"value": "test"
}
]
@@ -105,9 +105,9 @@
{{#str}}moremenu{{/str}}
</button>
<div id="moreactionsmenu" class="dropdown-menu" aria-labelledby="dropdown-actions" role="menu">
<div id="moreactionsmenu" class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown-actions" role="menu">
{{#options}}
<a role="menuitem" class="dropdown-item" {{#attributes}}{{name}}="{{value}}"{{/attributes}} href="{{url}}" tabindex="-1">{{string}}</a>
<a role="menuitem" class="dropdown-item" {{#attributes}}{{name}}="{{value}}" {{/attributes}} href="{{url}}" tabindex="-1">{{string}}</a>
{{/options}}
</div>
</div>
+1 -1
View File
@@ -29,7 +29,7 @@ Feature: Users can request and approve courses
| Course short name | Mynewcourse |
| Supporting information | pretty please |
And I press "Request a course"
And I should see "Your course request has been saved successfully."
And I should see "Course request submitted."
And I press "Continue"
And I am on course index
And I should not see "My new course"
+47
View File
@@ -1333,6 +1333,53 @@ class courselib_test extends advanced_testcase {
}
}
/**
* Test rebuildcache = false behaviour.
*
* When we pass rebuildcache = false to set_coursemodule_visible, the corusemodinfo cache will still contain
* the original visibility until we trigger a rebuild.
*
* @return void
* @covers ::set_coursemodule_visible
*/
public function test_module_visibility_no_rebuild(): void {
$this->setAdminUser();
$this->resetAfterTest(true);
// Create course and modules.
$course = $this->getDataGenerator()->create_course(['numsections' => 5]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$assign = $this->getDataGenerator()->create_module('assign', ['duedate' => time(), 'course' => $course->id]);
$modules = compact('forum', 'assign');
// Hiding the modules.
foreach ($modules as $mod) {
set_coursemodule_visible($mod->cmid, 0, 1, false);
// The modinfo cache still has the original visibility until we manually trigger a rebuild.
$cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
$this->assertEquals(1, $cm->visible);
}
rebuild_course_cache($course->id);
foreach ($modules as $mod) {
$this->check_module_visibility($mod, 0, 0);
}
// Showing the modules.
foreach ($modules as $mod) {
set_coursemodule_visible($mod->cmid, 1, 1, false);
$cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
$this->assertEquals(0, $cm->visible);
}
rebuild_course_cache($course->id);
foreach ($modules as $mod) {
$this->check_module_visibility($mod, 1, 1);
}
}
public function test_section_visibility_events() {
$this->setAdminUser();
$this->resetAfterTest(true);
+5
View File
@@ -1,6 +1,11 @@
This files describes API changes in /course/*,
information provided here is intended especially for developers.
=== 4.1.7 ===
* set_coursemodule_visible() has a new $rebuildcache parameter. If this is being called multiple times in the same request,
consider passing `false` for this parameter and rebuilding the cache once after all the course modules have been updated.
See course_update_section() for an example.
=== 4.1 ===
* The function course_modchooser() has been finally deprecated and can not be used anymore. Please use
course_activitychooser() instead.
+6
View File
@@ -238,7 +238,13 @@ abstract class data_controller {
*/
protected function is_unique($value) : bool {
global $DB;
// Ensure the "value" datafield can be safely compared across all databases.
$datafield = $this->datafield();
if ($datafield === 'value') {
$datafield = $DB->sql_cast_to_char($datafield);
}
$where = "fieldid = ? AND {$datafield} = ?";
$params = [$this->get_field()->get('id'), $value];
if ($this->get('id')) {
@@ -136,6 +136,29 @@ class data_controller extends \core_customfield\data_controller {
$instance->{$this->get_form_element_name()} = $value;
}
/**
* Checks if the value is empty, overriding the base method to ensure it's the "text" element of our value being compared
*
* @param mixed $value
* @return bool
*/
protected function is_empty($value): bool {
if (is_array($value)) {
$value = $value['text'];
}
return html_is_blank($value);
}
/**
* Checks if the value is unique, overriding the base method to ensure it's the "text" element of our value being compared
*
* @param mixed $value
* @return bool
*/
protected function is_unique($value): bool {
return parent::is_unique($value['text']);
}
/**
* Delete data
*
@@ -166,9 +189,6 @@ class data_controller extends \core_customfield\data_controller {
require_once($CFG->libdir . '/filelib.php');
$value = $this->get_value();
if ($this->is_empty($value)) {
return null;
}
if ($dataid = $this->get('id')) {
$context = $this->get_context();
+4 -4
View File
@@ -173,8 +173,8 @@ class enrol_meta_handler {
$ue->userid = $userid;
$ue->enrolid = $instance->id;
$ue->status = $parentstatus;
if ($instance->customint2) {
groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->id);
if ($instance->customint2 && $group = $DB->get_record('groups', ['id' => $instance->customint2])) {
groups_add_member($group, $userid, 'enrol_meta', $instance->id);
}
}
@@ -346,8 +346,8 @@ function enrol_meta_sync($courseid = NULL, $verbose = false) {
$ue->timestart = ($ue->timestart == 9999999999) ? 0 : (int)$ue->timestart;
$meta->enrol_user($instance, $ue->userid, null, $ue->timestart, $ue->timeend, $ue->status);
if ($instance->customint2) {
groups_add_member($instance->customint2, $ue->userid, 'enrol_meta', $instance->id);
if ($instance->customint2 && $group = $DB->get_record('groups', ['id' => $instance->customint2])) {
groups_add_member($group, $ue->userid, 'enrol_meta', $instance->id);
}
if ($verbose) {
mtrace(" enrolling: $ue->userid ==> $instance->courseid");
+37
View File
@@ -16,6 +16,9 @@
namespace enrol_meta;
use context_course;
use enrol_meta_plugin;
/**
* Meta enrolment sync functional test.
*
@@ -594,6 +597,40 @@ class plugin_test extends \advanced_testcase {
}
/**
* Test enrolling users in a course, where the customint2 (group) property of the instance points to an invalid group
*
* @covers \enrol_meta_handler::sync_with_parent_course
* @covers ::enrol_meta_sync
*/
public function test_add_to_group_invalid(): void {
$this->resetAfterTest();
$this->enable_plugin();
$courseone = $this->getDataGenerator()->create_course();
$coursetwo = $this->getDataGenerator()->create_course();
/** @var enrol_meta_plugin $plugin */
$plugin = enrol_get_plugin('meta');
$plugin->add_instance($coursetwo, ['customint1' => $courseone->id, 'customint2' => 42]);
// Ensure the event observer works for invalid groups.
$userone = $this->getDataGenerator()->create_and_enrol($courseone);
// Now disable the plugin, add another enrolment.
$this->disable_plugin();
$usertwo = $this->getDataGenerator()->create_and_enrol($courseone);
// Re-enable the plugin, run sync task - should also work for invalid groups.
$this->enable_plugin();
enrol_meta_sync($coursetwo->id);
$coursetwocontext = context_course::instance($coursetwo->id);
$this->assertTrue(is_enrolled($coursetwocontext, $userone));
$this->assertTrue(is_enrolled($coursetwocontext, $usertwo));
}
/**
* Test user_enrolment_created event.
*/
+1 -1
View File
@@ -92,7 +92,7 @@ If an enrolment key is specified, any user attempting to enrol in the course wil
$string['passwordinvalid'] = 'Incorrect enrolment key, please try again';
$string['passwordinvalidhint'] = 'That enrolment key was incorrect, please try again<br />
(Here\'s a hint - it starts with \'{$a}\')';
$string['passwordmatchesgroupkey'] = 'Enrolment key matches an existing group enrolment key';
$string['passwordmatchesgroupkey'] = 'This enrolment key is already used as a group enrolment key.';
$string['pluginname'] = 'Self enrolment';
$string['pluginname_desc'] = 'The self enrolment plugin allows users to choose which courses they want to participate in. The courses may be protected by an enrolment key. Internally the enrolment is done via the manual enrolment plugin which has to be enabled in the same course.';
$string['requirepassword'] = 'Require enrolment key';
+1 -1
View File
@@ -800,7 +800,7 @@ class self_test extends \advanced_testcase {
] + (array) $instance, [], $instance, $context);
$this->assertArrayHasKey('password', $errors);
$this->assertEquals('Enrolment key matches an existing group enrolment key', $errors['password']);
$this->assertEquals('This enrolment key is already used as a group enrolment key.', $errors['password']);
}
/**
+37
View File
@@ -1122,6 +1122,43 @@ class enrollib_test extends advanced_testcase {
$this->assertCount(1, enrol_get_course_users($course1->id, true));
}
/**
* test_course_users in groups
*
* @covers \enrol_get_course_users()
* @return void
*/
public function test_course_users_in_groups() {
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$this->getDataGenerator()->enrol_user($user3->id, $course->id);
$this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user1->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user1->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user2->id]);
$this->assertCount(3, enrol_get_course_users($course->id));
$this->assertCount(1, enrol_get_course_users($course->id, false, [], [], [$group1->id]));
$this->assertCount(2, enrol_get_course_users($course->id, false, [], [], [$group2->id]));
$instances = enrol_get_instances($course->id, true);
$manualinstance = reset($instances);
$manualplugin = enrol_get_plugin('manual');
$manualplugin->update_user_enrol($manualinstance, $user1->id, ENROL_USER_SUSPENDED);
$this->assertCount(2, enrol_get_course_users($course->id, false, [], [], [$group2->id]));
$this->assertCount(1, enrol_get_course_users($course->id, true, [], [], [$group2->id]));
}
/**
* Test count of enrolled users
*
+122
View File
@@ -0,0 +1,122 @@
<?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_grades\external;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/grade/lib.php');
require_once($CFG->dirroot . '/user/externallib.php');
use coding_exception;
use core_user_external;
use external_api;
use external_function_parameters;
use external_multiple_structure;
use external_single_structure;
use external_value;
use external_warnings;
use invalid_parameter_exception;
use moodle_exception;
use restricted_context_exception;
use user_picture;
/**
* Get the gradable users in a course.
*
* @package core_grades
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_gradable_users extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters (
[
'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED),
'groupid' => new external_value(PARAM_INT, 'Group Id', VALUE_DEFAULT, 0),
'onlyactive' => new external_value(PARAM_BOOL, 'Only active enrolment', VALUE_DEFAULT, false),
]
);
}
/**
* Given a course ID find the gradable users within a group.
*
* @param int $courseid Course ID
* @param int|null $groupid Group ID
* @param bool $onlyactive Whether we should only return active enrolments.
* @return array Users and warnings.
* @throws coding_exception
* @throws invalid_parameter_exception
* @throws moodle_exception
* @throws restricted_context_exception
*/
public static function execute(int $courseid, ?int $groupid = 0, bool $onlyactive = false): array {
global $DB, $PAGE;
$params = self::validate_parameters(
self::execute_parameters(),
[
'courseid' => $courseid,
'groupid' => $groupid,
'onlyactive' => $onlyactive,
]
);
$warnings = [];
$coursecontext = \context_course::instance($params['courseid']);
parent::validate_context($coursecontext);
require_capability('moodle/course:viewparticipants', $coursecontext);
$course = $DB->get_record('course', ['id' => $params['courseid']]);
$onlyactive = $onlyactive || !has_capability('moodle/course:viewsuspendedusers', $coursecontext);
$users = get_gradable_users($course->id, $params['groupid'], $onlyactive);
$users = array_map(function ($user) use ($PAGE) {
$user->fullname = fullname($user);
$userpicture = new user_picture($user);
$userpicture->size = 1;
$user->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
$user->profileimageurl = $userpicture->get_url($PAGE)->out(false);
return $user;
}, $users);
sort($users);
return [
'users' => $users,
'warnings' => $warnings,
];
}
/**
* Returns description of method result value.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'users' => new external_multiple_structure(core_user_external::user_description()),
'warnings' => new external_warnings(),
]);
}
}
@@ -0,0 +1,41 @@
@gradingform @gradingform_guide
Feature: Teacher can delete marking guide
As a teacher,
I should be able to delete a marking guide
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | course | name | advancedgradingmethod_submissions |
| assign | C1 | Assign 1 | guide |
And I am on the "Course 1" course page logged in as teacher1
And I go to "Assign 1" advanced grading definition page
And I set the following fields to these values:
| Name | Marking guide 1 |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Criterion 1 | Criterion 1 description for student | Criterion 1 description for markers | 100 |
And I press "Save marking guide and make it ready"
Scenario: Delete a marking guide
Given I am on the "Course 1" course page
And I go to "Assign 1" advanced grading page
When I click on "Delete the currently defined form" "link"
And I press "Cancel"
# Confirm that marking guide was not deleted if Cancel is pressed
Then I should see "Marking guide 1 Ready for use"
And I should see "Criterion 1"
And I click on "Delete the currently defined form" "link"
And I press "Continue"
# Confirm that marking guide was deleted successfully if Continue is pressed
And I should see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status."
And I should not see "Marking guide 1 Ready for use"
And I should not see "Criterion 1"
+22 -13
View File
@@ -149,27 +149,31 @@ class gradeimport_csv_load_data {
/**
* Inserts a record into the grade_import_values table. This also adds common record information.
*
* @param object $record The grade record being inserted into the database.
* @param stdClass $record The grade record being inserted into the database.
* @param int $studentid The student ID.
* @return bool|int true or insert id on success. Null if the grade value is too high.
* @param grade_item $gradeitem Grade item.
* @return bool|int true or insert id on success. Null if the grade value is too high or too low or grade item not exist.
*/
protected function insert_grade_record($record, $studentid) {
protected function insert_grade_record(stdClass $record, int $studentid, grade_item $gradeitem) {
global $DB, $USER, $CFG;
$record->importcode = $this->importcode;
$record->userid = $studentid;
$record->importer = $USER->id;
// By default the maximum grade is 100.
$gradepointmaximum = 100;
// If the grade limit has been increased then use the gradepointmax setting.
if ($CFG->unlimitedgrades) {
$gradepointmaximum = $CFG->gradepointmax;
}
// If the record final grade is set then check that the grade value isn't too high.
// Final grade will not be set if we are inserting feedback.
if (!isset($record->finalgrade) || $record->finalgrade <= $gradepointmaximum) {
$gradepointmaximum = $gradeitem->grademax;
$gradepointminimum = $gradeitem->grademin;
$finalgradeinrange =
isset($record->finalgrade) && $record->finalgrade <= $gradepointmaximum && $record->finalgrade >= $gradepointminimum;
if (!isset($record->finalgrade) || $finalgradeinrange || $CFG->unlimitedgrades) {
return $DB->insert_record('grade_import_values', $record);
} else {
$this->cleanup_import(get_string('gradevaluetoobig', 'grades', $gradepointmaximum));
if ($record->finalgrade > $gradepointmaximum) {
$this->cleanup_import(get_string('gradevaluetoobig', 'grades', format_float($gradepointmaximum)));
} else {
$this->cleanup_import(get_string('gradevaluetoosmall', 'grades', format_float($gradepointminimum)));
}
return null;
}
}
@@ -575,7 +579,12 @@ class gradeimport_csv_load_data {
}
}
}
$insertid = self::insert_grade_record($newgrade, $this->studentid);
if (isset($newgrade->itemid)) {
$gradeitem = new grade_item(['id' => $newgrade->itemid]);
} else if (isset($newgrade->newgradeitem)) {
$gradeitem = new grade_item(['id' => $newgrade->newgradeitem]);
}
$insertid = isset($gradeitem) ? self::insert_grade_record($newgrade, $this->studentid, $gradeitem) : null;
// Check to see if the insert was successful.
if (empty($insertid)) {
return null;
@@ -597,7 +606,7 @@ class gradeimport_csv_load_data {
} else {
// The grade item for this is not updated.
$newfeedback->importonlyfeedback = true;
$insertid = self::insert_grade_record($newfeedback, $this->studentid);
$insertid = self::insert_grade_record($newfeedback, $this->studentid, new grade_item(['id' => $newfeedback->itemid]));
// Check to see if the insert was successful.
if (empty($insertid)) {
return null;
@@ -27,10 +27,11 @@ class phpunit_gradeimport_csv_load_data extends gradeimport_csv_load_data {
*
* @param object $record
* @param int $studentid
* @param grade_item $gradeitem
*/
public function test_insert_grade_record($record, $studentid) {
public function test_insert_grade_record($record, $studentid, grade_item $gradeitem) {
$this->importcode = 00001;
$this->insert_grade_record($record, $studentid);
$this->insert_grade_record($record, $studentid, $gradeitem);
}
/**
+2 -1
View File
@@ -187,7 +187,8 @@ Bobby,Bunce,,"Moodle HQ","Rock on!",student5@example.com,75.00,,75.00,{exportdat
$record->feedback = 'Some test feedback';
$testobject = new \phpunit_gradeimport_csv_load_data();
$testobject->test_insert_grade_record($record, $user->id);
$testobject->test_insert_grade_record($record, $user->id, new \grade_item());
$gradeimportvalues = $DB->get_records('grade_import_values');
// Get the insert id.
+21 -11
View File
@@ -786,18 +786,12 @@ function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
*
* @param int $courseid The course ID.
* @param int|null $groupid The group ID (optional).
* @param bool $onlyactiveenrol Include only active enrolments.
* @return array $users A list of enrolled gradable users.
*/
function get_gradable_users(int $courseid, ?int $groupid = null): array {
global $CFG;
$context = context_course::instance($courseid);
// Create a graded_users_iterator because it will properly check the groups etc.
$defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
$onlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol) ||
!has_capability('moodle/course:viewsuspendedusers', $context);
function get_gradable_users(int $courseid, ?int $groupid = null, bool $onlyactiveenrol = false): array {
$course = get_course($courseid);
// Create a graded_users_iterator because it will properly check the groups etc.
$gui = new graded_users_iterator($course, null, $groupid);
$gui->require_active_enrolment($onlyactiveenrol);
$gui->init();
@@ -891,6 +885,12 @@ function print_grade_page_head(int $courseid, string $active_type, ?string $acti
?string $headerhelpcomponent = null, ?stdClass $user = null, ?action_bar $actionbar = null, $showtitle = true) {
global $CFG, $OUTPUT, $PAGE, $USER;
if ($heading !== false) {
// Make sure to trim heading, including the non-breaking space character.
$heading = str_replace("&nbsp;", " ", $heading);
$heading = trim($heading);
}
// Put a warning on all gradebook pages if the course has modules currently scheduled for background deletion.
require_once($CFG->dirroot . '/course/lib.php');
if (course_modules_pending_deletion($courseid, true)) {
@@ -924,14 +924,24 @@ function print_grade_page_head(int $courseid, string $active_type, ?string $acti
}
$coursecontext = context_course::instance($courseid);
// Title will be constituted by information starting from the unique identifying information for the page.
if (in_array($active_type, ['report', 'settings'])) {
if ($heading) {
// If heading is supplied, use this for the page title.
$uniquetitle = $heading;
} else if (in_array($active_type, ['report', 'settings'])) {
// For grade reports or settings pages of grade plugins, use the plugin name for the unique title.
$uniquetitle = $stractive_plugin;
// But if editing mode is turned on, check if the report plugin has an editing mode title string and use it if present.
if ($PAGE->user_is_editing() && $active_type === 'report') {
$strcomponent = "gradereport_{$active_plugin}";
if (get_string_manager()->string_exists('editingmode_title', $strcomponent)) {
$uniquetitle = get_string('editingmode_title', $strcomponent);
}
}
} else {
$uniquetitle = $stractive_type . ': ' . $stractive_plugin;
}
$titlecomponents = [
$uniquetitle,
get_string('grades'),
$coursecontext->get_context_name(false),
];
$PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $titlecomponents));
+1 -3
View File
@@ -107,13 +107,11 @@ if (!empty($target) && !empty($action) && confirm_sesskey()) {
grade_report_grader::do_process_action($target, $action, $courseid);
}
$reportname = get_string('pluginname', 'gradereport_grader');
// Do this check just before printing the grade header (and only do it once).
grade_regrade_final_grades_if_required($course);
// Print header
print_grade_page_head($COURSE->id, 'report', 'grader', $reportname, false, $buttons);
print_grade_page_head($COURSE->id, 'report', 'grader', false, false, $buttons);
//Initialise the grader report object that produces the table
//the class grade_report_grader_ajax was removed as part of MDL-21562
@@ -28,6 +28,7 @@ $string['ajaxerror'] = 'Error';
$string['ajaxfailedupdate'] = 'Unable to update [1] for [2]';
$string['ajaxfieldchanged'] = 'The field you are currently editing has changed, would you like to use the updated value?';
$string['ajaxchoosescale'] = 'Choose';
$string['editingmode_title'] = 'Editing the grader report';
$string['eventgradereportviewed'] = 'Grader report viewed';
$string['grader:manage'] = 'Manage the grader report';
$string['grader:view'] = 'View grader report';
+29 -11
View File
@@ -802,8 +802,6 @@ class grade_report_grader extends grade_report {
$rows = array();
$this->rowcount = 0;
$numrows = count($this->gtree->get_levels());
$numusers = count($this->users);
$gradetabindex = 1;
$columnstounset = array();
$strgrade = $this->get_lang_string('gradenoun');
$strfeedback = $this->get_lang_string("feedback");
@@ -945,7 +943,6 @@ class grade_report_grader extends grade_report {
// Preload scale objects for items with a scaleid and initialize tab indices
$scaleslist = array();
$tabindices = array();
foreach ($this->gtree->get_items() as $itemid => $item) {
$scale = null;
@@ -955,9 +952,6 @@ class grade_report_grader extends grade_report {
} else {
$jsarguments['items'][$itemid] = array('id'=>$itemid, 'name'=>$item->get_name(true), 'type'=>'value', 'scale'=>false, 'decimals'=>$item->get_decimals());
}
$tabindices[$item->id]['grade'] = $gradetabindex;
$tabindices[$item->id]['feedback'] = $gradetabindex + $numusers;
$gradetabindex += $numusers * 2;
}
$scalesarray = array();
@@ -1123,7 +1117,7 @@ class grade_report_grader extends grade_report {
} else {
$nogradestr = $this->get_lang_string('nooutcome', 'grades');
}
$attributes = array('tabindex' => $tabindices[$item->id]['grade'], 'id'=>'grade_'.$userid.'_'.$item->id);
$attributes = ['id' => 'grade_' . $userid . '_' . $item->id];
$gradelabel = $fullname . ' ' . $item->get_name(true);
$itemcell->text .= html_writer::label(
get_string('useractivitygrade', 'gradereport_grader', $gradelabel), $attributes['id'], false,
@@ -1149,9 +1143,33 @@ class grade_report_grader extends grade_report {
$gradelabel = $fullname . ' ' . $item->get_name(true);
$itemcell->text .= '<label class="accesshide" for="grade_'.$userid.'_'.$item->id.'">'
.get_string('useractivitygrade', 'gradereport_grader', $gradelabel).'</label>';
$itemcell->text .= '<input size="6" tabindex="' . $tabindices[$item->id]['grade']
. '" type="text" class="text" title="'. $strgrade .'" name="grade['
.$userid.'][' .$item->id.']" id="grade_'.$userid.'_'.$item->id.'" value="'.$value.'" />';
// Set this input field with type="number" if the decimal separator for current language is set to
// a period. Other decimal separators may not be recognised by browsers yet which may cause issues
// when entering grades.
$decsep = get_string('decsep', 'core_langconfig');
$isnumeric = $decsep === '.';
$inputtype = $isnumeric ? 'number' : 'text';
$inputparams = [
'type' => $inputtype,
'class' => 'text',
'title' => $strgrade,
'name' => "grade[{$userid}][{$item->id}]",
'id' => "grade_{$userid}_{$item->id}",
'value' => $value,
];
// If we're rendering this as a number field, set step and min/max attributes (if applicable).
if ($isnumeric) {
$inputparams['step'] = 'any';
if (isset($item->grademin)) {
$inputparams['min'] = $item->grademin;
}
if (isset($item->grademax)) {
$inputparams['max'] = $item->grademax;
}
}
$itemcell->text .= html_writer::empty_tag('input', $inputparams);
} else {
$itemcell->text .= $gradepassicon . "<span class='gradevalue{$hidden}{$gradepass}'>" .
format_float($gradeval, $decimalpoints) . "</span>";
@@ -1163,7 +1181,7 @@ class grade_report_grader extends grade_report {
$feedbacklabel = $fullname . ' ' . $item->get_name(true);
$itemcell->text .= '<label class="accesshide" for="feedback_'.$userid.'_'.$item->id.'">'
.get_string('useractivityfeedback', 'gradereport_grader', $feedbacklabel).'</label>';
$itemcell->text .= '<input class="quickfeedback" tabindex="' . $tabindices[$item->id]['feedback'].'" id="feedback_'.$userid.'_'.$item->id
$itemcell->text .= '<input class="quickfeedback" id="feedback_'.$userid.'_'.$item->id
. '" size="6" title="' . $strfeedback . '" type="text" name="feedback['.$userid.']['.$item->id.']" value="' . s($grade->feedback) . '" />';
}
+20
View File
@@ -755,5 +755,25 @@ abstract class grade_report {
return $modnames;
}
/**
* Load a valid list of gradable users in a course.
*
* @param int $courseid The course ID.
* @param int|null $groupid The group ID (optional).
* @return array A list of enrolled gradable users.
*/
public static function get_gradable_users(int $courseid, ?int $groupid = null): array {
global $CFG;
require_once($CFG->dirroot . '/grade/lib.php');
$context = context_course::instance($courseid);
$defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
$onlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol) ||
!has_capability('moodle/course:viewsuspendedusers', $context);
return get_gradable_users($courseid, $groupid, $onlyactiveenrol);
}
}
+5 -2
View File
@@ -80,14 +80,17 @@ class grade_report_overview extends grade_report {
* @param string $context
*/
public function __construct($userid, $gpr, $context) {
global $CFG, $COURSE, $DB;
global $CFG, $COURSE, $DB, $USER;
parent::__construct($COURSE->id, $gpr, $context);
// Get the user (for full name).
$this->user = $DB->get_record('user', array('id' => $userid));
// Set onlyactive flag to true if the user's viewing his/her report.
$onlyactive = ($this->user->id === $USER->id);
// Load the user's courses.
$this->courses = enrol_get_users_courses($this->user->id, false, 'id, shortname, showgrades');
$this->courses = enrol_get_users_courses($this->user->id, $onlyactive, 'id, shortname, showgrades');
$this->showrank = array();
$this->showrank['any'] = false;
@@ -0,0 +1,31 @@
@gradereport @gradereport_overview
Feature: Grade overview report should be hidden from suspended enrolments
While viewing the grade overview report
As a student
I should only see courses I am active in
Background:
Given the following "courses" exist:
| fullname | shortname |
| Active course | C1 |
| Suspended course | C2 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role | status |
| student1 | C1 | student | 0 |
| student1 | C2 | student | 1 |
Scenario: Students should not see grades for courses with suspended enrolments
Given I am on the "Active course" "grades > Overview report > View" page logged in as "student1"
Then I should not see "Suspended course" in the "overview-grade" "table"
And I should see "Active course" in the "overview-grade" "table"
Scenario: Admins should see courses with suspended enrolments
Given I log in as "admin"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "Student 1"
When I click on "Grades overview" "link"
Then I should see "Suspended course" in the "overview-grade" "table"
And I should see "Active course" in the "overview-grade" "table"
@@ -24,6 +24,7 @@
namespace gradereport_singleview\local\screen;
use grade_report;
use gradereport_singleview\local\ui\range;
use gradereport_singleview\local\ui\bulk_insert;
use grade_grade;
@@ -139,7 +140,7 @@ class grade extends tablelike implements selectable_items, filterable_items {
*/
public function init($selfitemisempty = false) {
$this->items = $this->load_users();
$this->items = grade_report::get_gradable_users($this->courseid, $this->groupid);
$this->totalitemcount = count($this->items);
if ($selfitemisempty) {
@@ -301,7 +302,9 @@ class grade extends tablelike implements selectable_items, filterable_items {
* @return string
*/
public function heading(): string {
return get_string('gradeitem', 'gradereport_singleview', $this->item->get_name());
global $PAGE;
$headinglangstring = $PAGE->user_is_editing() ? 'gradeitemedit' : 'gradeitem';
return get_string($headinglangstring, 'gradereport_singleview', $this->item->get_name());
}
/**
@@ -25,6 +25,7 @@
namespace gradereport_singleview\local\screen;
use context_course;
use grade_report;
use moodle_url;
use html_writer;
use grade_structure;
@@ -33,6 +34,7 @@ use grade_item;
use stdClass;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/grade/report/lib.php');
/**
* Abstract class used as a base for the 3 screens.
@@ -99,7 +99,7 @@ class user extends tablelike implements selectable_items {
public function init($selfitemisempty = false) {
if (!$selfitemisempty) {
$validusers = $this->load_users();
$validusers = \grade_report::get_gradable_users($this->courseid, $this->groupid);
if (!isset($validusers[$this->itemid])) {
// If the passed user id is not valid, show the first user from the list instead.
$this->item = reset($validusers);
@@ -302,7 +302,9 @@ class user extends tablelike implements selectable_items {
* @return string
*/
public function heading(): string {
return get_string('gradeuser', 'gradereport_singleview', fullname($this->item));
global $PAGE;
$headinglangstring = $PAGE->user_is_editing() ? 'gradeuseredit' : 'gradeuser';
return get_string($headinglangstring, 'gradereport_singleview', fullname($this->item));
}
/**
+4 -2
View File
@@ -27,6 +27,7 @@ define('NO_OUTPUT_BUFFERING', true);
require_once('../../../config.php');
require_once($CFG->dirroot.'/lib/gradelib.php');
require_once($CFG->dirroot.'/grade/lib.php');
require_once($CFG->dirroot.'/grade/report/lib.php');
$courseid = required_param('id', PARAM_INT);
$groupid = optional_param('group', null, PARAM_INT);
@@ -90,7 +91,8 @@ switch ($itemtype) {
// If there is a stored user item (last viewed) in a session variable, bypass the user select zero state
// and display this user item. Also, make sure that the stored last viewed user is part of the current
// list of gradable users in this course.
if ($lastvieweduseritemid && array_key_exists($lastvieweduseritemid, get_gradable_users($courseid, $currentgroup))) {
if ($lastvieweduseritemid &&
array_key_exists($lastvieweduseritemid, grade_report::get_gradable_users($courseid, $currentgroup))) {
$itemtype = 'user';
$itemid = $lastvieweduseritemid;
} else {
@@ -103,7 +105,7 @@ switch ($itemtype) {
}
// If the item id (user id) cannot be defined or the user id is not part of the list of gradable users,
// display the user select zero state.
if (is_null($itemid) || !array_key_exists($itemid, get_gradable_users($courseid, $currentgroup))) {
if (is_null($itemid) || !array_key_exists($itemid, grade_report::get_gradable_users($courseid, $currentgroup))) {
$itemtype = 'user_select';
}
break;
@@ -44,8 +44,10 @@ $string['excludenonegrades'] = 'Exclude none';
$string['eventgradereportviewed'] = 'Grade single view report viewed.';
$string['feedbackfor'] = 'Feedback for {$a}';
$string['gradefor'] = 'Grade for {$a}';
$string['gradeitem'] = 'Grade item: {$a}';
$string['gradeuser'] = 'Grade user: {$a}';
$string['gradeitem'] = 'Viewing grades for {$a}';
$string['gradeitemedit'] = 'Editing grades for {$a}';
$string['gradeuser'] = 'Viewing {$a}\'s grades';
$string['gradeuseredit'] = 'Editing {$a}\'s grades';
$string['gotonextreport'] = 'Go to next user report';
$string['gotopreviousreport'] = 'Go to previous user report';
$string['noscreens'] = 'Could not find a suitable single view screen.';
@@ -26,6 +26,6 @@
}}
<div class="zero-state mx-auto w-50 text-center my-6">
<img src="{{imglink}}" alt="{{#str}}viewsinglegradeitem, gradereport_singleview{{/str}}" aria-hidden="true" class="my-5">
<h3>{{#str}}viewsinglegradeitem, gradereport_singleview{{/str}}</h3>
<h2 class="h3">{{#str}}viewsinglegradeitem, gradereport_singleview{{/str}}</h2>
<p>{{#str}}singleviewdescription, gradereport_singleview{{/str}}</p>
</div>
@@ -26,6 +26,6 @@
}}
<div class="zero-state mx-auto w-50 text-center my-6">
<img src="{{imglink}}" alt="{{#str}}viewsingleuser, gradereport_singleview{{/str}}" aria-hidden="true" class="my-5">
<h3>{{#str}}viewsingleuser, gradereport_singleview{{/str}}</h3>
<h2 class="h3">{{#str}}viewsingleuser, gradereport_singleview{{/str}}</h2>
<p>{{#str}}singleviewdescription, gradereport_singleview{{/str}}</p>
</div>
+1 -1
View File
@@ -113,7 +113,7 @@ if (has_capability('moodle/grade:viewall', $context)) {
$userid = $lastvieweduserid;
}
$gradableusers = get_gradable_users($courseid, $currentgroup);
$gradableusers = grade_report::get_gradable_users($courseid, $currentgroup);
// Validate whether the requested user is a valid gradable user in this course. If, not display the user select
// zero state.
if (empty($gradableusers) || ($userid && !array_key_exists($userid, $gradableusers))) {
+4 -4
View File
@@ -43,9 +43,9 @@ Feature: Site settings can be used to hide parts of the gradebook UI
@javascript
Scenario: Disable category overriding
And "tr .course input[type='text']" "css_element" should exist
Then I navigate to "Grades > Grade category settings" in site administration
Given "Student 1 Course total" "field" should exist
And I navigate to "Grades > Grade category settings" in site administration
And I set the field "Allow category grades to be manually overridden" to "0"
And I press "Save changes"
And I am on the "Course 1" "grades > Grader report > View" page
And "tr .course input[type='text']" "css_element" should not exist
When I am on the "Course 1" "grades > Grader report > View" page
Then "Student 1 Course total" "field" should not exist
+68
View File
@@ -0,0 +1,68 @@
@core @core_grades @javascript @_file_upload
Feature: An admin can import grades into gradebook using a CSV file
In order to import grades using a CSV file
As a teacher
I need to be able to upload a CSV file and see uploaded grades in gradebook
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
And the following "grade item" exists:
| course | C1 |
| itemname | Manual item 1 |
| grademin | 10 |
| grademax | 500 |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Manual item 1 | student1 | 50.00 |
| Manual item 1 | student2 | 50.00 |
| Manual item 1 | student3 | 50.00 |
@javascript
Scenario: Max grade of grade item is respected when importing grades
Given I am on the "Course 1" "Course" page logged in as "teacher1"
And I navigate to "More > Import" in the course gradebook
And I upload "grade/tests/fixtures/grade_import_grademax.csv" file to "File" filemanager
And I click on "Upload grades" "button"
And I set the field "Map from" to "Email address"
And I set the field "Map to" to "Email address"
And I set the field "Manual item 1" to "Manual item 1"
And I click on "Upload grades" "button"
And I should see "One of the grade values is larger than the allowed grade maximum of 500.0"
And I should see "Import failed. No data was imported."
And I click on "Continue" "button"
And I upload "grade/tests/fixtures/grade_import_grademin.csv" file to "File" filemanager
And I click on "Upload grades" "button"
And I set the field "Map from" to "Email address"
And I set the field "Map to" to "Email address"
And I set the field "Manual item 1" to "Manual item 1"
And I click on "Upload grades" "button"
And I should see "One of the grade values is smaller than the allowed grade minimum of 10.0"
And I should see "Import failed. No data was imported."
And I click on "Continue" "button"
When I upload "grade/tests/fixtures/grade_import.csv" file to "File" filemanager
And I click on "Upload grades" "button"
And I set the field "Map from" to "Email address"
And I set the field "Map to" to "Email address"
And I set the field "Manual item 1" to "Manual item 1"
And I click on "Upload grades" "button"
And I should see "Grade import success"
And I click on "Continue" "button"
Then the following should exist in the "user-grades" table:
| -1- | -1- | -4- | -5- |
| Student 1 | student1@example.com | 400.00 | 400.00 |
| Student 2 | student2@example.com | 50.00 | 50.00 |
| Student 3 | student3@example.com | 50.00 | 50.00 |
+19 -52
View File
@@ -19,50 +19,23 @@ Feature: We can use a minimum grade different than zero
| student1 | C1 | student |
| student2 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Sub category 1 | C1 |
| Sub category 2 | C1 |
| fullname | course | aggregateonlygraded |
| Sub category 1 | C1 | 0 |
| Sub category 2 | C1 | 0 |
And the following "grade items" exist:
| itemname | grademin | course |
| Manual item 1 | -100 | C1 |
| Manual item 2 | 50 | C1 |
And the following "grade items" exist:
| itemname | grademin | grademax | course | gradecategory |
| Manual item 3 | -100 | 50 | C1 | Sub category 1 |
And the following "grade items" exist:
| itemname | grademin | course | gradecategory |
| Manual item 4 | -100 | C1 | Sub category 1 |
| Manual item 5 | 50 | C1 | Sub category 2 |
| Manual item 6 | 50 | C1 | Sub category 2 |
And I log in as "admin"
And I set the following administration settings values:
| grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
And I am on the "Course 1" "grades > gradebook setup" page
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 1 |
| Minimum grade | -100 |
| Grade category | Course 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 2 |
| Minimum grade | 50 |
| Grade category | Course 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 3 |
| Maximum grade | 50 |
| Minimum grade | -100 |
| Grade category | Sub category 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 4 |
| Minimum grade | -100 |
| Grade category | Sub category 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 5 |
| Minimum grade | 50 |
| Grade category | Sub category 2 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 6 |
| Minimum grade | 50 |
| Grade category | Sub category 2 |
And I press "Save changes"
And I navigate to "Setup > Course grade settings" in the course gradebook
And I set the field "Show weightings" to "Show"
And I set the field "Show contribution to course total" to "Show"
@@ -70,13 +43,7 @@ Feature: We can use a minimum grade different than zero
@javascript
Scenario: Natural aggregation with negative and positive grade
And I navigate to "Setup > Gradebook setup" in the course gradebook
And I set the following settings for grade item "Sub category 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Natural |
| Exclude empty grades | 0 |
Given I navigate to "Setup > Gradebook setup" in the course gradebook
And I set the following settings for grade item "Course 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
@@ -89,11 +56,11 @@ Feature: We can use a minimum grade different than zero
And I give the grade "50.00" to the user "Student 1" for the grade item "Manual item 5"
And I give the grade "75.00" to the user "Student 1" for the grade item "Manual item 6"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 1"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 2"
And I give the grade "50.00" to the user "Student 2" for the grade item "Manual item 2"
And I give the grade "-10.00" to the user "Student 2" for the grade item "Manual item 3"
And I give the grade "50.00" to the user "Student 2" for the grade item "Manual item 4"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 5"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 6"
And I give the grade "50.00" to the user "Student 2" for the grade item "Manual item 5"
And I give the grade "50.00" to the user "Student 2" for the grade item "Manual item 6"
And I press "Save changes"
And I navigate to "View > User report" in the course gradebook
And I click on "Student 1" in the "user" search widget
+197
View File
@@ -0,0 +1,197 @@
<?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_grades\external;
use external_api;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* Unit tests for the core_grades\external\get_gradable_users.
*
* @package core_grades
* @category external
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>
* @covers \core_grades\external\get_gradable_users
*/
class get_gradable_users_test extends \externallib_advanced_testcase {
/**
* Test the behaviour of get_gradable_users.
*
* @dataProvider execute_data
* @param bool $onlyactiveenrol if we should only return active enrolments
* @param bool $grouprestricted if we should only return users within a group
* @param array $expected expected users
*/
public function test_execute(bool $onlyactiveenrol, bool $grouprestricted, array $expected): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$generator = $this->getDataGenerator();
$course = $generator->create_course();
// Create and enrol test users.
$student1 = $generator->create_user(['username' => 'student1', 'firstname' => 'Apple', 'lastname' => 'Apricot']);
$student2 = $generator->create_user(['username' => 'student2', 'firstname' => 'Banana', 'lastname' => 'Blueberry']);
$student3 = $generator->create_user(['username' => 'student3', 'firstname' => 'Cherry', 'lastname' => 'Cranberry']);
$student4 = $generator->create_user(['username' => 'student4', 'firstname' => 'Durian', 'lastname' => 'Dracontomelon']);
$student5 = $generator->create_user(['username' => 'student5', 'firstname' => 'Eggplant', 'lastname' => 'Ensete']);
$role = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
$generator->enrol_user($student1->id, $course->id, $role->id);
$generator->enrol_user($student2->id, $course->id, $role->id);
$generator->enrol_user($student3->id, $course->id, $role->id);
$generator->enrol_user($student4->id, $course->id, $role->id);
$generator->enrol_user($student5->id, $course->id, $role->id, 'manual', 0, 0, ENROL_USER_SUSPENDED);
$group1 = $generator->create_group(['courseid' => $course->id]);
$group2 = $generator->create_group(['courseid' => $course->id]);
$generator->create_group_member(['userid' => $student1->id, 'groupid' => $group1->id]);
$generator->create_group_member(['userid' => $student1->id, 'groupid' => $group2->id]);
$generator->create_group_member(['userid' => $student2->id, 'groupid' => $group2->id]);
$generator->create_group_member(['userid' => $student3->id, 'groupid' => $group2->id]);
$DB->set_field('course', 'groupmode', SEPARATEGROUPS, ['id' => $course->id]);
$teacher = $generator->create_user(['username' => 'teacher1']);
$role = $DB->get_record('role', ['shortname' => 'editingteacher'], '*', MUST_EXIST);
$generator->enrol_user($teacher->id, $course->id, $role->id);
$generator->create_module('assign', ['course' => $course->id]);
$groupid = $grouprestricted ? $group2->id : 0;
$result = get_gradable_users::execute($course->id, $groupid, $onlyactiveenrol);
$result = external_api::clean_returnvalue(get_gradable_users::execute_returns(), $result);
$mapped = array_map(function($user) {
return [
'fullname' => $user['fullname'],
'firstname' => $user['firstname'],
'lastname' => $user['lastname'],
'profileimageurl' => $user['profileimageurl'],
];
}, array_values($result['users']));
$this->assertEquals($expected, $mapped);
}
/**
* Data provider for test_execute.
*
* @return array
*/
public function execute_data(): array {
return [
'All users' => [
false,
false,
[
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Apple',
'lastname' => 'Apricot',
'fullname' => 'Apple Apricot',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Banana',
'lastname' => 'Blueberry',
'fullname' => 'Banana Blueberry',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Cherry',
'lastname' => 'Cranberry',
'fullname' => 'Cherry Cranberry',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Durian',
'lastname' => 'Dracontomelon',
'fullname' => 'Durian Dracontomelon',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Eggplant',
'lastname' => 'Ensete',
'fullname' => 'Eggplant Ensete',
],
],
],
'Only active enrolment' => [
true,
false,
[
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Apple',
'lastname' => 'Apricot',
'fullname' => 'Apple Apricot',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Banana',
'lastname' => 'Blueberry',
'fullname' => 'Banana Blueberry',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Cherry',
'lastname' => 'Cranberry',
'fullname' => 'Cherry Cranberry',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Durian',
'lastname' => 'Dracontomelon',
'fullname' => 'Durian Dracontomelon',
],
],
],
'Group restricted' => [
false,
true,
[
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Apple',
'lastname' => 'Apricot',
'fullname' => 'Apple Apricot',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Banana',
'lastname' => 'Blueberry',
'fullname' => 'Banana Blueberry',
],
[
'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/_s/boost/core/1/u/f1',
'firstname' => 'Cherry',
'lastname' => 'Cranberry',
'fullname' => 'Cherry Cranberry',
],
],
],
];
}
}
+2
View File
@@ -0,0 +1,2 @@
First name,Last name,Email address,Manual item 1
Student,1,student1@example.com,400
1 First name Last name Email address Manual item 1
2 Student 1 student1@example.com 400
+2
View File
@@ -0,0 +1,2 @@
First name,Last name,Email address,Manual item 1
Student,1,student1@example.com,800
1 First name Last name Email address Manual item 1
2 Student 1 student1@example.com 800
+2
View File
@@ -0,0 +1,2 @@
First name,Last name,Email address,Manual item 1
Student,1,student1@example.com,5
1 First name Last name Email address Manual item 1
2 Student 1 student1@example.com 5
+4 -4
View File
@@ -751,7 +751,7 @@ class lib_test extends \advanced_testcase {
// Now, let's suspend the enrolment of student2.
$this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
// Should return only the active gradable users (student1 and student3).
$gradableusers = get_gradable_users($course->id);
$gradableusers = \grade_report::get_gradable_users($course->id);
$this->assertEqualsCanonicalizing([$student1->id, $student3->id], array_keys($gradableusers));
// Give teacher 'viewsuspendedusers' capability and set a preference to display suspended users.
@@ -761,17 +761,17 @@ class lib_test extends \advanced_testcase {
$this->setUser($teacher);
// Should return all gradable users (including suspended enrolments).
$gradableusers = get_gradable_users($course->id);
$gradableusers = \grade_report::get_gradable_users($course->id);
$this->assertEqualsCanonicalizing([$student1->id, $student2->id, $student3->id], array_keys($gradableusers));
// Reactivate the course enrolment of student2.
$this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student', 'manual', 0, 0, ENROL_USER_ACTIVE);
$this->setAdminUser();
// Should return all gradable users from group1 (student1 and student2).
$gradableusers = get_gradable_users($course->id, $group1->id);
$gradableusers = \grade_report::get_gradable_users($course->id, $group1->id);
$this->assertEqualsCanonicalizing([$student1->id, $student2->id], array_keys($gradableusers));
// Should return all gradable users from group2 (student3).
$gradableusers = get_gradable_users($course->id, $group2->id);
$gradableusers = \grade_report::get_gradable_users($course->id, $group2->id);
$this->assertEqualsCanonicalizing([$student3->id], array_keys($gradableusers));
}
}
+10
View File
@@ -1,6 +1,16 @@
This file describes API changes in /grade/* ;
Information provided here is intended especially for developers.
=== 4.1.7 ===
* Some changes to how print_grade_page_head() sets the page title:
* If a non-empty `$heading` parameter is passed, it will be used as the page title's unique identifying information.
* 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.
* The function gradeimport_csv_load_data::insert_grade_record() now has extra parameter $gradeitem to carry over the grade item related info
for validation.
* The function get_gradable_users() in grade/lib.php has extra param now to retrieve only active enrolments.
* A new webservice core_grades_get_gradable_users has been added to retrieve gradable users for a course.
=== 4.1.6 ===
* The grade `itemname` property contained in the return structure of the following external methods is now PARAM_CLEANHTML:
- `gradereport_user_get_grade_items`

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