Compare commits

..

365 Commits

Author SHA1 Message Date
Eloy Lafuente (stronk7) 4972dd43dd Moodle release 3.6.4 2019-05-11 23:17:47 +02:00
Eloy Lafuente (stronk7) 2f1ed94388 weekly release 3.6.3+ 2019-05-10 20:31:19 +02:00
Eloy Lafuente (stronk7) f49a92efa5 Merge branch 'MDL-65537-36-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_36_STABLE 2019-05-10 20:08:45 +02:00
David Mudrák 7fb7ab13dc MDL-65537 lang: Use fixed strings in tests, too 2019-05-10 16:12:10 +02:00
Helen Foster 34d9787dec MDL-65537 lang: Import fixed English strings (en_fix) 2019-05-10 16:11:45 +02:00
Eloy Lafuente (stronk7) d890ed6adf Merge branch 'MDL-65558-36' of git://github.com/andrewnicols/moodle into MOODLE_36_STABLE 2019-05-10 11:30:10 +02:00
Jun Pataleta ed10f401df Merge branch 'MDL-65540-36' of git://github.com/andrewnicols/moodle into MOODLE_36_STABLE 2019-05-10 15:58:46 +08:00
Andrew Nicols df5e6e00fb MDL-65558 enrol_database: use , as port separator 2019-05-10 12:24:42 +08:00
Andrew Nicols 84eed6a5a4 MDL-65558 auth_db: use , as port separator 2019-05-10 12:24:42 +08:00
Andrew Nicols cb857db4f2 MDL-63959 feedback: Adjust unit test for perf 2019-05-10 11:42:51 +08:00
Andrew Nicols ff2435d89c Merge branch 'MDL-63959-36' of git://github.com/tobiasreischmann/moodle into MOODLE_36_STABLE 2019-05-10 11:41:47 +08:00
Shamim Rezaie 111bca500e MDL-65540 core_search: Explicit component name in the str helper 2019-05-10 08:07:31 +08:00
Shamim Rezaie 4701196ce8 MDL-65540 tool_dataprivacy: Explicit component name in the str helper 2019-05-10 08:07:27 +08:00
Eloy Lafuente (stronk7) 8d4f985112 Merge branch 'MDL-65197-36' of https://github.com/paulholden/moodle into MOODLE_36_STABLE 2019-05-09 20:02:38 +02:00
Eloy Lafuente (stronk7) ac3f1bdecd Merge branch 'MDL-29320-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-05-09 19:49:48 +02:00
Jun Pataleta 953195bf38 Merge branch 'MDL-65445_36-qtype_ddwtos' of https://github.com/jonof/moodle into MOODLE_36_STABLE 2019-05-09 15:58:36 +08:00
Andrew Nicols fa6a5a4b29 Merge branch 'MDL-65479_36' of git://github.com/stronk7/moodle into MOODLE_36_STABLE 2019-05-09 15:17:01 +08:00
Andrew Nicols 6be75c49ec Merge branch 'MDL-65435_36' of git://github.com/stronk7/moodle into MOODLE_36_STABLE 2019-05-09 14:51:36 +08:00
Jun Pataleta 5d9bf8fe28 Merge branch 'MDL-65141-36' of git://github.com/jleyva/moodle into MOODLE_36_STABLE 2019-05-09 12:27:39 +08:00
Adrian Greeve c1143b3932 Merge branch 'MDL-58315_36_boost_block_classes' of https://github.com/zpottie/moodle into MOODLE_36_STABLE 2019-05-09 11:33:40 +08:00
Adrian Greeve e2df9eabc5 Merge branch 'MDL-65387-36' of https://github.com/lucaboesch/moodle into MOODLE_36_STABLE 2019-05-09 11:23:44 +08:00
Eloy Lafuente (stronk7) 42481b9ca0 MDL-65435 behat: Support any arg type with behat reruns
Right now, when executing a single rerun of a parallel run
using vendor/bin/behat, not all the argument alternatives
are supported (equal or space). This commit allows both to work.
2019-05-08 23:23:00 +02:00
Luca Bösch ecd0e011ee MDL-65387 dataformats: Table download formats choose pulldown style. 2019-05-08 20:21:33 +02:00
Eloy Lafuente (stronk7) e82c80f706 MDL-65479 repository_url: make regexp PCRE2 compatible
PHP 7.3 comes with the newer PCRE2 engine that is slightly stricter,
more specifically about using unescaped "-" within classes. They
now hat to be at the end of the class or escaped.

This is the only case we have found in core to make it compatible.

Also note that the file changes is, apparently an imported library,
but we have not it registered and already have performed changes to
it, so I've ignored that point. In fact, it has tabs and so on, I've
not modified that either.
2019-05-08 18:27:21 +02:00
Michael Hawkins a8c51871f4 MDL-61738 messageinbound: Fix quota checks & filesize for email uploads
Private files uploaded by email will now honour the file quota limit,
because the filesize is set correctly and checked against users'
remaining personal quota limit. Previously, attachment size was always
set to zero, and quota was checked against the draft area (this is
not valid for email uploads, because each file is moved out of the
draft area as it is processed, so multiple files totalling greater
than the remaining quota would still pass the check).
2019-05-08 17:56:47 +02:00
Dan Marsden d5067bffd2 MDL-64708 cohort: Return url is not used anywhere in core. 2019-05-08 17:56:47 +02:00
Mark Nelson a3d19efab4 MDL-65365 core_message: prevent users from viewing all conversations 2019-05-08 17:56:47 +02:00
Eloy Lafuente (stronk7) 888f55e41e weekly release 3.6.3+ 2019-05-08 17:48:46 +02:00
Adrian Greeve f22174be37 Merge branch 'MDL-65484-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-05-08 17:38:32 +02:00
Andrew Nicols eff5fd4984 Merge branch 'MDL-35396-36' of git://github.com/mihailges/moodle into MOODLE_36_STABLE 2019-05-08 17:38:28 +02:00
Damyon Wiese 09543c0123 MDL-65484 assign: Scrolling overflow CSS
Use relatively positioned elements, not absolutely positioned to prevent
elements being visible when they are scrolled outside the containing drawing region.
2019-05-07 16:21:33 +08:00
Mihail Geshoski 29924b65e3 MDL-35396 lesson: Fix detailed statistics display for essay questions 2019-05-07 13:59:23 +08:00
Jun Pataleta 7819b928b1 Merge branch 'MDL-65313-36-fix' of https://github.com/snake/moodle into MOODLE_36_STABLE 2019-05-07 11:25:53 +08:00
Jake Dallimore 151fc538de MDL-65313 core: fix deletion of group conversation favourites
This now requires itemid, which cannot be null. This is safer. The
context is still used an an additional filter.
2019-05-07 11:03:28 +08:00
Jun Pataleta 391d53f871 Merge branch 'MDL-65153-36-fix1' of http://github.com/damyon/moodle into MOODLE_36_STABLE 2019-05-07 10:08:39 +08:00
Damyon Wiese 64f1f9ed5a MDL-65153 competencies: course competencies page
The webservice to fetch the data for the course competencies page requires the
pluginbaseurl to be set.
2019-05-07 10:02:29 +08:00
Sara Arjona c6631a119c MDL-65313 core_favourite: fix issue with groups_delete_group 2019-05-06 13:03:02 +02:00
Sara Arjona 6af01fb2e4 Merge branch 'MDL-65374-36' of https://github.com/lucaboesch/moodle into
MOODLE_36_STABLE
2019-05-06 12:09:31 +02:00
Jun Pataleta 83af30b285 MDL-65313 core: Fix orphaned favourites cleanup 2019-05-06 16:48:43 +08:00
Jun Pataleta 37858eb4a8 Merge branch 'MDL-65399-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-05-06 16:26:34 +08:00
Sara Arjona 569b5eeac1 Merge branch 'MDL-65396-36' of https://github.com/lucaboesch/moodle into MOODLE_36_STABLE 2019-05-06 10:20:07 +02:00
Jun Pataleta 43a538205f Merge branch 'MDL-65153-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-05-06 16:13:51 +08:00
Shamim Rezaie 5557ec004c MDL-65399 mod_quiz: Handling the edge case when section name is '' 2019-05-06 18:13:42 +10:00
Shamim Rezaie fe3d98f0a0 MDL-65399 mod_quiz: UI should prevent deleting the last slot of section 2019-05-06 18:13:41 +10:00
Shamim Rezaie 2b9c3f1089 MDL-65399 mod_quiz: remove_slot should update the existing info
The remove_slot method should update the remaining slot's slot
attribute. It also should update the remaining slot's question number
and section info.
2019-05-06 18:13:41 +10:00
Shamim Rezaie e6b2cb22d0 MDL-65399 mod_quiz: Remove $slots from the \mod_quiz\structure class
It's enough to have $slotsinorder, and the $slots property
was redundant. There should only be a single source of truth.
2019-05-06 18:13:40 +10:00
Jun Pataleta 6f1ffbdf1f Merge branch 'MDL-65313-36' of https://github.com/snake/moodle into MOODLE_36_STABLE 2019-05-06 16:02:10 +08:00
Adrian Greeve 65faf38134 Merge branch 'MDL-64968-36' of https://github.com/lucaboesch/moodle into MOODLE_36_STABLE 2019-05-06 15:59:10 +08:00
Jake Dallimore a3b8e03c1c MDL-65313 core: upgrade code to remove stale favourites for messaging
These were left behing when deleting a group or a course.
2019-05-06 14:14:38 +08:00
Jake Dallimore daea8b596f MDL-65313 core: delete relevant favourites when deleting a conversation 2019-05-06 14:13:24 +08:00
Adrian Greeve 84d3af595b Merge branch 'MDL-65104-36' of git://git.cameron1729.xyz/moodle into MOODLE_36_STABLE 2019-05-06 11:59:27 +08:00
Adrian Greeve a402077cf2 Merge branch 'MDL-65296_36' of https://github.com/timhunt/moodle into MOODLE_36_STABLE 2019-05-06 11:03:17 +08:00
Jake Dallimore d199355889 Merge branch 'MDL-64988-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-05-06 10:34:31 +08:00
Luca Bösch bdf593e235 MDL-65396 theme_boost: Style duration chooser pulldown select. 2019-05-04 11:02:01 +02:00
Luca Bösch 2ca8477af4 MDL-65374 core_grades: style "Move selected items to" pulldown. 2019-05-04 11:01:50 +02:00
Luca Bösch 947a322df7 MDL-64968 mod_lesson: Style "Yes, I'd like to try again" button. 2019-05-04 11:01:43 +02:00
Eloy Lafuente (stronk7) 0442f44049 weekly release 3.6.3+ 2019-05-03 17:52:10 +02:00
Shamim Rezaie 43b55f1354 MDL-64988 mod_quiz: Fix questions always being added to the last section 2019-05-03 20:19:33 +10:00
zpottie 24bcd5b772 MDL-58315 core: Add support for custom block classes in Boost 2019-05-03 16:14:31 +12:00
Alexander Bias 07c0e0e090 MDL-65157 core_completion: no progress for untracked users
This is a backport of the code from MDL-64898 by Dmitrii Metelkin to Moodle 3.6
2019-05-03 11:54:15 +08:00
Andrew Nicols dde68b8c47 Merge branch 'MDL-65299_boost_theme-colors_override_M36' of https://github.com/Dave-B/moodle into MOODLE_36_STABLE 2019-05-03 11:12:53 +08:00
Adrian Greeve 2b918bc767 Merge branch 'MDL-64851-36' of https://github.com/s-cenni/MDL-64851 into MOODLE_36_STABLE 2019-05-03 10:44:50 +08:00
Jake Dallimore f411cdd29a Merge branch 'MDL-65134-36' of git://github.com/peterRd/moodle into MOODLE_36_STABLE 2019-05-03 10:05:42 +08:00
Andrew Nicols f3b6ddec28 Merge branch 'MDL-65262-36' of git://github.com/lameze/moodle into MOODLE_36_STABLE 2019-05-03 10:02:54 +08:00
Simey Lameze 091cad4be8 MDL-65262 core_calendar: change location param type 2019-05-03 09:43:02 +08:00
Andrew Nicols d941f7a2bd Merge branch 'MDL-64394-accesslog-MOODLE_36_STABLE' of https://github.com/brendanheywood/moodle into MOODLE_36_STABLE 2019-05-03 08:50:26 +08:00
Sara Arjona cf91bb38cf MDL-65169 core_message: remove extra line at the end 2019-05-02 15:44:48 +02:00
Sara Arjona c487276abc Merge branch 'MDL-65169-36' of https://github.com/snake/moodle into MOODLE_36_STABLE 2019-05-02 15:29:22 +02:00
Andrew Nicols 687af3bae7 MDL-65447 theme: Apply pendingJS checks to BS transitions 2019-05-02 13:34:25 +02:00
Jun Pataleta 525db9e050 Merge branch 'MDL-65411-36' of git://github.com/lameze/moodle into MOODLE_36_STABLE 2019-05-02 17:02:43 +08:00
Ryan Wyllie 07d60fe334 MDL-65134 core_message: Introduce event specific function
Introduced an event specific formatting function.
Additional catch on render fn calls
2019-05-02 16:24:13 +08:00
Peter 3a7b1b4d6e MDL-65134 core_message: Rerender the message with the updated last msg
* When a new last message we need to reorder the messages within the
message list. Call render for this.
* Standardize the cached items
* Delete cached entries on clear
2019-05-02 16:17:54 +08:00
Jun Pataleta 3332b5ed21 Merge branch 'MDL-65446-36' of https://github.com/ryanwyllie/moodle into MOODLE_36_STABLE 2019-05-02 15:01:14 +08:00
Jake Dallimore f05770656d Merge branch 'MDL-64439-MOODLE_36_STABLE' of https://github.com/ilya-catalyst/moodle into MOODLE_36_STABLE 2019-05-02 14:52:39 +08:00
Jun Pataleta 61dec4c904 Merge branch 'MDL-65273-36' of https://github.com/lucaboesch/moodle into MOODLE_36_STABLE 2019-05-01 17:25:12 +08:00
Paul Holden e7bc8b35ee MDL-65197 editor: handle empty user preference in privacy export. 2019-05-01 09:53:32 +01:00
Jun Pataleta 17a25fbe04 Merge branch 'MDL-65250_36' of git://github.com/stronk7/moodle into MOODLE_36_STABLE 2019-05-01 16:04:12 +08:00
Luca Bösch 168ac5c80a MDL-65273 roles: Roles permissions buttons styles. 2019-05-01 09:28:32 +02:00
Adrian Greeve b0d6391ab7 Merge branch 'MDL-65149-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-05-01 14:59:26 +08:00
Adrian Greeve c74b156127 Merge branch 'MDL-64959_36_STABLE' of https://github.com/abhimanyuZ/moodle into MOODLE_36_STABLE 2019-05-01 14:31:12 +08:00
Adrian Greeve a11e641a3a Merge branch 'MDL-65031-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-05-01 14:05:00 +08:00
Adrian Greeve 832292c29e Merge branch 'MDL-65178-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-05-01 11:57:52 +08:00
Simey Lameze f5dedb71e1 MDL-65411 core_calendar: wrap long description text 2019-05-01 09:55:13 +08:00
Jake Dallimore e7e5880c0f Merge branch 'MDL-60851-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-05-01 09:33:14 +08:00
Damyon Wiese 4d6a6fb033 MDL-60851 backup: coding style fixes 2019-05-01 08:51:22 +08:00
Damyon Wiese f86abcf932 MDL-60851 backup: Sanitise setting dependencies
The only different between each setting dependency type is the evaluation of the condition,
and the mform js validation arguments - so that should be the only thing that is extended
by each subclass.
2019-05-01 08:51:22 +08:00
Damyon Wiese 42c6dadcd4 MDL-60851 backup: Fix undefined $value 2019-05-01 08:51:22 +08:00
Damyon Wiese cb49d89ba4 MDL-60851 backup: More unit tests for dependencies 2019-05-01 08:51:22 +08:00
Eloy Lafuente (stronk7) f2c4f5cd07 Merge branch 'MDL-65397-36-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_36_STABLE 2019-05-01 01:43:03 +02:00
Eloy Lafuente (stronk7) 3bbf743330 weekly release 3.6.3+ 2019-05-01 00:28:47 +02:00
Eloy Lafuente (stronk7) edad20f8e4 Merge branch 'install_36_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_36_STABLE 2019-05-01 00:28:45 +02:00
Juan Leyva 9c5af7f9ec MDL-65141 tool_mobile: Return all the language translations 2019-04-30 15:25:39 +02:00
Jun Pataleta 9a6022b235 MDL-29320 user: Make email query case-insensitive 2019-04-30 16:21:25 +08:00
Jun Pataleta 90f26d99ab MDL-29320 admin: Improve test for multiple accounts with same email
* Use Scenario Outlines
* More coverage:
  - Test for email case-sensitivity
  - Updating user profile as admin
  - Updating own user profile
2019-04-30 16:21:25 +08:00
Jun Pataleta 35ed170068 MDL-29320 user: Remove @javascript tag for add new user behat test
* It's not necessary and will improve Behat execution time
2019-04-30 16:21:25 +08:00
Tobias Reischmann c106bcfc54 MDL-63959 mod_feedback: Add phpunit tests for get_pages in completion 2019-04-30 07:57:17 +02:00
Zoltán Szarvas f4e974d2fc MDL-63959 mod_feedback: Fixed nested dependency handling
For example the dependecy chain is the following: A->B->C. When a
question (A) depends on another dependent item (B) and B hasn't
displayed (because of C's response), the $value for the B's response
will be null. In this case the can_see_item() method returned
null. Because the can_see_item() returned null (not false), the
get_pages() method displayed the question A, because it checks for
explicit false: $this->can_see_item($item) !== false.
Now, false is also returned, if the dependent question is not visible.
2019-04-30 07:57:16 +02:00
Ryan Wyllie 9075a96e5d MDL-65446 admin: fix admin email option for cli install 2019-04-30 13:35:43 +08:00
Jake Dallimore a1afcb2e7a MDL-65169 core_message: user search respects profile visibility
Two modes of visibility checking are supported, depending on the value
of the 'messagingallusers' site setting (site-wide messaging):
- If site-wide messaging is enabled, a user may only be returned in
search results if the searching user can view their profile somewhere
(either the site profile or any course profile).
- If site-wide messaging is disabled, a user may only be returned in
search results if the searching user shares a course with them and can
view their course profile in the shared course.
2019-04-30 11:26:40 +08:00
Jonathon Fowler ed35b34461 MDL-65445 qtype_ddwtos: reposition draggables when the drops move 2019-04-30 12:47:04 +10:00
Jake Dallimore baa7be6a86 MDL-65169 core_message: add behat step allowing searching for a string
New step: 'I search for "xx" in messaging'.
2019-04-30 10:40:48 +08:00
Eloy Lafuente (stronk7) acc98001f0 MDL-63137 mod_feedback: discern between user or id better
is_int('1') => false, so better we invert the check that is safer.
2019-04-30 08:50:12 +08:00
AMOS bot e1282ad929 Automatically generated installer lang files 2019-04-30 00:12:26 +00:00
Eloy Lafuente (stronk7) e24d2a78e5 Merge branch 'MDL-65293-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-04-29 22:46:05 +02:00
Eloy Lafuente (stronk7) 7cc659b2b9 Merge branch 'MDL-65439-36' of git://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-04-29 22:41:59 +02:00
Eloy Lafuente (stronk7) 030ed9e798 Merge branch 'MDL-63137-36' of git://github.com/aanabit/moodle into MOODLE_36_STABLE 2019-04-29 20:10:51 +02:00
cescobedo 98c19db4c4 MDL-65439 core_message: Behat tests for manage preferences 2019-04-29 13:28:31 +02:00
cescobedo 344f8adeed MDL-65439 core_message: New step to open settings preferences 2019-04-29 13:20:39 +02:00
Helen Foster 506317da3a MDL-65397 lang: Import fixed English strings (en_fix)
Significant string changes:

* direct:view,gradeimport_direct - wording corrected from 'CSV' to
  'spreadsheet'
* limitanswers_help,mod_choice - additional wording added explaining how
  the setting works with groups
2019-04-29 11:45:16 +02:00
Jun Pataleta 1e8361252d Merge branch 'MDL-65279-36' of https://github.com/tkorner/moodle into MOODLE_36_STABLE 2019-04-29 17:09:20 +08:00
Sara Arjona d437812525 Merge branch 'MDL-64702-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-29 08:42:44 +02:00
Jun Pataleta 87f24237dc Merge branch 'MDL-65206-36' of git://github.com/tobiasreischmann/moodle into MOODLE_36_STABLE 2019-04-29 12:44:43 +08:00
AMOS bot 927c441b10 Automatically generated installer lang files 2019-04-27 00:12:43 +00:00
Eloy Lafuente (stronk7) 138460e98b weekly release 3.6.3+ 2019-04-26 18:41:15 +02:00
Amaia Anabitarte c42a9a49b4 MDL-63137 mod_feedback: PHPUnit tests for new userid parameter 2019-04-26 17:33:14 +02:00
Amaia Anabitarte 3f80870619 MDL-63137 mod_feedback: mod_feedback_core_calendar_provide_event_action
Update mod_feedback_core_calendar_provide_event_action function to add a new userid parameter
Add userid to check capabilities, etc. to feedback completion and structure.
2019-04-26 17:33:14 +02:00
Amaia Anabitarte 9c52e5ea99 MDL-63137 mod_feedback: Pointing TODO tasks to right deprecation issue 2019-04-26 17:33:14 +02:00
Sara Arjona cafb171c3a Merge branch 'MDL-64676-36-fix' of https://github.com/snake/moodle into MOODLE_36_STABLE 2019-04-26 11:50:40 +02:00
Jake Dallimore e8b9a60126 MDL-64676 core_message: fix legacy behat step which failed on firefox
Old step was looking for a link, which didn't exist. Now, we use image.
2019-04-26 15:59:26 +08:00
Damyon Wiese 748117eb47 MDL-64702 badges: Add missing data to backup
Backup and restore of badges is missing data:
* related badges
* alignments
* endorsement

Also added to "clone" (duplicate a badge).
2019-04-26 09:38:37 +08:00
Eloy Lafuente (stronk7) 6c948829cd MDL-60418 Behat: Fix missing $installurl 2019-04-25 23:38:58 +02:00
Eloy Lafuente (stronk7) 605d3e8d98 Merge branch 'MDL-60418_36' of git://github.com/timhunt/moodle into MOODLE_36_STABLE 2019-04-25 23:26:06 +02:00
Tim Hunt ba61f2f91b MDL-60418 Behat: Fix broken docs links in Behat code. 2019-04-25 21:39:27 +01:00
David Monllaó 8cd7ebf239 Merge branch 'MDL-64673-36' of git://github.com/aanabit/moodle into MOODLE_36_STABLE 2019-04-25 18:41:46 +02:00
Amaia Anabitarte eae54c38f7 MDL-64673 core_message: Modifying group_messate_tab xpath 2019-04-25 10:07:53 +02:00
Eloy Lafuente (stronk7) 6bfe5a229d Merge branch 'MDL-65170-36' of https://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-04-24 20:37:23 +02:00
David Monllaó 937b5c4c69 Merge branch 'MDL-64672-36' of git://github.com/aanabit/moodle into MOODLE_36_STABLE 2019-04-24 20:07:06 +02:00
Sara Arjona cab2c6ed9b MDL-65170 core_user: fix behat for viewing full profile
Users belonging to the same course with separate groups forced
will only be able to find their contacts or other users on the
same group.
2019-04-24 19:53:17 +02:00
Amaia Anabitarte 808c1d1407 MDL-64673 core_message: Starring conversations behat test 2019-04-24 18:16:24 +02:00
Amaia Anabitarte 6a6da30b4d MDL-64666 core_message: Favourite private conversations data generator 2019-04-24 18:16:24 +02:00
David Monllaó 5413077874 Merge branch 'MDL-64985-36' of git://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-04-24 18:13:56 +02:00
David Monllaó 6ffae61d70 Merge branch 'MDL-64666-36' of git://github.com/aanabit/moodle into MOODLE_36_STABLE 2019-04-24 18:07:50 +02:00
Sara Arjona ab20b56894 MDL-64985 message: display aria label in user profile link
Remove the aria-hidden label to display the label/text in
the user profile link.
2019-04-24 18:04:34 +02:00
Sara Arjona e2d400a136 MDL-64985 message: add better aria labels to the actions menu
Add better aria labels to the action menus in messaging, renaming
some labels and declaring it as a menu.
2019-04-24 18:04:27 +02:00
Sara Arjona 0f7c44657e MDL-64985 message: add aria label to cancel selection button
Add aria labels for cancel edit message button (the little cross
when selecting messages).
2019-04-24 18:04:20 +02:00
Amaia Anabitarte 4c9c07a855 MDL-64666 core_message: New behat test for delete conversation 2019-04-24 17:43:52 +02:00
Amaia Anabitarte 7e60d9f35f MDL-64666 core_message: Favourite private conversations data generator 2019-04-24 17:43:52 +02:00
Eloy Lafuente (stronk7) 25e6630c5d Merge branch 'MDL-64267-36' of git://github.com/bmbrands/moodle into MOODLE_36_STABLE 2019-04-24 15:23:37 +02:00
Eloy Lafuente (stronk7) d4f2b45455 Merge branch 'MDL-64151-36-fix' of https://github.com/marinaglancy/moodle into MOODLE_36_STABLE 2019-04-24 12:49:27 +02:00
Bas Brands bc270ce303 MDL-64267 core_message: fix z-index on sticky positioned notification 2019-04-24 11:58:45 +02:00
Marina Glancy 90667b82e8 MDL-64151 forms: select element without selection
Select element that is not multiple should export null when nothing is
selected. If the element is displayed there will always be something
selected.
2019-04-24 11:40:39 +02:00
Jake Dallimore 09d7853909 Merge branch 'MDL-64267-36' of git://github.com/bmbrands/moodle into MOODLE_36_STABLE 2019-04-24 16:55:19 +08:00
Amaia Anabitarte 179f527ae3 MDL-64672 core_message: Behat tests for unread messages 2019-04-24 10:00:52 +02:00
Amaia Anabitarte e2c4522e8c MDL-64666 core_message: Favourite private conversations data generator 2019-04-24 10:00:52 +02:00
Bas Brands 57ec34a9cc MDL-64267 core_message: sticky positioning of contact request info 2019-04-24 09:31:38 +02:00
Tobias Reischmann 7d02fe560a MDL-64267 core_message: Removed pb-2 from message-container
The padding at the bottom broke layout in clean theme since it caused an
overflow, which resulted in a second scrollbar on the message-drawer.
2019-04-24 09:31:37 +02:00
Tobias Reischmann 408efe43c5 MDL-64267 core_message: Moved contact-request to dont cause scrollbar
The contact-request-sent-message-container was outside of the
conversation body and caused multiple overflows and thus multiple
scrollbars. Moving it inside does not have an effect on the layout
except one scrollbar is enough now.
2019-04-24 09:31:37 +02:00
Sara Arjona ea210f78e4 Merge branch 'MDL-65170-36' of https://github.com/snake/moodle into MOODLE_36_STABLE 2019-04-24 09:02:35 +02:00
Adrian Greeve debc37f3c2 Merge branch 'MDL-65375-m36' of https://github.com/sammarshallou/moodle into MOODLE_36_STABLE 2019-04-24 10:30:22 +08:00
Adrian Greeve e642c550c4 Merge branch 'MDL-65308_36' of git://github.com/stronk7/moodle into MOODLE_36_STABLE 2019-04-24 09:28:41 +08:00
Eloy Lafuente (stronk7) dfc4176c4a Merge branch 'MDL-64676-36' of https://github.com/aanabit/moodle into MOODLE_36_STABLE 2019-04-23 19:48:42 +02:00
Eloy Lafuente (stronk7) 796a7c9b67 Merge branch 'MDL-64151-36' of git://github.com/marinaglancy/moodle into MOODLE_36_STABLE 2019-04-23 19:38:40 +02:00
Eloy Lafuente (stronk7) 972146bb1e weekly release 3.6.3+ 2019-04-23 17:36:27 +02:00
David Monllaó eaba78a8b2 Merge branch 'MDL-64102-36' of git://github.com/bmbrands/moodle into MOODLE_36_STABLE 2019-04-23 13:34:06 +02:00
David Monllaó e9a8d6e781 Merge branch 'MDL-65102-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-04-23 13:34:03 +02:00
Damyon Wiese da59e61407 MDL-64702 badges: Add to privacy
Add endorsement, related badges and alignment to privacy export.
2019-04-23 11:15:32 +08:00
Eloy Lafuente (stronk7) 7bdb7736b3 weekly release 3.6.3+ 2019-04-23 11:15:32 +08:00
AMOS bot dfd771cddb Automatically generated installer lang files 2019-04-23 11:15:31 +08:00
sam marshall 2327fedbea MDL-65375 Restore: Null data in other field should be serialised
If the 'other' field is empty, then following a restore of course
logs, it should still be serialised so that it is consistent with
the normal behaviour when logging.

Without this change, an event with null value for other has the
field set to NULL if you restore it, but 'N;' when the event is
initially created.
2019-04-18 15:04:18 +01:00
Adrian Greeve 5c0c0f1ceb Merge branch 'MDL-29318-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-04-18 13:49:52 +08:00
Eloy Lafuente (stronk7) 2711a4013a weekly release 3.6.3+ 2019-04-17 20:04:27 +02:00
Eloy Lafuente (stronk7) 5e650c09fc Merge branch 'install_36_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_36_STABLE 2019-04-17 20:04:23 +02:00
Eloy Lafuente (stronk7) f62ab50109 MDL-65308 core: change a couple of unsafe preg_quote() cases
With PHP 7.3, the hash (#) is being escaped by preg_quote().

While normally that doesnt have much impact and normal operations
continue working perfectly... when the results of the function are
used to match against the same string... they don't match anymore.

Have found a couple of there double-uses in core and this
commit fixes them. Covered with tests.
2019-04-16 02:13:40 +02:00
AMOS bot 85e6250179 Automatically generated installer lang files 2019-04-13 00:13:21 +00:00
Eloy Lafuente (stronk7) 808a369677 weekly release 3.6.3+ 2019-04-12 21:35:15 +02:00
Eloy Lafuente (stronk7) 29e6d9d9eb Merge branch 'install_36_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_36_STABLE 2019-04-12 21:35:13 +02:00
Jake Dallimore 604796406c Merge branch 'MDL-62974-36' of github.com:abgreeve/moodle into MOODLE_36_STABLE 2019-04-12 11:17:50 +08:00
Adrian Greeve 25f240bd6f MDL-62974 mod_lesson: Minor update to html class. 2019-04-12 11:08:46 +08:00
Eloy Lafuente (stronk7) 29c5b2b01f Merge branch 'MDL-62974-36' of https://github.com/jmvedrine/moodle into MOODLE_36_STABLE 2019-04-11 23:50:07 +02:00
Marina Glancy dd971a0a45 MDL-64151 form: autocomplete element remove last choice
This is the same situation as was in MDL-56864 but now in 'course', 'autocomplete' and 'select' elements
- if the element allows multiple selection it is impossible to remove the last element
2019-04-11 16:46:40 +02:00
Sara Cenni 0a1a11aae4 MDL-64851 badge: reviewing badges of invisible courses 2019-04-11 13:29:57 +02:00
Adrian Greeve a017a672bb Merge branch 'MDL-64719-purge_user_cache-36' of git://github.com/leonstr/moodle into MOODLE_36_STABLE 2019-04-11 13:39:16 +08:00
David Balch 0df5a25aca MDL-65299 Boost: Allow default.scss theme-colors override. 2019-04-10 14:13:18 +00:00
Adrian Greeve 39a920802e Merge branch 'MDL-46975-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-04-10 14:25:30 +08:00
AMOS bot 23d1362718 Merge branch 'MOODLE_36_STABLE' into install_36_STABLE 2019-04-10 00:12:20 +00:00
Jean-Michel Vedrine 58b6b184b3 MDL-62974 lesson: Suppress HTML font tags 2019-04-09 22:58:13 +02:00
Eloy Lafuente (stronk7) dcaa783741 weekly release 3.6.3+ 2019-04-09 20:37:08 +02:00
Eloy Lafuente (stronk7) ad55a7047e Merge branch 'MDL-64819-36' of https://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-04-09 16:03:25 +02:00
Sara Arjona 550c3afcd8 MDL-64819 course: fix behat errors with the move icon
Some errors have been detected when using Clean + Goutte.
2019-04-09 14:13:56 +02:00
AMOS bot efcf91a9a1 Automatically generated installer lang files 2019-04-09 00:11:24 +00:00
AMOS bot 04a30bd4df Automatically generated installer lang files 2019-04-09 00:11:24 +00:00
Amaia Anabitarte eeb0c2f410 MDL-64676 core_message: Fix old behat tests affected by new messaging 2019-04-08 19:49:56 +02:00
Sara Arjona 4831903987 Merge branch 'MDL-64819-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-08 14:20:41 +02:00
Tim Hunt 991ecfec28 MDL-65296 questions: field should be declared
It was already being set, but IDE autocomplete etc did not know that.
2019-04-08 12:24:49 +01:00
Sara Arjona f32bb17861 Merge branch 'MDL-64669-36' of git://github.com/aanabit/moodle into MOODLE_36_STABLE 2019-04-08 13:12:49 +02:00
Bas Brands 828b40dca1 MDL-64102 core_message: add message tails to match ux design 2019-04-08 12:37:22 +02:00
Adrian Greeve 01872fd505 Merge branch 'MDL-65218-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-08 16:55:10 +08:00
Amaia Anabitarte a64fdeacd8 MDL-64669 core_message: Waiting for buttons to hide before login out 2019-04-08 10:25:02 +02:00
Amaia Anabitarte 1118af2a3b MDL-64669 core_message: New steps and selectors for messaging 2019-04-08 10:24:35 +02:00
Amaia Anabitarte 018aecdeb5 MDL-64669 core_message: Behat tests for send messages 2019-04-08 10:24:35 +02:00
Damyon Wiese fadc44f298 MDL-64819 output: Hide icons with no title
When output from javascript they should match what gets output from php.
2019-04-08 15:41:18 +08:00
Shamim Rezaie 1987a409b8 MDL-65293 core_calendar: Privacy deletion fix
Deleting in course context should not delete from course modules
2019-04-08 15:39:36 +10:00
Jake Dallimore b93b0d6355 MDL-65170 core_user: perf improvement for user_get_user_details_courses
We don't always need to iterate over the user's courses, so only fetch
them when we do.
2019-04-08 09:04:54 +08:00
Jake Dallimore e3b664447b MDL-65170 core_user: user_get_user_details_courses respects groupmode
This had a relatively simply check for course profile visibility, which
meant that users in separate groups could see one another's profile.
This has been replaced with the more correct check in
user_can_view_profile().
2019-04-08 09:04:54 +08:00
Eloy Lafuente (stronk7) 6155dd23d2 weekly release 3.6.3+ 2019-04-05 18:32:39 +02:00
Thomas Korner 0553a6d90c MDL-65279 core: typo in categoriesandcourses fixed
AMOS BEGIN
 MOV [categoriesandcoures,core],[categoriesandcourses,core]
AMOS END
2019-04-05 14:36:27 +02:00
Damyon Wiese 9223d556fb MDL-64819 output: Activity menu icons
Remove duplciate alt text from activity menu icons and the links to add new sections.
2019-04-05 12:02:56 +08:00
Eloy Lafuente (stronk7) 8e1ab8a382 Merge branch 'MDL-65029_36' of git://github.com/timhunt/moodle into MOODLE_36_STABLE 2019-04-05 00:40:38 +02:00
Eloy Lafuente (stronk7) 4cb5500048 Merge branch 'MDL-65127-36' of git://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-04-05 00:13:10 +02:00
Eloy Lafuente (stronk7) 9cc403d680 Merge branch 'MDL-65094-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-04-04 22:38:29 +02:00
Eloy Lafuente (stronk7) 8d98d2c9fd Merge branch 'MDL-65038_MOODLE_36_STABLE' of https://github.com/maksudr/moodle into MOODLE_36_STABLE 2019-04-04 20:31:13 +02:00
Eloy Lafuente (stronk7) 44160cf213 MDL-65087 behat: Select the proper search button via xpath
By adding the "Search" aria label to a number of elements
any search of "Search" buttons, previously working in other
parts of the UI are not found anymore (because the hidden
ones are found before).

So, moving to click via xpath. Ideally we should be able to
find the target button in an easier, human readable way. But
there aren't many ids, names around to make it easier.
2019-04-04 11:39:12 +08:00
Jun Pataleta 1b57e6b87a Merge branch 'MDL-62307_MOODLE_36_STABLE' of https://github.com/maksudr/moodle into MOODLE_36_STABLE 2019-04-03 16:50:14 +08:00
Jun Pataleta 885cf9f06a Merge branch 'MDL-65112-MOODLE_36_STABLE' of https://github.com/dmitriim/moodle into MOODLE_36_STABLE 2019-04-03 16:19:28 +08:00
Jake Dallimore 8923fb3bb1 Merge branch 'MDL-65087-36' of git://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-04-03 14:12:53 +08:00
Jake Dallimore e246171b56 Merge branch 'MDL-65247-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-04-03 13:47:12 +08:00
Jun Pataleta 1ecc0f0769 MDL-65247 core: Fix action_modal Behat tests 2019-04-03 08:46:00 +08:00
Eloy Lafuente (stronk7) 21573d41f7 MDL-65250 core: Fix some undefined variables in compact()
Since PHP 7.3 they throw a PHP Notice.
2019-04-03 02:43:58 +02:00
Eloy Lafuente (stronk7) b553489c13 Merge branch 'MDL-65213-36' of git://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-04-03 00:23:06 +02:00
Eloy Lafuente (stronk7) b7fe40f96a Merge branch 'MDL-64994-36-pythonmlversion' of git://github.com/mudrd8mz/moodle into MOODLE_36_STABLE 2019-04-03 00:09:38 +02:00
Sara Arjona 3efb002aaa MDL-65213 core_message: fix random failures when declining contacts 2019-04-02 20:28:41 +02:00
Eloy Lafuente (stronk7) 2bcef03b09 weekly release 3.6.3+ 2019-04-02 15:53:01 +02:00
Eloy Lafuente (stronk7) 7a13dabb1a Merge branch 'install_36_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_36_STABLE 2019-04-02 15:52:54 +02:00
Jun Pataleta 97e3be3cf6 Merge branch 'MDL-64331-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-02 10:56:54 +08:00
Adrian Greeve 7388f721e9 Merge branch 'MDL-65144-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-02 10:16:29 +08:00
Damyon Wiese 3f9247df88 MDL-64331 modals: Be careful closing modals
Don't close a modal when the user clicks outside of it and the modal contains a form.
2019-04-02 10:03:03 +08:00
Brendan Heywood 4ad8be5c62 MDL-64394 core: Fixed bug when using access log with pre_loginpage_hook 2019-04-02 11:41:25 +11:00
Ilya Tregubov a6b98fe716 MDL-64439 auth: Refresh $cm in require_login after pre_loginpage_hook. 2019-04-02 10:23:40 +11:00
Leon Stringer 435d91e4bb MDL-64719 cache: Implement purge_current_user() in \cache
\cache::make() may return a cache_disabled subclass but callers, specifically
user/lib.php:user_create_user(), invoke \cache::purge_current_user() which was
only implemented by the cache_session subclass.  Added empty
\cache::purge_current_user() which subclasses can implement, i.e.
cache_session, or not, i.e. cache_disabled.
2019-04-01 15:35:21 +01:00
Damyon Wiese baccabf20b MDL-65144 output: Add img-fluid to logo
Extra wide logos do not scale to the screen size - this change adds the bootstrap class
img-fluid to logos on the loginpage, signupform and in the context header.

Thanks to Maksud R for working on this.
2019-04-01 15:51:18 +08:00
Jake Dallimore 9dcb7d057d Merge branch 'MDL-65089_36' of https://github.com/dvdcastro/moodle into MOODLE_36_STABLE 2019-04-01 15:30:27 +08:00
Jake Dallimore 77fe79ff6d Merge branch 'MDL-64194-36_block_myoverview_incorrect_pagination' of https://github.com/tomdickman/moodle into MOODLE_36_STABLE 2019-04-01 14:36:24 +08:00
Jun Pataleta 21e0be247c Merge branch 'MDL-64524-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-01 13:46:41 +08:00
Jun Pataleta 564019efff Merge branch 'MDL-63906_36' of git://github.com/dmonllao/moodle into MOODLE_36_STABLE 2019-04-01 10:32:00 +08:00
Adrian Greeve b5dfcb9e07 Merge branch 'MDL-65173_36' of git://github.com/markn86/moodle into MOODLE_36_STABLE 2019-04-01 10:22:36 +08:00
Eloy Lafuente (stronk7) 49681cd03d MDL-65218 recycle bin: cover immunization via settings provider
Both for course and category recycle bins
2019-04-01 09:26:50 +08:00
Eloy Lafuente (stronk7) cdcc3ab5da MDL-65218 recycle bin: immunize recycle bin from backup_auto_storage
As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
setting (storing backups @ real location. For recycle bin we want to ensure that
backup files are always stored in Moodle file area. In order to achieve that, we
hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
operations.  See MDL-65218 for more information.

This hack will be removed once recycle bin switches to use its own backup mode, with
own preferences and 100% appart from MODLE_AUTOMATED.
2019-04-01 09:26:50 +08:00
Eloy Lafuente (stronk7) 165baf349b MDL-65218 recycle bin: Course backups (cat bin) to use MODE_AUTOMATED
Surely this should have been done as part of MDL-63263, but it wasn't.
Better let's do it here so every recycle bin backup is using the
very same mode.
2019-04-01 09:26:50 +08:00
Tim Hunt 5b898cb5e0 MDL-65029 questions: refactor generation of question div id
We are planning to change the id in Moodle 3.7. However, the new
method name has been back-ported to stable branches, because we
know a lot of third-party question types like to have a single
branch which supports multiple Moodle versions.
2019-03-29 13:24:53 +00:00
Tobias Reischmann 24e4f1d1c8 MDL-65206 calendar: Enable update of location in repeated events.
The location is now updated in repeated events if it is altered in one
event and the option 'Also apply changes to the other X events in this
repeat series' is selected. If the location is not altered but some
other details, then manual changes of the location in other events
will not be overriden.
2019-03-29 14:14:24 +01:00
Sara Arjona 26e2d21925 MDL-65127 message: add aria-labels to the numbers in the overview
Add aria-label to the unread message and notification icons.
Add aria-label to the total conversations, unread conversations
and unread messages in the overview.
Add aria-label to the pending contact requests in the overview
and also in the contacts tab.
Add aria-hidden to the last message date.
2019-03-29 09:38:50 +01:00
Sara Arjona bc1d3f2e2a MDL-65087 message: add aria-label to some buttons on bootstrapbase
The aria-label has been added to the settings and search buttons and
also to the starred image displayed when a conversation is favourited.
2019-03-29 09:34:13 +01:00
Sara Arjona b185b38288 MDL-65087 message: add aria-label to search, settings and starred 2019-03-29 09:34:13 +01:00
Sara Arjona 88426ca6bf MDL-65087 message: add button role to action links on bootstrapbase
For accessibility reasons, add the role="button" attribute to the links
with href="#" performing actions for the bootstrapbase theme, like
the settings icon.
2019-03-29 09:34:13 +01:00
Sara Arjona 644ecdcaf3 MDL-65087 message: add button role to all the links that perfom actions
For accessibility reasons, add the role="button" attribute to the links
with href="#" performing actions, like the icon to open the messaging
drawer.
2019-03-29 09:34:13 +01:00
Jun Pataleta 07b01e4ff7 MDL-46975 core_auth: Make email validation case-insensitive
* Make email query case-insensitive
* Check only for duplicate emails if $CFG->allowaccountssameemail
is empty.
* Compare the values in "Email address" and "Email (again)" in the
signup form in a case-insensitive fashion.
2019-03-29 13:38:34 +08:00
Jun Pataleta 9b7293b190 MDL-46975 auth_email: Behat test for email validation on signup 2019-03-29 13:38:34 +08:00
Jun Pataleta 8c515e95b7 MDL-29318 login: Handle email in case-insensitive manner
* Let get_complete_user_data() handle the fetching of user data and
  handle the logic of the errors to be shown based on the exception
  it throws. This also saves us 1 DB query by eliminating the need to
  count for the users that match a given email first before fetching
  user information.
2019-03-29 13:32:59 +08:00
Jun Pataleta 06c88be18f MDL-29318 login: Additional test for forgot_password_data_provider() 2019-03-29 13:32:59 +08:00
Jun Pataleta 8cb4579884 MDL-29318 core: Fixes for get_complete_user_data()
* Added email in the list of case-insensitive fields.
* New optional parameter $throwexception for \get_complete_user_data().
  If true, an exception will be thrown when there's no matching record
  found or when there are multiple records found for the given field
  value. If false, it will simply return false.
  Defaults to false when not set. This ensures that
  get_complete_user_data() fetches the correct user data.
2019-03-29 13:32:59 +08:00
Jun Pataleta b53ec2bb74 MDL-29318 core: More unit tests for get_complete_user_data() 2019-03-29 13:30:04 +08:00
Tom Dickman a9883c6ee7 MDL-64194 block_myoverview: fix incorrect pagination
Pagination was found to be incorrect due to the all the items event not being triggered if the amount
of courses the user has is equal to the amount set via pagination.
The condition could not just be changed to `>=` as this would trigger the event when there were further
pages to display.
A check of `remainingCourses` was added to condition so if the amount of courses on the current loaded
page is less than the pagination amount, or there are no remaining courses, the all items event is
triggered and the pagination bar hidden correctly.
2019-03-29 11:53:10 +11:00
AMOS bot 1446f3f89c Automatically generated installer lang files 2019-03-29 00:15:26 +00:00
David Castro 709cf42e06 MDL-65089 glossary: Enforcing case comparison in SQL. 2019-03-28 18:33:53 -05:00
Dmitrii Metelkin 0d493433d3 MDL-65112 mod_lesson: fix html for shortanswer responses 2019-03-29 08:32:04 +11:00
Eloy Lafuente (stronk7) 318fd402e1 weekly release 3.6.3+ 2019-03-28 16:49:19 +01:00
Eloy Lafuente (stronk7) 1671a9f7fd Merge branch 'install_36_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_36_STABLE 2019-03-28 16:49:13 +01:00
Shamim Rezaie 72f0984c4e MDL-65102 core_form: autocomplete element to handle submit event 2019-03-28 15:17:50 +11:00
Eloy Lafuente (stronk7) 7da687624c Merge branch 'MDL-63196-36' of git://github.com/lameze/moodle into MOODLE_36_STABLE 2019-03-28 00:12:41 +01:00
Eloy Lafuente (stronk7) 4ecc15449c Merge branch 'MDL-64996-36-untrainedstaticmodels' of git://github.com/mudrd8mz/moodle into MOODLE_36_STABLE 2019-03-27 23:45:41 +01:00
Eloy Lafuente (stronk7) b05f3cdc4b Merge branch 'MDL-62992-36' of git://github.com/lameze/moodle into MOODLE_36_STABLE 2019-03-27 23:06:15 +01:00
Eloy Lafuente (stronk7) 4c627b6a57 Merge branch 'MDL-64664-36' of git://github.com/cescobedo/moodle into MOODLE_36_STABLE 2019-03-27 22:58:40 +01:00
cescobedo d0770f8154 MDL-64664 core_message: Create more specific selector to contact menu 2019-03-27 13:57:44 +01:00
Jun Pataleta 25d5103a4c Merge branch 'MDL-65008_moodle_36_stable' of https://github.com/dvdcastro/moodle into MOODLE_36_STABLE 2019-03-27 10:40:37 +08:00
Simey Lameze 98ab2d0a44 MDL-62992 quiz: fix tags filtering on random question modal 2019-03-27 10:26:19 +08:00
Simey Lameze d57c35472a MDL-62992 question: conditionaly enable question tags feature 2019-03-27 10:26:19 +08:00
Paul Holden ed88c64a5c MDL-43130 access: fix user counting when retrieving assignable roles.
Previously users assigned the same role in a context via multiple
components would be counted multiple times.
2019-03-27 00:17:59 +01:00
Eloy Lafuente (stronk7) f1d125b08e Merge branch 'MDL-65081-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-03-26 22:43:54 +01:00
Eloy Lafuente (stronk7) 9d84af2a52 Merge branch 'MDL-63152-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-03-26 13:28:21 +01:00
Eloy Lafuente (stronk7) 1c52897f85 Merge branch 'MDL-63149-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-03-26 13:25:11 +01:00
Eloy Lafuente (stronk7) 0a9117bd61 Merge branch 'MDL-63151-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-03-26 13:15:55 +01:00
Shamim Rezaie abe0e8fbad MDL-65094 registration: don't encourage registration if on localhost 2019-03-26 17:26:21 +11:00
Damyon Wiese a05eb5558e MDL-65178 competencies: count_competencies fix
Fix sql error in course_module_competency::count_competencies() function.
2019-03-26 09:49:00 +08:00
Sara Arjona 2bf03fc2e0 Merge branch 'MDL-64615-36' of git://github.com/jleyva/moodle into MOODLE_36_STABLE 2019-03-25 18:20:13 +01:00
Sara Arjona 17aaad9033 Merge branch 'MDL-64790-36_apidoc_fix' of https://github.com/nicoroeser/moodle into MOODLE_36_STABLE 2019-03-25 12:38:06 +01:00
Mark Nelson 22f75de983 MDL-65173 message_email: removed bogus logic
This reverts commit 844a59c301.
2019-03-25 17:18:34 +08:00
David Mudrák a825339917 MDL-64994 analytics: Improve the Python package version check
Use the new method to check that the installed Python package has high
enough yet still API-compatible version.
2019-03-25 09:24:06 +01:00
David Mudrák 0e91160efe MDL-64994 analytics: Add a simple semantic version check method
This method is to be used for checking that a compatible version of the
moodlemlbackend package is installed on the server. The package is
expected to use the semantic versioning scheme (semver.org).
2019-03-25 09:24:06 +01:00
David Mudrák 6b1ce97894 MDL-64996 analytics: Make sure the no_teaching model is trained
Due to the bug, the no_teaching model might have been marked as
not-trained in the database. Static predictions models (i.e. those using
a target based on assumptions, not facts) are always considered as
trained. If they were marked as not-trained, the prediction scheduled
task would skip them and they would produce no predictions.

Ideally, such a fix should be done for all static models. But there is
no easy way to do it during the upgrade where accessing the analytics
API is not possible. I don't think there are many models out there that
would be affected by this so this seems to be good enough solution for
now (and the future ability to reset models will cover the rest).
2019-03-25 09:02:10 +01:00
David Mudrák 38ce5978dd MDL-64996 analytics: Don't mark static model as untrained after clearing
Static predictions models (i.e. those using a target based on
assumptions, not facts) are always considered as trained. Clearing them
must not mark them as untrained. Doing so would make them being skipped
by the prediction scheduled task.
2019-03-25 08:59:33 +01:00
Adrian Greeve 5b4c20944d Merge branch 'MDL-60339-36' of git://github.com/Chocolate-lightning/moodle into MOODLE_36_STABLE 2019-03-25 11:11:04 +08:00
Adrian Greeve 12c256a78b Merge branch 'MDL-64492-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-03-25 10:59:19 +08:00
AMOS bot 6b45969dc6 Automatically generated installer lang files 2019-03-25 00:09:24 +00:00
Abhimanyu Kumar 4abab60161 MDL-64959 uploaduser: remove leading/trailing spaces from CSV headings
Earlier while uploading users via CSV, if any leading or trailing space was there in
headings of CSV file then error was displayed which will be difficult for a non-technical
person to understand. After this patch, leading and trailing spaces from headings will
be trimmed automatically.
2019-03-23 22:35:51 +05:30
maksudr 5bdbf3185d MDL-62307 mod_feedback: Decode HTML special char in add_form_element() 2019-03-22 15:45:47 +00:00
Mathew May 159f20a93e MDL-60339 course: Course editing only checked against site:manageblocks.
Add in additional capabilities to the page. When all_editing_caps is called it'll check against
moodle/site:manageblocks & added capabilities. If moodle/site:manageblocks
was not set the user would have trouble editing the page.
2019-03-22 13:50:49 +08:00
Damyon Wiese 24335e6c2b MDL-65153 competencies: Urls course competencies
The template for the course competencies page expects a pluginbaseurl parameter
but the webservice that fetches when rendered by AJAX is missing this property.
2019-03-22 13:17:56 +08:00
Jun Pataleta fe4763e560 MDL-65081 core_output: Unit tests for single_select and url_select 2019-03-22 11:15:10 +08:00
Jun Pataleta 8a80d01b70 MDL-65081 core_output: Set single_select and url_select attributes 2019-03-22 11:15:10 +08:00
Damyon Wiese ee21767d92 MDL-64492 course: Hide activity icons for Ally
Empty alt text results in an image with no label.
role="presentation" does not hide - it only removes the semantic information.
If the image provides no information, make it hidden with aria-hidden.
2019-03-22 11:01:37 +08:00
Simey Lameze d1e488ab9b MDL-63196 core_calendar: add behat scenario 2019-03-22 10:45:47 +08:00
Simey Lameze 5863a07a46 MDL-63196 calendar: add missing option to export category events 2019-03-22 10:45:46 +08:00
Damyon Wiese 50a8ba5661 MDL-65149 emoticons: Do not show all emoticons
Allow some emoticons to still work, but not be selectable from the text editor plugins.
2019-03-22 10:06:39 +08:00
Eloy Lafuente (stronk7) 673922a628 weekly release 3.6.3+ 2019-03-21 20:19:44 +01:00
Cameron Ball 3282dda765 MDL-65104 registration: Ensure $registration is always null when no record exists 2019-03-21 16:12:30 +08:00
Adrian Greeve 5da126bc67 Merge branch 'MDL-46803-greedy-roles-reset-MOODLE_36' of https://github.com/DSI-Universite-Rennes2/moodle into MOODLE_36_STABLE 2019-03-21 11:22:23 +08:00
Eloy Lafuente (stronk7) b503eca0d4 Merge branch 'wip-36-MDL-64563' of https://github.com/adpe/moodle into MOODLE_36_STABLE 2019-03-21 00:43:03 +01:00
Eloy Lafuente (stronk7) 95e75e70b5 MDL-63263 tool_recyclebin: show the note as part of the heading element
It looks better (IMO) than using admin_setting_description and works in all
branches.
2019-03-21 00:14:51 +01:00
Eloy Lafuente (stronk7) d86ad34353 Merge branch 'MDL-63263-MOODLE_36_STABLE' of https://github.com/tuanngocnguyen/moodle into MOODLE_36_STABLE 2019-03-20 23:16:09 +01:00
Eloy Lafuente (stronk7) c0b2cf55e4 Merge branch 'MDL-65022-36' of https://github.com/Dagefoerde/moodle into MOODLE_36_STABLE 2019-03-20 22:31:55 +01:00
Jan Dageförde bdb8dec713 MDL-65059 messaging: don't refer to block plugin type
AMOS BEGIN
 CPY [block,core],[blockuserconfirmbutton,core_message]
AMOS END
2019-03-20 22:22:11 +01:00
M d4fc00666b MDL-65038 core_files: Fix typo in function mock_response 2019-03-20 20:51:28 +00:00
Jun Pataleta 248f5a1efc Merge branch 'MDL-62872-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-03-20 16:30:29 +08:00
Adrian Greeve 036e58c5b5 Merge branch 'MDL-65005-36-fix' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-03-20 13:57:17 +08:00
Jun Pataleta ba7cf5ad88 MDL-65005 tool_policy: Ensure the user's on the Dashboard after agreeing 2019-03-20 13:13:23 +08:00
Eloy Lafuente (stronk7) 3f8bd31bf3 Merge branch 'MDL-65085_36' of git://github.com/dmonllao/moodle into MOODLE_36_STABLE 2019-03-20 00:40:28 +01:00
Eloy Lafuente (stronk7) de8c665fe3 Merge branch 'MDL-64990_36' of git://github.com/markn86/moodle into MOODLE_36_STABLE 2019-03-19 23:05:03 +01:00
Eloy Lafuente (stronk7) 23cd28f38c Merge branch 'MDL-64979-m36' of https://github.com/sammarshallou/moodle into MOODLE_36_STABLE 2019-03-19 22:07:51 +01:00
sam marshall 00c706dc8a MDL-64979 Behat: Add option to increase timeouts 2019-03-19 17:14:46 +00:00
Eloy Lafuente (stronk7) 93973e0472 Merge branch 'MDL-65005-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-03-19 17:57:50 +01:00
Adrian Greeve 3e46ac327a Merge branch 'MDL-64958-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-03-19 13:42:36 +08:00
Adrian Greeve a9afba6a38 Merge branch 'MDL-63144-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-03-19 11:26:03 +08:00
Eloy Lafuente (stronk7) 95764ed91d Merge branch 'MDL-64974-36' of git://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-03-19 00:23:45 +01:00
Eloy Lafuente (stronk7) b1dbc661bb Merge branch 'MDL-64987-36' of git://github.com/abgreeve/moodle into MOODLE_36_STABLE 2019-03-19 00:21:23 +01:00
Eloy Lafuente (stronk7) 809901f7c8 Merge branch 'MDL-65006-36' of git://github.com/sarjona/moodle into MOODLE_36_STABLE 2019-03-18 23:35:59 +01:00
Julien Boulen f8c209cf25 MDL-46803 course: Prevent unenrol users with multiple roles during reset 2019-03-18 11:18:46 +01:00
cescobedo 564ad754a8 MDL-64664 core_message: Behat tests to manage contacts 2019-03-18 09:50:00 +01:00
cescobedo d940ccbe0b MDL-64664 core_message: New data generator for contacts 2019-03-18 09:50:00 +01:00
cescobedo cbf62420e0 MDL-64664 core_message: New step for open contact menu 2019-03-18 09:50:00 +01:00
David Monllaó 066673fb37 MDL-63906 analytics: Release the lock on \Throwable received 2019-03-18 09:09:50 +01:00
Damyon Wiese 0bec5987c8 MDL-64524 gradingform_guide: Marking guide comments
When there are too many comments, scroll the list so they still fit in the dialogue.
2019-03-18 15:57:11 +08:00
Damyon Wiese 7c5c76bcdf MDL-64524 assign: Styles editpdf comments
Properly display the list of comments, but limiting their size (use title to indicate the full comments).
When there are too many comments, show a scrollbar in the menu.
2019-03-18 15:57:11 +08:00
Shamim Rezaie 4ef107fccb MDL-63152 mod_workshop: Add userid param to calendar callbacks 2019-03-18 15:53:28 +11:00
Shamim Rezaie 9addd44bb4 MDL-63152 mod_workshop: check if the module is visible to the user 2019-03-18 15:53:27 +11:00
Shamim Rezaie 0bc2276751 MDL-62872 atto_recordrtc: max_filesize detection fix 2019-03-18 15:20:38 +11:00
Jake Dallimore 3612fa390d Merge branch 'MDL-65291_36' of git://github.com/stronk7/moodle into MOODLE_36_STABLE 2019-03-16 07:17:47 +08:00
Eloy Lafuente (stronk7) 76e06c15c6 Merge branch 'MDL-65179-m36' of https://github.com/sammarshallou/moodle into MOODLE_36_STABLE 2019-04-16 20:45:23 +02:00
David Monllaó ba22230a4f Merge branch 'MDL-62572-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-15 11:07:08 +02:00
David Monllaó cf77a2d72d Merge branch 'wip-MDL-65220-MOODLE_36_STABLE' of https://github.com/jason-platts/moodle into MOODLE_36_STABLE 2019-04-15 07:58:18 +02:00
David Monllaó e8fd0db959 Merge branch 'MDL-64894-36' of git://github.com/damyon/moodle into MOODLE_36_STABLE 2019-04-15 07:45:41 +02:00
David Monllaó 99c7dc1929 Merge branch 'MDL-29317-36' of git://github.com/junpataleta/moodle into MOODLE_36_STABLE 2019-04-15 07:41:45 +02:00
Eloy Lafuente (stronk7) ceba417524 MDL-65291 phpunit: Make a test immune to DST changes 2019-04-15 01:22:41 +02:00
Damyon Wiese 4e5cd5956d MDL-62572 backup: Duplicating an activity
Don't apply admin defaults for import when using the duplicate function.
2019-04-12 14:05:06 +08:00
Trisha Milan c116d32f39 MDL-64894 Theme: Fix unable to expand collapsible topics 2019-04-01 14:54:26 +08:00
Jason Platts e2338e14a7 MDL-65220 behat: test scenarios sometimes do not clear database data 2019-03-29 15:32:43 +00:00
Jun Pataleta 057a4fc85b MDL-29317 gradeimport_csv: Case-insensitive query for email and username
Query using the following fields for
\gradeimport_csv_load_data::check_user_exists() should be done in a
case-insensitive manner:
* email - As agreed in MDL-29315
* username - Although usernames can only be in lowercase during
  registration, usernames are being handled in a case-insensitive
  fashion when logging in. It makes sense to make check_user_exists()
  consistent with this behaviour.
2019-03-29 13:29:49 +08:00
Jun Pataleta dfcad939ff MDL-29317 gradeimport_csv: Tests for case-insensitivity 2019-03-29 13:29:49 +08:00
sam marshall db6be2a885 MDL-65179 Web service: Token last access is updated too frequently 2019-03-28 17:14:02 +00:00
David Monllaó 5dfaf89ba2 MDL-65085 analytics: noreply user sends insights 2019-03-15 13:49:49 +01:00
Nicolas Roeser 0d72003e6e MDL-64790 upgrade: parameter API doc regression fix
Partially update the API documentation of function
upgrade_plugin_savepoint.

This restores consistency between function parameters and API
documentation; as commit 17da2e6f28 (fix
for MDL-16438) had renamed parameter $dir to $plugin, but not also
applied this change to the API documentation.

Also reword the parameter comment text so that it is more meaningful to
developers: document that the parameter contains the name of the plugin.

Change the API documentation of parameter $type as well so that it is
a) correct, and
b) not in conflict with the API documentation for $plugin.
2019-03-15 11:12:40 +01:00
Nathan Nguyen 47b4f21994 MDL-63263 tool_recyclebin: Apply automated backup setting
Use Automated backup settings as settings for Recycle bin backup and restore.
2019-03-15 14:55:08 +11:00
Rex Lorenzo 8e6e56c066 MDL-63263 tool_recyclebin: Recycle bin should always save user data. 2019-03-15 14:55:08 +11:00
Jake Dallimore f3ad037584 weekly release 3.6.3+ 2019-03-15 11:01:30 +08:00
Paul Holden 13898c79db MDL-46931 roles: retrieve distinct users in sub-query.
Prevents duplicate key column warning when a user is enrolled in
a course via multiple enrolment methods.
2019-03-14 01:27:30 +01:00
Eloy Lafuente (stronk7) 323801ad4b Merge branch 'MDL-63146-36' of git://github.com/rezaies/moodle into MOODLE_36_STABLE 2019-03-14 01:22:07 +01:00
Eloy Lafuente (stronk7) c013bb2cbc Merge branch 'MDL-61585_36' of git://github.com/dmonllao/moodle into MOODLE_36_STABLE 2019-03-14 01:15:59 +01:00
Eloy Lafuente (stronk7) 8789bc4fa6 Merge branch 'MDL-63524-36' of git://github.com/andrewnicols/moodle into MOODLE_36_STABLE 2019-03-13 23:55:36 +01:00
Andrew Nicols f02688e713 MDL-63524 behat: Do not skip tests on Step 0 error 2019-03-14 06:54:37 +08:00
Amaia Anabitarte 5eefd56230 MDL-64670 core_message: fixing selector xpath 2019-03-13 17:52:42 +01:00
Juan Leyva 51ee36d039 MDL-64615 tool_mobile: Do not modify custom download URLs 2019-03-13 17:10:28 +01:00
Adrian Greeve db65a2ca7e Merge branch 'MDL-64568_36' of git://github.com/markn86/moodle into MOODLE_36_STABLE 2019-03-13 15:48:40 +08:00
Amaia Anabitarte 6eef6e3ab9 MDL-64670 core_message: behat tests for group conversations
Co-authored-by: Mathew May <mathew@moodle.com>
Co-authored-by: Amaia Anabitarte <amaia@moodle.com>
2019-03-12 18:15:14 +01:00
Amaia Anabitarte 3dbced7596 MDL-64670 core_message: New steps and selectors for message drawer
Co-authored-by: Mathew May <mathew@moodle.com>
Co-authored-by: Amaia Anabitarte <amaia@moodle.com>
2019-03-12 18:15:13 +01:00
Jan Dageförde 86bef7c3b5 MDL-65022 wiki: declare the cancel button secondary 2019-03-12 09:05:42 +01:00
Shamim Rezaie 2436022f22 MDL-63151 mod_wiki: Add userid param to mod_wiki calendar callbacks 2019-03-12 17:16:53 +11:00
Shamim Rezaie 7fbf87df43 MDL-63151 mod_wiki: check if the module is visible to the user 2019-03-12 17:16:53 +11:00
Sara Arjona ce2e9f1cfb MDL-64974 badges: add missing configurecriteria capability check 2019-03-11 10:25:55 +01:00
David Monllaó 5bf54816e8 MDL-61585 analytics: Include missing indicators 2019-03-11 09:38:40 +01:00
Damyon Wiese 560b5b0efc MDL-65031 theme_boost: gradebook notifications
Tell the gradebook page to stack things vertically so that notifications
take the correct amount of space.
2019-03-11 12:47:21 +08:00
Jun Pataleta b211eb7afe MDL-64958 gradeimport_csv: Better unit tests for check_user_exists() 2019-03-11 09:35:45 +08:00
Jun Pataleta fc6d1fe719 MDL-64958 grade_import: Ensure correct user is being fetched
* We need to ensure that we are checking the correct user account.
  Since email and idnumber are not unique fields, there's a chance that
  multiple user records will match when querying for user data using
  these fields. This might lead to a different user's grades being
  inadvertently modified during grade import. In such a case, this
  function needs to return a null userid.
2019-03-11 09:35:45 +08:00
Mark Nelson 93f39f3db6 MDL-64568 core_group: delete conversation and members on group deletion 2019-03-11 09:24:44 +08:00
Mark Nelson c47c19634e MDL-64568 core: upgrade step to remove orphaned group conversations 2019-03-11 09:24:44 +08:00
Mark Nelson 5806a1f251 MDL-64990 tool_usertours: do not show messaging tour when disabled 2019-03-11 07:50:29 +08:00
David Castro 803d87c60f MDL-65008 question: Query enhancement.
Adding core_question as an additional filter to the tag backup.
2019-03-08 16:35:12 -05:00
Sara Arjona ff27e31a89 MDL-65006 policy: avoid loop when no policies need to be agreeded
Avoid an infinite loop when the user has no policies to agreed but
the policyagreed = 0.
2019-03-08 10:02:28 +01:00
Adrian Greeve 949282574a MDL-64987 repository_nextcloud: Remove duplicate string. 2019-03-08 14:35:34 +08:00
Jun Pataleta c42c551cff MDL-65005 block_timeline: Behat fixes 2019-03-08 14:06:41 +08:00
Jun Pataleta d3507a690a MDL-65005 block_timeline: Fix accessibility of Day filter menu 2019-03-08 14:06:41 +08:00
Jun Pataleta 36a5774a81 MDL-65005 block_timeline: Fix accessibility of Sort-by menu 2019-03-08 14:06:37 +08:00
Shamim Rezaie ace9ed3fc8 MDL-63149 mod_survey: Add userid param to mod_survey calendar callbacks 2019-03-07 17:54:43 +11:00
Shamim Rezaie 9042827e3f MDL-63144 mod_lti: Add userid param to mod_lti calendar callbacks 2019-03-07 17:49:16 +11:00
Shamim Rezaie 8b7eb8620a MDL-63144 mod_lti: Check if the module is visible to the user 2019-03-07 17:49:16 +11:00
Shamim Rezaie 8223305818 MDL-63146 mod_quiz: Add userid param to mod_quiz calendar callbacks 2019-03-05 14:23:33 +11:00
Adrian Perez eaaaeface3 MDL-64563 theme_boost: Add page site admin category name word-break 2019-02-09 20:16:21 +01:00
372 changed files with 7017 additions and 1572 deletions
+1 -1
View File
@@ -710,7 +710,7 @@ if ($interactive) {
cli_separator();
cli_heading(get_string('cliadminemail', 'install'));
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminemail']);
$options['adminemail'] = cli_input($prompt);
$options['adminemail'] = cli_input($prompt, $options['adminemail']);
}
// Validate that the address provided was an e-mail address.
+1 -2
View File
@@ -823,8 +823,7 @@ class core_admin_renderer extends plugin_renderer_base {
*/
protected function registration_warning($registered) {
if (!$registered) {
if (!$registered && site_is_public()) {
if (has_capability('moodle/site:config', context_system::instance())) {
$registerbutton = $this->single_button(new moodle_url('/admin/registration/index.php'),
get_string('register', 'admin'));
+5 -2
View File
@@ -69,8 +69,11 @@ class core_role_check_users_selector extends user_selector_base {
if ($coursecontext and $coursecontext != SITEID) {
$sql1 = " FROM {user} u
JOIN {user_enrolments} ue ON (ue.userid = u.id)
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
JOIN (SELECT DISTINCT subu.id
FROM {user} subu
JOIN {user_enrolments} ue ON (ue.userid = subu.id)
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
) subq ON subq.id = u.id
WHERE $wherecondition";
$params['courseid1'] = $coursecontext->instanceid;
+1 -1
View File
@@ -330,7 +330,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_skip_modif_prev', new lang_string('skipmodifprev', 'backup'), new lang_string('skipmodifprevhelp', 'backup'), 0));
// Automated defaults section.
$temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), ''));
$temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), new lang_string('recyclebin_desc', 'backup')));
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', new lang_string('generalusers', 'backup'), new lang_string('configgeneralusers', 'backup'), 1));
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_role_assignments', new lang_string('generalroleassignments','backup'), new lang_string('configgeneralroleassignments','backup'), 1));
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), 1));
+2 -2
View File
@@ -58,7 +58,7 @@ class behat_admin extends behat_base {
// We expect admin block to be visible, otherwise go to homepage.
if (!$this->getSession()->getPage()->find('css', '.block_settings')) {
$this->getSession()->visit($this->locate_path('/'));
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
}
// Search by label.
@@ -67,7 +67,7 @@ class behat_admin extends behat_base {
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
$submitsearch->press();
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
// Admin settings does not use the same DOM structure than other moodle forms
// but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
@@ -1,57 +1,74 @@
@core @core_admin
Feature: Enable multiple accounts to have the same email address
In order to have multiple accounts registerd on the system with the same email address
@core @core_admin @core_user
Feature: Allowing multiple accounts to have the same email address
In order to manage user accounts
As an admin
I need to enable multiple accounts to be registered with the same email address and verify it is applied
I need to be able to set whether to allow multiple accounts with the same email or not
Background:
Given I log in as "admin"
Scenario: Enable registration of multiple accounts with the same email address
Scenario Outline: Create a user with the same email as an existing user
Given the following config values are set as admin:
| allowaccountssameemail | 1 |
When I navigate to "Users > Accounts > Add a new user" in site administration
| allowaccountssameemail | <allowsameemail> |
And the following "users" exist:
| username | firstname | lastname | email |
| s1 | John | Doe | s1@example.com |
When I log in as "admin"
And I navigate to "Users > Accounts > Add a new user" in site administration
And I set the following fields to these values:
| Username | testmultiemailuser1 |
| Choose an authentication method | Manual accounts |
| New password | test@User1 |
| First name | Test |
| Surname | Multi1 |
| Email address | testmultiemailuser@example.com |
| Username | s2 |
| First name | Jane |
| Surname | Doe |
| Email address | <email> |
| New password | test |
And I press "Create user"
And I should see "Test Multi1"
And I press "Add a new user"
And I set the following fields to these values:
| Username | testmultiemailuser2 |
| Choose an authentication method | Manual accounts |
| New password | test@User2 |
| First name | Test |
| Surname | Multi2 |
| Email address | testmultiemailuser@example.com |
And I press "Create user"
Then I should see "Test Multi2"
And I should not see "This email address is already registered"
Then I should <expect> "This email address is already registered."
Scenario: Disable registration of multiple accounts with the same email address
Examples:
| allowsameemail | email | expect |
| 0 | s1@example.com | see |
| 0 | S1@EXAMPLE.COM | see |
| 1 | s1@example.com | not see |
| 1 | S1@EXAMPLE.COM | not see |
Scenario Outline: Update a user with the same email as an existing user
Given the following config values are set as admin:
| allowaccountssameemail | 0 |
When I navigate to "Users > Accounts > Add a new user" in site administration
And I set the following fields to these values:
| Username | testmultiemailuser1 |
| Choose an authentication method | Manual accounts |
| New password | test@User1 |
| First name | Test |
| Surname | Multi1 |
| Email address | testmultiemailuser@example.com |
And I press "Create user"
And I should see "Test Multi1"
And I press "Add a new user"
And I set the following fields to these values:
| Username | testmultiemailuser2 |
| Choose an authentication method | Manual accounts |
| New password | test@User2 |
| First name | Test |
| Surname | Multi2 |
| Email address | testmultiemailuser@example.com |
And I press "Create user"
Then I should see "This email address is already registered"
| allowaccountssameemail | <allowsameemail> |
And the following "users" exist:
| username | firstname | lastname | email |
| s1 | John | Doe | s1@example.com |
| s2 | Jane | Doe | s2@example.com |
When I log in as "admin"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I click on "Edit" "link" in the "Jane Doe" "table_row"
And I set the field "Email address" to "<email>"
And I press "Update profile"
Then I should <expect> "This email address is already registered."
Examples:
| allowsameemail | email | expect |
| 0 | s1@example.com | see |
| 0 | S1@EXAMPLE.COM | see |
| 1 | s1@example.com | not see |
| 1 | S1@EXAMPLE.COM | not see |
| 0 | S2@EXAMPLE.COM | not see |
| 1 | S2@EXAMPLE.COM | not see |
Scenario Outline: Update own user profile with the same email as an existing user
Given the following config values are set as admin:
| allowaccountssameemail | <allowsameemail> |
And the following "users" exist:
| username | firstname | lastname | email |
| s1 | John | Doe | s1@example.com |
| s2 | Jane | Doe | s2@example.com |
When I log in as "s2"
And I open my profile in edit mode
And I set the field "Email address" to "<email>"
And I press "Update profile"
Then I should <expect> "This email address is already registered."
Examples:
| allowsameemail | email | expect |
| 0 | s1@example.com | see |
| 0 | S1@EXAMPLE.COM | see |
| 1 | s1@example.com | not see |
| 1 | S1@EXAMPLE.COM | not see |
| 0 | S2@EXAMPLE.COM | not see |
| 1 | S2@EXAMPLE.COM | not see |
+3 -3
View File
@@ -162,11 +162,11 @@ class tool_behat_renderer extends plugin_renderer_base {
$html .= $this->output->heading($title);
// Info.
$installurl = behat_command::DOCS_URL . '#Installation';
$installurl = behat_command::DOCS_URL;
$installlink = html_writer::tag('a', $installurl, array('href' => $installurl, 'target' => '_blank'));
$writetestsurl = behat_command::DOCS_URL . '#Writing_features';
$writetestsurl = 'https://docs.moodle.org/dev/Writing acceptance tests';
$writetestslink = html_writer::tag('a', $writetestsurl, array('href' => $writetestsurl, 'target' => '_blank'));
$writestepsurl = behat_command::DOCS_URL . '#Adding_steps_definitions';
$writestepsurl = 'https://docs.moodle.org/dev/Writing_new_acceptance_test_step_definitions';
$writestepslink = html_writer::tag('a', $writestepsurl, array('href' => $writestepsurl, 'target' => '_blank'));
$infos = array(
get_string('installinfo', 'tool_behat', $installlink),
@@ -137,7 +137,7 @@
}} data-status="{{status}}"{{!
}}>
<td>{{typename}}</td>
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}</td>
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}</td>
<td><a href="{{requestedbyuser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{requestedbyuser.fullname}}</a></td>
<td>
<span class="label {{statuslabelclass}}" title="{{statuslabeltitle}}">{{statuslabel}}</span>
@@ -67,7 +67,8 @@
<a href="mailto:{{foruser.email}}">{{foruser.email}}</a>
<div class="clearfix m-t-1 m-b-1">
<span class="pull-left m-r-1">
<strong>{{#str}}daterequesteddetail, tool_dataprivacy{{/str}}</strong> {{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}
<strong>{{#str}}daterequesteddetail, tool_dataprivacy{{/str}}</strong>
{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}
</span>
<span class="pull-left m-r-1">
<strong>{{#str}}statusdetail, tool_dataprivacy{{/str}}</strong>
@@ -137,14 +137,14 @@ abstract class restore_tool_log_logstore_subplugin extends restore_subplugin {
}
}
}
// Now we want to serialize it so we can store it in the DB.
$data->other = serialize($data->other);
} else {
$message = "Event class not found: \"$eventclass\". Skipping log record.";
$this->log($message, backup::LOG_DEBUG);
return; // No such class, can not restore.
}
}
// Now we want to serialize it so we can store it in the DB.
$data->other = serialize($data->other);
return $data;
}
+1
View File
@@ -422,6 +422,7 @@ class external extends external_api {
'comppath' => competency_path_exporter::get_read_structure(),
))),
'manageurl' => new external_value(PARAM_LOCALURL, 'Url to the manage competencies page.'),
'pluginbaseurl' => new external_value(PARAM_LOCALURL, 'Url to the course competencies page.'),
));
}
@@ -113,6 +113,7 @@ class course_competencies_page implements renderable, templatable {
$data->courseid = $this->courseid;
$data->pagecontextid = $this->context->id;
$data->competencies = array();
$data->pluginbaseurl = (new moodle_url('/admin/tool/lp'))->out(true);
$gradable = is_enrolled($this->context, $USER, 'moodle/competency:coursecompetencygradable');
if ($gradable) {
@@ -66,7 +66,7 @@
<div class="clearfix"></div>
{{/canmanagecoursecompetencies}}
{{#competency}}
<a href="{{pluginbaseurl}}user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
<a href="{{pluginbaseurl}}/user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
id="competency-info-link-{{competency.id}}"
title="{{#str}}viewdetails, tool_lp{{/str}}">
<p><strong>{{{competency.shortname}}} <em>{{competency.idnumber}}</em></strong></p>
@@ -672,7 +672,7 @@ class manager {
$attachment->charset = $partdata->getCharset();
$attachment->description = $partdata->getDescription();
$attachment->contentid = $partdata->getContentId();
$attachment->filesize = $messagedata->getBodyPartSize($part);
$attachment->filesize = $partdata->getBytes();
if (!empty($CFG->antiviruses)) {
mtrace("--> Attempting virus scan of '{$attachment->filename}'");
+1 -1
View File
@@ -99,7 +99,7 @@ class api {
$lang = array();
if (!empty($addoninfo['lang'])) {
$stringmanager = get_string_manager();
$langs = $stringmanager->get_list_of_translations();
$langs = $stringmanager->get_list_of_translations(true);
foreach ($langs as $langid => $langname) {
foreach ($addoninfo['lang'] as $stringinfo) {
$lang[$langid][$stringinfo[0]] =
+6
View File
@@ -66,6 +66,12 @@ function tool_mobile_create_app_download_url() {
}
$downloadurl = new moodle_url($mobilesettings->setuplink);
// Do not update the URL if it is a custom one (we may break it completely).
if ($mobilesettings->setuplink != 'https://download.moodle.org/mobile') {
return $downloadurl;
}
$downloadurl->param('version', $CFG->version);
$downloadurl->param('lang', current_language());
@@ -301,6 +301,8 @@ class page_agreedocs implements renderable, templatable {
redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
}
} else {
// Update the policyagreed for the user to avoid infinite loop because there are no policies to-be-accepted.
api::update_policyagreed($userid);
$this->redirect_to_previous_url();
}
}
@@ -82,7 +82,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I should not see "Next"
And I should see "Course overview"
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
And I click on "Accept This site policy" "link" in the "User One" "table_row"
@@ -292,3 +292,23 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
When I press "Give consent"
Then "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Accepted on user's behalf" "text" should exist in the "User Two" "table_row"
Scenario: View acceptances made by users on their own after inactivating a policy
Given I log in as "user1"
And I should see "This site policy"
And I should not see "Course overview"
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I should see "Course overview"
And I log out
And I log in as "admin"
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
And I press "Continue"
And I click on "Set status to \"Inactive\"" "link" in the "This privacy policy" "table_row"
And I press "Continue"
And I log out
When I log in as "user1"
Then I should see "Course overview"
+16 -2
View File
@@ -108,6 +108,16 @@ class category_bin extends base_bin {
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
// As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
// setting (storing backups @ real location. For recycle bin we want to ensure that
// backup files are always stored in Moodle file area. In order to achieve that, we
// hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
// operations. See MDL-65218 for more information.
// This hack will be removed once recycle bin switches to use its own backup mode, with
// own preferences and 100% appart from MODLE_AUTOMATED.
// TODO: Remove this as part of MDL-65228.
$CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
// Backup the course.
$user = get_admin();
$controller = new \backup_controller(
@@ -115,11 +125,15 @@ class category_bin extends base_bin {
$course->id,
\backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO,
\backup::MODE_GENERAL,
\backup::MODE_AUTOMATED,
$user->id
);
$controller->execute_plan();
// We don't need the forced setting anymore, hence unsetting it.
// TODO: Remove this as part of MDL-65228.
unset($CFG->forced_plugin_settings['backup']);
// Grab the result.
$result = $controller->get_results();
if (!isset($result['backup_destination'])) {
@@ -233,7 +247,7 @@ class category_bin extends base_bin {
$tempdir,
$course->id,
\backup::INTERACTIVE_NO,
\backup::MODE_GENERAL,
\backup::MODE_AUTOMATED,
$user->id,
\backup::TARGET_NEW_COURSE
);
+16 -2
View File
@@ -112,6 +112,16 @@ class course_bin extends base_bin {
return;
}
// As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
// setting (storing backups @ real location. For recycle bin we want to ensure that
// backup files are always stored in Moodle file area. In order to achieve that, we
// hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
// operations. See MDL-65218 for more information.
// This hack will be removed once recycle bin switches to use its own backup mode, with
// own preferences and 100% appart from MODLE_AUTOMATED.
// TODO: Remove this as part of MDL-65228.
$CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
// Backup the activity.
$user = get_admin();
$controller = new \backup_controller(
@@ -119,11 +129,15 @@ class course_bin extends base_bin {
$cm->id,
\backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO,
\backup::MODE_GENERAL,
\backup::MODE_AUTOMATED,
$user->id
);
$controller->execute_plan();
// We don't need the forced setting anymore, hence unsetting it.
// TODO: Remove this as part of MDL-65228.
unset($CFG->forced_plugin_settings['backup']);
// Grab the result.
$result = $controller->get_results();
if (!isset($result['backup_destination'])) {
@@ -224,7 +238,7 @@ class course_bin extends base_bin {
$tempdir,
$this->_courseid,
\backup::INTERACTIVE_NO,
\backup::MODE_GENERAL,
\backup::MODE_AUTOMATED,
$user->id,
\backup::TARGET_EXISTING_ADDING
);
@@ -176,4 +176,116 @@ class tool_recyclebin_category_bin_tests extends advanced_testcase {
$course = reset($courses);
$this->assertEquals('Test course 2', $course->fullname);
}
/**
* Provider for test_course_restore_with_userdata() and test_course_restore_without_userdata()
*
* Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for
* direct usage with set_config()
*/
public function recycle_bin_settings_provider() {
return [
'backup/backup_auto_storage moodle' => [[
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0],
]],
'backup/backup_auto_storage external' => [[
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1],
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
]],
'backup/backup_auto_storage mixed' => [[
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
]],
];
}
/**
* Tests that user data is restored when course is restored.
*
* @dataProvider recycle_bin_settings_provider
* @param array $settings array of plugin, name, value stdClass().
*/
public function test_course_restore_with_userdata($settings) {
global $DB;
// Force configuration changes from provider.
foreach ($settings as $setting) {
// Need to create a directory for backup_auto_destination.
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
$setting->value = make_request_directory();
}
set_config($setting->name, $setting->value, $setting->plugin);
}
// We want user data to be included for this test.
set_config('backup_auto_users', true, 'backup');
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
// Delete course.
delete_course($this->course, false);
$this->assertFalse($DB->record_exists('course', ['id' => $this->course->id]));
// Verify there is now a backup @ cat recycle bin file area.
$recyclebin = new \tool_recyclebin\category_bin($this->course->category);
$this->assertEquals(1, count($recyclebin->get_items()));
// Restore the recycle bin item.
$recyclebin->restore_item(current($recyclebin->get_items()));
// Get the new course.
$newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST);
// Check that it was removed from the recycle bin.
$this->assertEquals(0, count($recyclebin->get_items()));
// Verify that student DOES continue enrolled.
$this->assertTrue(is_enrolled(context_course::instance($newcourse->id), $student->id));
}
/**
* Tests that user data is not restored when course is restored.
*
* @dataProvider recycle_bin_settings_provider
* @param array $settings array of plugin, name, value stdClass().
*/
public function test_course_restore_without_userdata($settings) {
global $DB;
// Force configuration changes from provider.
foreach ($settings as $setting) {
// Need to create a directory for backup_auto_destination.
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
$setting->value = make_request_directory();
}
set_config($setting->name, $setting->value, $setting->plugin);
}
// We want user data to be included for this test.
set_config('backup_auto_users', false, 'backup');
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
// Delete course.
delete_course($this->course, false);
$this->assertFalse($DB->record_exists('course', ['id' => $this->course->id]));
// Verify there is now a backup @ cat recycle bin file area.
$recyclebin = new \tool_recyclebin\category_bin($this->course->category);
$this->assertEquals(1, count($recyclebin->get_items()));
// Restore the recycle bin item.
$recyclebin->restore_item(current($recyclebin->get_items()));
// Get the new course.
$newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST);
// Check that it was removed from the recycle bin.
$this->assertEquals(0, count($recyclebin->get_items()));
// Verify that student DOES NOT continue enrolled.
$this->assertFalse(is_enrolled(context_course::instance($newcourse->id), $student->id));
}
}
+144 -1
View File
@@ -55,7 +55,7 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
$this->course = $this->getDataGenerator()->create_course();
$this->quiz = $this->getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(array(
'course' => $this->course->id
'course' => $this->course->id, 'grade' => 100.0, 'sumgrades' => 1
));
}
@@ -173,4 +173,147 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
$deletedbook = reset($items);
$this->assertEquals($book->name, $deletedbook->name);
}
/**
* Provider for test_coursemodule_restore_with_userdata() and test_coursemodule_restore_without_userdata()
*
* Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for
* direct usage with set_config()
*/
public function recycle_bin_settings_provider() {
return [
'backup/backup_auto_storage moodle' => [[
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0],
]],
'backup/backup_auto_storage external' => [[
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1],
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
]],
'backup/backup_auto_storage mixed' => [[
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
]],
];
}
/**
* Tests that user data is restored when module is restored.
*
* @dataProvider recycle_bin_settings_provider
* @param array $settings array of plugin, name, value stdClass().
*/
public function test_coursemodule_restore_with_userdata($settings) {
// Force configuration changes from provider.
foreach ($settings as $setting) {
// Need to create a directory for backup_auto_destination.
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
$setting->value = make_request_directory();
}
set_config($setting->name, $setting->value, $setting->plugin);
}
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
$this->setUser($student);
set_config('backup_auto_users', true, 'backup');
$this->create_quiz_attempt($this->quiz, $student);
// Delete quiz.
$cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
course_delete_module($cm->id);
phpunit_util::run_all_adhoc_tasks();
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
$this->assertEquals(0, count($quizzes));
// Restore quiz.
$recyclebin = new \tool_recyclebin\course_bin($this->course->id);
foreach ($recyclebin->get_items() as $item) {
$recyclebin->restore_item($item);
}
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
$this->assertEquals(1, count($quizzes));
$cm = array_pop($quizzes);
// Check if user quiz attempt data is restored.
$attempts = quiz_get_user_attempts($cm->instance, $student->id);
$this->assertEquals(1, count($attempts));
$attempt = array_pop($attempts);
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertEquals($student->id, $attemptobj->get_userid());
$this->assertEquals(true, $attemptobj->is_finished());
}
/**
* Tests that user data is not restored when module is restored.
*
* @dataProvider recycle_bin_settings_provider
* @param array $settings array of plugin, name, value stdClass().
*/
public function test_coursemodule_restore_without_userdata($settings) {
// Force configuration changes from provider.
foreach ($settings as $setting) {
// Need to create a directory for backup_auto_destination.
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
$setting->value = make_request_directory();
}
set_config($setting->name, $setting->value, $setting->plugin);
}
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
$this->setUser($student);
set_config('backup_auto_users', false, 'backup');
$this->create_quiz_attempt($this->quiz, $student);
// Delete quiz.
$cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
course_delete_module($cm->id);
phpunit_util::run_all_adhoc_tasks();
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
$this->assertEquals(0, count($quizzes));
// Restore quiz.
$recyclebin = new \tool_recyclebin\course_bin($this->course->id);
foreach ($recyclebin->get_items() as $item) {
$recyclebin->restore_item($item);
}
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
$this->assertEquals(1, count($quizzes));
$cm = array_pop($quizzes);
// Check if user quiz attempt data is restored.
$attempts = quiz_get_user_attempts($cm->instance, $student->id);
$this->assertEquals(0, count($attempts));
}
/**
* Add a question to quiz and create a quiz attempt.
* @param \stdClass $quiz Quiz
* @param \stdClass $student User
* @throws coding_exception
* @throws moodle_exception
*/
private function create_quiz_attempt($quiz, $student) {
// Add Question.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
quiz_add_quiz_question($numq->id, $quiz);
// Create quiz attempt.
$quizobj = quiz::create($quiz->id, $student->id);
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$timenow = time();
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
quiz_attempt_save_started($quizobj, $quba, $attempt);
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = array(1 => array('answer' => '0'));
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_finish($timenow, false);
}
}
+1
View File
@@ -183,6 +183,7 @@ function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $pr
$processed = array();
foreach ($columns as $key=>$unused) {
$field = $columns[$key];
$field = trim($field);
$lcfield = core_text::strtolower($field);
if (in_array($field, $stdfields) or in_array($lcfield, $stdfields)) {
// standard fields are only lowercase
+7 -2
View File
@@ -796,8 +796,7 @@ class manager {
// the format filename => version. The version value needs to
// be increased if the tour has been updated.
$shippedtours = [
'36_dashboard.json' => 3,
'36_messaging.json' => 3,
'36_dashboard.json' => 3
];
// These are tours that we used to ship but don't ship any longer.
@@ -807,6 +806,12 @@ class manager {
'boost_course_view.json' => 1,
];
if ($CFG->messaging) {
$shippedtours['36_messaging.json'] = 3;
} else {
$unshippedtours['36_messaging.json'] = 3;
}
$existingtourrecords = $DB->get_recordset('tool_usertours_tours');
// Get all of the existing shipped tours and check if they need to be
+7
View File
@@ -57,5 +57,12 @@ function xmldb_tool_usertours_upgrade($oldversion) {
// Automatically generated Moodle v3.6.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2018120301) {
// Update the tours shipped with Moodle.
manager::update_shipped_tours();
upgrade_plugin_savepoint(true, 2018120301, 'tool', 'usertours');
}
return true;
}
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2018120300; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2018120301; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2018112800; // Requires this Moodle version.
$plugin->component = 'tool_usertours'; // Full name of the plugin (used for diagnostics).
+34 -19
View File
@@ -577,9 +577,14 @@ abstract class base {
return $result;
}
// Remove samples the target consider invalid.
$this->analysabletarget->add_sample_data($samplesdata);
$this->analysabletarget->filter_out_invalid_samples($sampleids, $analysable, $includetarget);
try {
// Remove samples the target consider invalid.
$this->analysabletarget->add_sample_data($samplesdata);
$this->analysabletarget->filter_out_invalid_samples($sampleids, $analysable, $includetarget);
} catch (\Throwable $e) {
$dataset->close_process();
throw $e;
}
if (!$sampleids) {
$result->status = \core_analytics\model::NO_DATASET;
@@ -588,19 +593,24 @@ abstract class base {
return $result;
}
foreach ($this->indicators as $key => $indicator) {
// The analyser attaches the main entities the sample depends on and are provided to the
// indicator to calculate the sample.
$this->indicators[$key]->add_sample_data($samplesdata);
}
try {
foreach ($this->indicators as $key => $indicator) {
// The analyser attaches the main entities the sample depends on and are provided to the
// indicator to calculate the sample.
$this->indicators[$key]->add_sample_data($samplesdata);
}
// Here we start the memory intensive process that will last until $data var is
// unset (until the method is finished basically).
if ($includetarget) {
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges,
$this->analysabletarget);
} else {
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges);
// Here we start the memory intensive process that will last until $data var is
// unset (until the method is finished basically).
if ($includetarget) {
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges,
$this->analysabletarget);
} else {
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges);
}
} catch (\Throwable $e) {
$dataset->close_process();
throw $e;
}
if (!$data) {
@@ -610,11 +620,16 @@ abstract class base {
return $result;
}
// Add extra metadata.
$this->add_model_metadata($data);
try {
// Add extra metadata.
$this->add_model_metadata($data);
// Write all calculated data to a file.
$file = $dataset->store($data);
// Write all calculated data to a file.
$file = $dataset->store($data);
} catch (\Throwable $e) {
$dataset->close_process();
throw $e;
}
// Flag the model + analysable + timesplitting as analysed.
$dataset->close_process();
+1 -1
View File
@@ -196,7 +196,7 @@ abstract class base extends \core_analytics\calculable {
$message->component = 'moodle';
$message->name = 'insights';
$message->userfrom = get_admin();
$message->userfrom = \core_user::get_noreply_user();
$message->userto = $user;
$insighturl = new \moodle_url('/report/insights/insights.php?modelid=' . $modelid . '&contextid=' . $context->id);
+4 -1
View File
@@ -1524,7 +1524,10 @@ class model {
// 1 db read per context.
$this->purge_insights_cache();
$this->model->trained = 0;
if (!$this->is_static()) {
$this->model->trained = 0;
}
$this->model->timemodified = time();
$this->model->usermodified = $USER->id;
$DB->update_record('analytics_models', $this->model);
+24
View File
@@ -168,10 +168,34 @@ class analytics_model_testcase extends advanced_testcase {
$this->assertEmpty($DB->count_records('analytics_predict_samples'));
$this->assertEmpty($DB->count_records('analytics_used_files'));
// Check that the model is marked as not trained after clearing (as it is not a static one).
$this->assertEquals(0, $DB->get_field('analytics_models', 'trained', array('id' => $this->modelobj->id)));
set_config('enabled_stores', '', 'tool_log');
get_log_manager(true);
}
/**
* Test behaviour of {\core_analytics\model::clear()} for static models.
*/
public function test_clear_static() {
global $DB;
$this->resetAfterTest();
$statictarget = new test_static_target_shortname();
$indicators['test_indicator_max'] = \core_analytics\manager::get_indicator('test_indicator_max');
$model = \core_analytics\model::create($statictarget, $indicators, '\core\analytics\time_splitting\quarters');
$modelobj = $model->get_model_obj();
// Static models are always considered trained.
$this->assertEquals(1, $DB->get_field('analytics_models', 'trained', array('id' => $modelobj->id)));
$model->clear();
// Check that the model is still marked as trained even after clearing.
$this->assertEquals(1, $DB->get_field('analytics_models', 'trained', array('id' => $modelobj->id)));
}
public function test_model_manager() {
$this->resetAfterTest(true);
+7
View File
@@ -99,6 +99,13 @@ class auth_db_testcase extends advanced_testcase {
case 'mssql':
set_config('type', 'mssqlnative', 'auth_db');
set_config('sybasequoting', '1', 'auth_db');
// The native sqlsrv driver uses a comma as separator between host and port.
$dbhost = $CFG->dbhost;
if (!empty($dboptions['dbport'])) {
$dbhost .= ',' . $dboptions['dbport'];
}
set_config('host', $dbhost, 'auth_db');
break;
default:
+33
View File
@@ -63,3 +63,36 @@ Feature: User must accept policy when logging in and signing up
And I log in as "user1"
And I open my profile in edit mode
And the field "First name" matches value "User1"
Scenario Outline: Email validation during email registration
Given the following config values are set as admin:
| allowaccountssameemail | <allowsameemail> |
| registerauth | email |
| passwordpolicy | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| s1 | John | Doe | s1@example.com |
And I am on site homepage
And I follow "Log in"
When I press "Create new account"
And I set the following fields to these values:
| Username | s2 |
| Password | test |
| Email address | <email1> |
| Email (again) | <email2> |
| First name | Jane |
| Surname | Doe |
And I press "Create my new account"
Then I should <expect> "This email address is already registered. Perhaps you created an account in the past?"
And I should <expect2> "Invalid email address"
Examples:
| allowsameemail | email1 | email2 | expect | expect2 |
| 0 | s1@example.com | s1@example.com | see | not see |
| 0 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | see | not see |
| 0 | s1@example.com | S1@EXAMPLE.COM | see | not see |
| 0 | s2@example.com | s1@example.com | not see | see |
| 1 | s1@example.com | s1@example.com | not see | not see |
| 1 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | not see | not see |
| 1 | s1@example.com | S1@EXAMPLE.COM | not see | not see |
| 1 | s1@example.com | s2@example.com | not see | see |
+1 -5
View File
@@ -36,11 +36,7 @@ $string['auth_ldap_contexts_key'] = 'Contexts';
$string['auth_ldap_create_context'] = 'If you enable user creation with email confirmation, specify the context where users are created. This context should be different from other users to prevent security issues. You don\'t need to add this context to ldap_context-variable, Moodle will search for users from this context automatically.<br /><b>Note!</b> You have to modify the method user_create() in file auth/ldap/auth.php to make user creation work';
$string['auth_ldap_create_context_key'] = 'Context for new users';
$string['auth_ldap_create_error'] = 'Error creating user in LDAP.';
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server.
If the given username and password are valid, Moodle creates a new user
entry in its database. This module can read user attributes from LDAP and prefill
wanted fields in Moodle. For following logins only the username and
password are checked.';
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server. If the given username and password are valid, Moodle creates a new user entry in its database. This plugin can read user attributes from LDAP and prefill wanted fields in Moodle. For following logins only the username and password are checked.';
$string['auth_ldap_expiration_desc'] = 'Select \'{$a->no}\' to disable expired password checking or \'{$a->ldapserver}\' to read the password expiry time directly from the LDAP server.';
$string['auth_ldap_expiration_key'] = 'Expiry';
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiry warning is issued.';
@@ -311,11 +311,11 @@ class backup_controller extends base_controller {
// Basic/initial prevention against time/memory limits
core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
raise_memory_limit(MEMORY_EXTRA);
// If this is not a course backup, inform the plan we are not
// If this is not a course backup, or single activity backup (e.g. duplicate) inform the plan we are not
// including all the activities for sure. This will affect any
// task/step executed conditionally to stop including information
// for section and activity backup. MDL-28180.
if ($this->get_type() !== backup::TYPE_1COURSE) {
if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) {
$this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
$this->plan->set_excluding_activities();
}
@@ -326,11 +326,11 @@ class restore_controller extends base_controller {
// Basic/initial prevention against time/memory limits
core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
raise_memory_limit(MEMORY_EXTRA);
// If this is not a course restore, inform the plan we are not
// If this is not a course restore or single activity restore (e.g. duplicate), inform the plan we are not
// including all the activities for sure. This will affect any
// task/step executed conditionally to stop processing information
// for section and activity restore. MDL-28180.
if ($this->get_type() !== backup::TYPE_1COURSE) {
if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) {
$this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
$this->plan->set_excluding_activities();
}
+32 -2
View File
@@ -794,12 +794,25 @@ class backup_badges_structure_step extends backup_structure_step {
$badge = new backup_nested_element('badge', array('id'), array('name', 'description',
'timecreated', 'timemodified', 'usercreated', 'usermodified', 'issuername',
'issuerurl', 'issuercontact', 'expiredate', 'expireperiod', 'type', 'courseid',
'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron'));
'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron',
'version', 'language', 'imageauthorname', 'imageauthoremail', 'imageauthorurl',
'imagecaption'));
$criteria = new backup_nested_element('criteria');
$criterion = new backup_nested_element('criterion', array('id'), array('badgeid',
'criteriatype', 'method', 'description', 'descriptionformat'));
$endorsement = new backup_nested_element('endorsement', array('id'), array('badgeid',
'issuername', 'issuerurl', 'issueremail', 'claimid', 'claimcomment', 'dateissued'));
$alignments = new backup_nested_element('alignments');
$alignment = new backup_nested_element('alignment', array('id'), array('badgeid',
'targetname', 'targeturl', 'targetdescription', 'targetframework', 'targetcode'));
$relatedbadges = new backup_nested_element('relatedbadges');
$relatedbadge = new backup_nested_element('relatedbadge', array('id'), array('badgeid',
'relatedbadgeid'));
$parameters = new backup_nested_element('parameters');
$parameter = new backup_nested_element('parameter', array('id'), array('critid',
'name', 'value', 'criteriatype'));
@@ -815,6 +828,11 @@ class backup_badges_structure_step extends backup_structure_step {
$criteria->add_child($criterion);
$criterion->add_child($parameters);
$parameters->add_child($parameter);
$badge->add_child($endorsement);
$badge->add_child($alignments);
$alignments->add_child($alignment);
$badge->add_child($relatedbadges);
$relatedbadges->add_child($relatedbadge);
$badge->add_child($manual_awards);
$manual_awards->add_child($manual_award);
@@ -822,6 +840,10 @@ class backup_badges_structure_step extends backup_structure_step {
$badge->set_source_table('badge', array('courseid' => backup::VAR_COURSEID));
$criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID));
$endorsement->set_source_table('badge_endorsement', array('badgeid' => backup::VAR_PARENTID));
$alignment->set_source_table('badge_competencies', array('badgeid' => backup::VAR_PARENTID));
$relatedbadge->set_source_table('badge_related', array('badgeid' => backup::VAR_PARENTID));
$parametersql = 'SELECT cp.*, c.criteriatype
FROM {badge_criteria_param} cp JOIN {badge_criteria} c
@@ -838,6 +860,10 @@ class backup_badges_structure_step extends backup_structure_step {
$badge->annotate_ids('user', 'usermodified');
$criterion->annotate_ids('badge', 'badgeid');
$parameter->annotate_ids('criterion', 'critid');
$endorsement->annotate_ids('badge', 'badgeid');
$alignment->annotate_ids('badge', 'badgeid');
$relatedbadge->annotate_ids('badge', 'badgeid');
$relatedbadge->annotate_ids('badge', 'relatedbadgeid');
$badge->annotate_files('badges', 'badgeimage', 'id');
$manual_award->annotate_ids('badge', 'badgeid');
$manual_award->annotate_ids('user', 'recipientid');
@@ -2270,7 +2296,11 @@ class backup_questions_structure_step extends backup_structure_step {
FROM {tag} t
JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.itemid = ?
AND ti.itemtype = 'question'", array(backup::VAR_PARENTID));
AND ti.itemtype = 'question'
AND ti.component = 'core_question'",
[
backup::VAR_PARENTID
]);
// don't need to annotate ids nor files
// (already done by {@link backup_annotate_all_question_files}
+109 -1
View File
@@ -2506,6 +2506,9 @@ class restore_badges_structure_step extends restore_structure_step {
$paths[] = new restore_path_element('badge', '/badges/badge');
$paths[] = new restore_path_element('criterion', '/badges/badge/criteria/criterion');
$paths[] = new restore_path_element('parameter', '/badges/badge/criteria/criterion/parameters/parameter');
$paths[] = new restore_path_element('endorsement', '/badges/badge/endorsement');
$paths[] = new restore_path_element('alignment', '/badges/badge/alignments/alignment');
$paths[] = new restore_path_element('relatedbadge', '/badges/badge/relatedbadges/relatedbadge');
$paths[] = new restore_path_element('manual_award', '/badges/badge/manual_awards/manual_award');
return $paths;
@@ -2550,13 +2553,87 @@ class restore_badges_structure_step extends restore_structure_step {
'attachment' => $data->attachment,
'notification' => $data->notification,
'status' => BADGE_STATUS_INACTIVE,
'nextcron' => $data->nextcron
'nextcron' => $data->nextcron,
'version' => $data->version,
'language' => $data->language,
'imageauthorname' => $data->imageauthorname,
'imageauthoremail' => $data->imageauthoremail,
'imageauthorurl' => $data->imageauthorurl,
'imagecaption' => $data->imagecaption
);
$newid = $DB->insert_record('badge', $params);
$this->set_mapping('badge', $data->id, $newid, $restorefiles);
}
/**
* Create an endorsement for a badge.
*
* @param mixed $data
* @return void
*/
public function process_endorsement($data) {
global $DB;
$data = (object)$data;
$params = array(
'badgeid' => $this->get_new_parentid('badge'),
'issuername' => $data->issuername,
'issuerurl' => $data->issuerurl,
'issueremail' => $data->issueremail,
'claimid' => $data->claimid,
'claimcomment' => $data->claimcomment,
'dateissued' => $this->apply_date_offset($data->dateissued)
);
$newid = $DB->insert_record('badge_endorsement', $params);
$this->set_mapping('endorsement', $data->id, $newid);
}
/**
* Link to related badges for a badge. This relies on post processing in after_execute().
*
* @param mixed $data
* @return void
*/
public function process_relatedbadge($data) {
global $DB;
$data = (object)$data;
$relatedbadgeid = $data->relatedbadgeid;
if ($relatedbadgeid) {
// Only backup and restore related badges if they are contained in the backup file.
$params = array(
'badgeid' => $this->get_new_parentid('badge'),
'relatedbadgeid' => $relatedbadgeid
);
$newid = $DB->insert_record('badge_related', $params);
}
}
/**
* Link to an alignment for a badge.
*
* @param mixed $data
* @return void
*/
public function process_alignment($data) {
global $DB;
$data = (object)$data;
$params = array(
'badgeid' => $this->get_new_parentid('badge'),
'targetname' => $data->targetname,
'targeturl' => $data->targeturl,
'targetdescription' => $data->targetdescription,
'targetframework' => $data->targetframework,
'targetcode' => $data->targetcode
);
$newid = $DB->insert_record('badge_competencies', $params);
$this->set_mapping('alignment', $data->id, $newid);
}
public function process_criterion($data) {
global $DB;
@@ -2569,6 +2646,7 @@ class restore_badges_structure_step extends restore_structure_step {
'description' => isset($data->description) ? $data->description : '',
'descriptionformat' => isset($data->descriptionformat) ? $data->descriptionformat : 0,
);
$newid = $DB->insert_record('badge_criteria', $params);
$this->set_mapping('criterion', $data->id, $newid);
}
@@ -2634,8 +2712,38 @@ class restore_badges_structure_step extends restore_structure_step {
}
protected function after_execute() {
global $DB;
// Add related files.
$this->add_related_files('badges', 'badgeimage', 'badge');
$badgeid = $this->get_new_parentid('badge');
// Remap any related badges.
// We do this in the DB directly because this is backup/restore it is not valid to call into
// the component API.
$params = array('badgeid' => $badgeid);
$query = "SELECT DISTINCT br.id, br.badgeid, br.relatedbadgeid
FROM {badge_related} br
WHERE (br.badgeid = :badgeid)";
$relatedbadges = $DB->get_records_sql($query, $params);
$newrelatedids = [];
foreach ($relatedbadges as $relatedbadge) {
$relatedid = $this->get_mappingid('badge', $relatedbadge->relatedbadgeid);
$params['relatedbadgeid'] = $relatedbadge->relatedbadgeid;
$DB->delete_records_select('badge_related', '(badgeid = :badgeid AND relatedbadgeid = :relatedbadgeid)', $params);
if ($relatedid) {
$newrelatedids[] = $relatedid;
}
}
if (!empty($newrelatedids)) {
$relatedbadges = [];
foreach ($newrelatedids as $relatedid) {
$relatedbadge = new stdClass();
$relatedbadge->badgeid = $badgeid;
$relatedbadge->relatedbadgeid = $relatedid;
$relatedbadges[] = $relatedbadge;
}
$DB->insert_records('badge_related', $relatedbadges);
}
}
}
@@ -580,6 +580,17 @@ abstract class backup_controller_dbops extends backup_dbops {
'backup_import_competencies' => 'competencies'
);
self::apply_admin_config_defaults($controller, $settings, true);
if ((!$controller->get_interactive()) &&
$controller->get_type() == backup::TYPE_1ACTIVITY) {
// This is duplicate - there is no concept of defaults - these settings must be on.
$settings = array(
'activities',
'blocks',
'filters',
'questionbank'
);
self::force_enable_settings($controller, $settings);
}
break;
case backup::MODE_AUTOMATED:
// Load the automated defaults.
@@ -607,6 +618,30 @@ abstract class backup_controller_dbops extends backup_dbops {
}
}
/**
* Turn these settings on. No defaults from admin settings.
*
* @param backup_controller $controller
* @param array $settings a map from admin config names to setting names (Config name => Setting name)
*/
private static function force_enable_settings(backup_controller $controller, array $settings) {
$plan = $controller->get_plan();
foreach ($settings as $config => $settingname) {
$value = true;
if ($plan->setting_exists($settingname)) {
$setting = $plan->get_setting($settingname);
// We do not allow this setting to be locked for a duplicate function.
if ($setting->get_status() !== base_setting::NOT_LOCKED) {
$setting->set_status(base_setting::NOT_LOCKED);
}
$setting->set_value($value);
$setting->set_status(base_setting::LOCKED_BY_CONFIG);
} else {
$controller->log('Unknown setting: ' . $setting, BACKUP::LOG_DEBUG);
}
}
}
/**
* Sets the controller settings default values from the admin config.
*
@@ -183,6 +183,18 @@ abstract class restore_controller_dbops extends restore_dbops {
);
self::apply_admin_config_defaults($controller, $settings, true);
}
if ($controller->get_mode() == backup::MODE_IMPORT &&
(!$controller->get_interactive()) &&
$controller->get_type() == backup::TYPE_1ACTIVITY) {
// This is duplicate - there is no concept of defaults - these settings must be on.
$settings = array(
'activities',
'blocks',
'filters',
'questionbank'
);
self::force_enable_settings($controller, $settings);
};
// Add some dependencies.
$plan = $controller->get_plan();
@@ -233,6 +245,30 @@ abstract class restore_controller_dbops extends restore_dbops {
return $value;
}
/**
* Turn these settings on. No defaults from admin settings.
*
* @param restore_controller $controller
* @param array $settings a map from admin config names to setting names (Config name => Setting name)
*/
private static function force_enable_settings(restore_controller $controller, array $settings) {
$plan = $controller->get_plan();
foreach ($settings as $config => $settingname) {
$value = true;
if ($plan->setting_exists($settingname)) {
$setting = $plan->get_setting($settingname);
// We do not allow this setting to be locked for a duplicate function.
if ($setting->get_status() !== base_setting::NOT_LOCKED) {
$setting->set_status(base_setting::NOT_LOCKED);
}
$setting->set_value($value);
$setting->set_status(base_setting::LOCKED_BY_CONFIG);
} else {
$controller->log('Unknown setting: ' . $settingname, BACKUP::LOG_DEBUG);
}
}
}
/**
* Sets the controller settings default values from the admin config.
*
@@ -238,12 +238,12 @@ class restore_log_rule implements processable {
protected function build_regexp($expression, $tokens) {
// Replace to temp (and preg_quote() safe) placeholders
foreach ($tokens as $token) {
$expression = preg_replace('~' . preg_quote($token, '~') . '~', '#@@#@@#', $expression, 1);
$expression = preg_replace('~' . preg_quote($token, '~') . '~', '%@@%@@%', $expression, 1);
}
// quote the expression
$expression = preg_quote($expression, '~');
// Replace all the placeholders
$expression = preg_replace('~#@@#@@#~', '(.*)', $expression);
$expression = preg_replace('~%@@%@@%~', '(.*)', $expression);
return '~' . $expression . '~';
}
}
@@ -49,4 +49,20 @@ class backup_restore_log_rule_testcase extends basic_testcase {
// But the original log has been kept unmodified by the process() call.
$this->assertEquals($originallog, $log);
}
public function test_build_regexp() {
$original = 'Any (string) with [placeholders] like {this} and {this}. [end].';
$expectation = '~Any \(string\) with (.*) like (.*) and (.*)\. (.*)\.~';
$lr = new restore_log_rule('this', 'doesnt', 'matter', 'here');
$class = new ReflectionClass('restore_log_rule');
$method = $class->getMethod('extract_tokens');
$method->setAccessible(true);
$tokens = $method->invoke($lr, $original);
$method = $class->getMethod('build_regexp');
$method->setAccessible(true);
$this->assertSame($expectation, $method->invoke($lr, $original, $tokens));
}
}
+141 -259
View File
@@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -82,7 +81,7 @@ abstract class setting_dependency {
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
public function destroy() {
// No need to destroy anything recursively here, direct reset
// No need to destroy anything recursively here, direct reset.
$this->setting = null;
$this->dependentsetting = null;
}
@@ -94,16 +93,19 @@ abstract class setting_dependency {
* @return bool
*/
final public function process_change($changetype, $oldvalue) {
// Check the type of change requested
// Check the type of change requested.
switch ($changetype) {
// Process a status change
case base_setting::CHANGED_STATUS: return $this->process_status_change($oldvalue);
// Process a visibility change
case base_setting::CHANGED_VISIBILITY: return $this->process_visibility_change($oldvalue);
// Process a value change
case base_setting::CHANGED_VALUE: return $this->process_value_change($oldvalue);
// Process a status change.
case base_setting::CHANGED_STATUS:
return $this->process_status_change($oldvalue);
// Process a visibility change.
case base_setting::CHANGED_VISIBILITY:
return $this->process_visibility_change($oldvalue);
// Process a value change.
case base_setting::CHANGED_VALUE:
return $this->process_value_change($oldvalue);
}
// Throw an exception if we get this far
// Throw an exception if we get this far.
throw new backup_ui_exception('unknownchangetype');
}
/**
@@ -112,11 +114,11 @@ abstract class setting_dependency {
* @return bool
*/
protected function process_visibility_change($oldvisibility) {
// Store the current dependent settings visibility for comparison
// Store the current dependent settings visibility for comparison.
$prevalue = $this->dependentsetting->get_visibility();
// Set it regardless of whether we need to
// Set it regardless of whether we need to.
$this->dependentsetting->set_visibility($this->setting->get_visibility());
// Return true if it changed
// Return true if it changed.
return ($prevalue != $this->dependentsetting->get_visibility());
}
/**
@@ -182,15 +184,16 @@ class setting_dependency_disabledif_equals extends setting_dependency {
*/
public function __construct(base_setting $setting, base_setting $dependentsetting, $value, $defaultvalue = false) {
parent::__construct($setting, $dependentsetting, $defaultvalue);
$this->value = ($value)?(string)$value:0;
$this->value = ($value) ? (string)$value : 0;
}
/**
* Returns true if the dependent setting is locked by this setting_dependency.
* @return bool
*/
public function is_locked() {
// If the setting is locked or the dependent setting should be locked then return true
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || $this->setting->get_value() == $this->value) {
// If the setting is locked or the dependent setting should be locked then return true.
if ($this->setting->get_status() !== base_setting::NOT_LOCKED ||
$this->evaluate_disabled_condition($this->setting->get_value())) {
return true;
}
// Else the dependent setting is not locked by this setting_dependency.
@@ -208,17 +211,25 @@ class setting_dependency_disabledif_equals extends setting_dependency {
return false;
}
$prevalue = $this->dependentsetting->get_value();
// If the setting is the desired value enact the dependency
if ($this->setting->get_value() == $this->value) {
// If the setting is the desired value enact the dependency.
$settingvalue = $this->setting->get_value();
if ($this->evaluate_disabled_condition($settingvalue)) {
// The dependent setting needs to be locked by hierachy and set to the
// default value.
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
$this->dependentsetting->set_value($this->defaultvalue);
// For checkboxes the default value is false, but when the setting is
// locked, the value should inherit from the parent setting.
if ($this->defaultvalue === false) {
$this->dependentsetting->set_value($settingvalue);
} else {
$this->dependentsetting->set_value($this->defaultvalue);
}
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
// We can unlock the dependent setting
// We can unlock the dependent setting.
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
}
// Return true if the value has changed for the dependent setting
// Return true if the value has changed for the dependent setting.
return ($prevalue != $this->dependentsetting->get_value());
}
/**
@@ -227,17 +238,18 @@ class setting_dependency_disabledif_equals extends setting_dependency {
* @return bool
*/
protected function process_status_change($oldstatus) {
// Store the dependent status
// Store the dependent status.
$prevalue = $this->dependentsetting->get_status();
// Store the current status
// Store the current status.
$currentstatus = $this->setting->get_status();
if ($currentstatus == base_setting::NOT_LOCKED) {
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY && $this->setting->get_value() != $this->value) {
// Dependency has changes, is not fine, unlock the dependent setting
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY &&
!$this->evaluate_disabled_condition($this->setting->get_value())) {
// Dependency has changes, is not fine, unlock the dependent setting.
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
}
} else {
// Make sure the dependent setting is also locked, in this case by hierarchy
// Make sure the dependent setting is also locked, in this case by hierarchy.
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
}
// Return true if the dependent setting has changed.
@@ -248,17 +260,17 @@ class setting_dependency_disabledif_equals extends setting_dependency {
* @return bool True if there were changes
*/
public function enforce() {
// This will be set to true if ANYTHING changes
// This will be set to true if ANYTHING changes.
$changes = false;
// First process any value changes
// First process any value changes.
if ($this->process_value_change($this->setting->get_value())) {
$changes = true;
}
// Second process any status changes
// Second process any status changes.
if ($this->process_status_change($this->setting->get_status())) {
$changes = true;
}
// Finally process visibility changes
// Finally process visibility changes.
if ($this->process_visibility_change($this->setting->get_visibility())) {
$changes = true;
}
@@ -271,152 +283,72 @@ class setting_dependency_disabledif_equals extends setting_dependency {
*/
public function get_moodleform_properties() {
return array(
'setting'=>$this->dependentsetting->get_ui_name(),
'dependenton'=>$this->setting->get_ui_name(),
'condition'=>'eq',
'value'=>$this->value
'setting' => $this->dependentsetting->get_ui_name(),
'dependenton' => $this->setting->get_ui_name(),
'condition' => 'eq',
'value' => $this->value
);
}
/**
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
* This function should be abstract, but there will probably be existing sub-classes so we must provide a default
* implementation.
* @param mixed $value The value of the parent setting.
* @return bool
*/
protected function evaluate_disabled_condition($value) {
return $value == $this->value;
}
}
/**
* A dependency that disables the secondary setting if the primary setting is
* not equal to the provided value
*
* @copyright 2011 Darko Miletic <dmiletic@moodlerooms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class setting_dependency_disabledif_not_equals extends setting_dependency_disabledif_equals {
/**
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
* @param mixed $value The value of the parent setting.
* @return bool
*/
protected function evaluate_disabled_condition($value) {
return $value != $this->value;
}
/**
* Returns an array of properties suitable to be used to define a moodleforms
* disabled command
* @return array
*/
public function get_moodleform_properties() {
return array(
'setting' => $this->dependentsetting->get_ui_name(),
'dependenton' => $this->setting->get_ui_name(),
'condition' => 'notequal',
'value' => $this->value
);
}
}
/**
* A dependency that disables the secondary setting if the primary setting is
* not equal to the provided value
*
* @copyright 2011 Darko Miletic <dmiletic@moodlerooms.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class setting_dependency_disabledif_not_equals extends setting_dependency_disabledif_equals {
/**
* Enforces the dependency if required.
* @return bool True if there were changes
*/
public function enforce() {
// This will be set to true if ANYTHING changes
$changes = false;
// First process any value changes
if (!$this->process_value_change($this->setting->get_value())) {
$changes = true;
}
// Second process any status changes
if ($this->process_status_change($this->setting->get_status())) {
$changes = true;
}
// Finally process visibility changes
if ($this->process_visibility_change($this->setting->get_visibility())) {
$changes = true;
}
return $changes;
}
/**
* Returns an array of properties suitable to be used to define a moodleforms
* disabled command
* @return array
*/
public function get_moodleform_properties() {
return array(
'setting'=>$this->dependentsetting->get_ui_name(),
'dependenton'=>$this->setting->get_ui_name(),
'condition'=>'notequal',
'value'=>$this->value
);
}
}
* Disable if a value is in a list.
*/
class setting_dependency_disabledif_in_array extends setting_dependency_disabledif_equals {
//with array
class setting_dependency_disabledif_equals2 extends setting_dependency {
/**
* The value to compare to
* @var mixed
*/
protected $value;
/**
* Creates the dependency
*
* @param base_setting $setting
* @param base_setting $dependentsetting
* @param mixed $value
* @param mixed $defaultvalue
*/
public function __construct(base_setting $setting, base_setting $dependentsetting, array $value, $defaultvalue = false) {
parent::__construct($setting, $dependentsetting, $defaultvalue);
$this->value = $value;
}
/**
* Returns true if the dependent setting is locked by this setting_dependency.
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
* @param mixed $value The value of the parent setting.
* @return bool
*/
public function is_locked() {
// If the setting is locked or the dependent setting should be locked then return true
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || in_array($this->setting->get_value(), $this->value)) {
return true;
}
// Else the dependent setting is not locked by this setting_dependency.
return false;
}
/**
* Processes a value change in the primary setting
* @param mixed $oldvalue
* @return bool
*/
protected function process_value_change($oldvalue) {
$prevalue = $this->dependentsetting->get_value();
// If the setting is the desired value enact the dependency
if (in_array($this->setting->get_value(), $this->value)) {
// The dependent setting needs to be locked by hierachy and set to the
// default value.
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
$this->dependentsetting->set_value($this->defaultvalue);
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
// We can unlock the dependent setting
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
}
// Return true if the value has changed for the dependent setting
return ($prevalue != $this->dependentsetting->get_value());
}
/**
* Processes a status change in the primary setting
* @param mixed $oldstatus
* @return bool
*/
protected function process_status_change($oldstatus) {
// Store the dependent status
$prevalue = $this->dependentsetting->get_status();
// Store the current status
$currentstatus = $this->setting->get_status();
if ($currentstatus == base_setting::NOT_LOCKED) {
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY && !in_array($this->setting->get_value(), $this->value)) {
// Dependency has changes, is not fine, unlock the dependent setting
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
}
} else {
// Make sure the dependent setting is also locked, in this case by hierarchy
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
}
// Return true if the dependent setting has changed.
return ($prevalue != $this->dependentsetting->get_status());
}
/**
* Enforces the dependency if required.
* @return bool True if there were changes
*/
public function enforce() {
// This will be set to true if ANYTHING changes
$changes = false;
// First process any value changes
if ($this->process_value_change($this->setting->get_value())) {
$changes = true;
}
// Second process any status changes
if ($this->process_status_change($this->setting->get_status())) {
$changes = true;
}
// Finally process visibility changes
if ($this->process_visibility_change($this->setting->get_visibility())) {
$changes = true;
}
return $changes;
protected function evaluate_disabled_condition($value) {
return in_array($value, $this->value);
}
/**
* Returns an array of properties suitable to be used to define a moodleforms
* disabled command
@@ -424,14 +356,19 @@ class setting_dependency_disabledif_equals2 extends setting_dependency {
*/
public function get_moodleform_properties() {
return array(
'setting'=>$this->dependentsetting->get_ui_name(),
'dependenton'=>$this->setting->get_ui_name(),
'condition'=>'eq',
'value'=>$this->value
'setting' => $this->dependentsetting->get_ui_name(),
'dependenton' => $this->setting->get_ui_name(),
'condition' => 'eq',
'value' => $this->value
);
}
}
/**
* This class is here for backwards compatibility (terrible name).
*/
class setting_dependency_disabledif_equals2 extends setting_dependency_disabledif_in_array {
}
/**
* A dependency that disables the secondary element if the primary element is
@@ -452,9 +389,9 @@ class setting_dependency_disabledif_checked extends setting_dependency_disabledi
*/
public function get_moodleform_properties() {
return array(
'setting'=>$this->dependentsetting->get_ui_name(),
'dependenton'=>$this->setting->get_ui_name(),
'condition'=>'checked'
'setting' => $this->dependentsetting->get_ui_name(),
'dependenton' => $this->setting->get_ui_name(),
'condition' => 'checked'
);
}
}
@@ -478,9 +415,9 @@ class setting_dependency_disabledif_not_checked extends setting_dependency_disab
*/
public function get_moodleform_properties() {
return array(
'setting'=>$this->dependentsetting->get_ui_name(),
'dependenton'=>$this->setting->get_ui_name(),
'condition'=>'notchecked'
'setting' => $this->dependentsetting->get_ui_name(),
'dependenton' => $this->setting->get_ui_name(),
'condition' => 'notchecked'
);
}
}
@@ -497,6 +434,16 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
parent::__construct($setting, $dependentsetting, false, $defaultvalue);
$this->value = false;
}
/**
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
* @param mixed $value The value of the parent setting.
* @return bool
*/
protected function evaluate_disabled_condition($value) {
return !empty($value);
}
/**
* Returns an array of properties suitable to be used to define a moodleforms
* disabled command
@@ -504,50 +451,12 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
*/
public function get_moodleform_properties() {
return array(
'setting'=>$this->dependentsetting->get_ui_name(),
'dependenton'=>$this->setting->get_ui_name(),
'condition'=>'notequal',
'value'=>''
'setting' => $this->dependentsetting->get_ui_name(),
'dependenton' => $this->setting->get_ui_name(),
'condition' => 'notequal',
'value' => ''
);
}
/**
* Processes a value change in the primary setting
* @param mixed $oldvalue
* @return bool
*/
protected function process_value_change($oldvalue) {
$prevalue = $this->dependentsetting->get_value();
// If the setting is the desired value enact the dependency
$value = $this->setting->get_value();
if (!empty($value)) {
// The dependent setting needs to be locked by hierachy and set to the
// default value.
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
if ($this->defaultvalue === false) {
$this->dependentsetting->set_value($value);
} else {
$this->dependentsetting->set_value($this->defaultvalue);
}
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
// We can unlock the dependent setting
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
}
// Return true if the value has changed for the dependent setting
return ($prevalue != $this->dependentsetting->get_value());
}
/**
* Returns true if the dependent setting is locked by this setting_dependency.
* @return bool
*/
public function is_locked() {
// If the setting is locked or the dependent setting should be locked then return true
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || !empty($value)) {
return true;
}
// Else the dependent setting is not locked by this setting_dependency.
return false;
}
}
/**
@@ -562,6 +471,16 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
parent::__construct($setting, $dependentsetting, false, $defaultvalue);
$this->value = false;
}
/**
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
* @param mixed $value The value of the parent setting.
* @return bool
*/
protected function evaluate_disabled_condition($value) {
return empty($value);
}
/**
* Returns an array of properties suitable to be used to define a moodleforms
* disabled command
@@ -569,47 +488,10 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
*/
public function get_moodleform_properties() {
return array(
'setting'=>$this->dependentsetting->get_ui_name(),
'dependenton'=>$this->setting->get_ui_name(),
'condition'=>'notequal',
'value'=>''
'setting' => $this->dependentsetting->get_ui_name(),
'dependenton' => $this->setting->get_ui_name(),
'condition' => 'notequal',
'value' => ''
);
}
/**
* Processes a value change in the primary setting
* @param mixed $oldvalue
* @return bool
*/
protected function process_value_change($oldvalue) {
$prevalue = $this->dependentsetting->get_value();
// If the setting is the desired value enact the dependency
$value = $this->setting->get_value();
if (empty($value)) {
// The dependent setting needs to be locked by hierachy and set to the
// default value.
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
if ($this->defaultvalue === false) {
$this->dependentsetting->set_value($value);
} else {
$this->dependentsetting->set_value($this->defaultvalue);
}
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
// We can unlock the dependent setting
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
}
// Return true if the value has changed for the dependent setting
return ($prevalue != $this->dependentsetting->get_value());
}
/**
* Returns true if the dependent setting is locked by this setting_dependency.
* @return bool
*/
public function is_locked() {
// If the setting is locked or the dependent setting should be locked then return true
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || empty($value)) {
return true;
}
// Else the dependent setting is not locked by this setting_dependency.
return false;
}
}
+49 -5
View File
@@ -45,7 +45,7 @@ class backp_settings_testcase extends basic_testcase {
/**
* test base_setting class
*/
function test_base_setting() {
public function test_base_setting() {
// Instantiate base_setting and check everything
$bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
$this->assertTrue($bs instanceof base_setting);
@@ -290,10 +290,54 @@ class backp_settings_testcase extends basic_testcase {
$this->assertEquals($ubs3->get_status(), $ubs1->get_status());
}
/**
* Test that locked and unlocked states on dependent backup settings at the same level
* correctly do not flow from the parent to the child setting when the setting is locked by permissions.
*/
public function test_dependency_empty_locked_by_permission_child_is_not_unlocked() {
// Check dependencies are working ok.
$bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 2);
$bs1->set_level(1);
$bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 2);
$bs2->set_level(1); // Same level *must* work.
$bs1->add_dependency($bs2, setting_dependency::DISABLED_EMPTY);
$bs1->set_status(base_setting::LOCKED_BY_PERMISSION);
$this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
$bs2->set_status(base_setting::LOCKED_BY_PERMISSION);
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
// Unlocking the parent should NOT unlock the child.
$bs1->set_status(base_setting::NOT_LOCKED);
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs2->get_status());
}
/**
* Test that locked and unlocked states on dependent backup settings at the same level
* correctly do flow from the parent to the child setting when the setting is locked by config.
*/
public function test_dependency_not_empty_locked_by_config_parent_is_unlocked() {
$bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 0);
$bs1->set_level(1);
$bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 0);
$bs2->set_level(1); // Same level *must* work.
$bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
$bs1->set_status(base_setting::LOCKED_BY_CONFIG);
$this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
$this->assertEquals(base_setting::LOCKED_BY_CONFIG, $bs1->get_status());
// Unlocking the parent should unlock the child.
$bs1->set_status(base_setting::NOT_LOCKED);
$this->assertEquals(base_setting::NOT_LOCKED, $bs2->get_status());
}
/**
* test backup_setting class
*/
function test_backup_setting() {
public function test_backup_setting() {
// Instantiate backup_setting class and set level
$bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
$bs->set_level(1);
@@ -340,7 +384,7 @@ class backp_settings_testcase extends basic_testcase {
/**
* test activity_backup_setting class
*/
function test_activity_backup_setting() {
public function test_activity_backup_setting() {
$bs = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
$this->assertEquals($bs->get_level(), backup_setting::ACTIVITY_LEVEL);
@@ -355,7 +399,7 @@ class backp_settings_testcase extends basic_testcase {
/**
* test section_backup_setting class
*/
function test_section_backup_setting() {
public function test_section_backup_setting() {
$bs = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
$this->assertEquals($bs->get_level(), backup_setting::SECTION_LEVEL);
@@ -370,7 +414,7 @@ class backp_settings_testcase extends basic_testcase {
/**
* test course_backup_setting class
*/
function test_course_backup_setting() {
public function test_course_backup_setting() {
$bs = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
$this->assertEquals($bs->get_level(), backup_setting::COURSE_LEVEL);
@@ -14,6 +14,10 @@ Feature: Duplicate activities
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I log in as "admin"
And I set the following administration settings values:
| backup_import_activities | 0 |
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Database" to section "1" and I fill the form with:
+2 -2
View File
@@ -95,7 +95,7 @@ if ($badge->status != BADGE_STATUS_INACTIVE) {
$json['endorsement'] = $endorsementurl->out(false);
}
$competencies = $badge->get_alignment();
$competencies = $badge->get_alignments();
if (!empty($competencies)) {
foreach ($competencies as $item) {
$alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
@@ -123,4 +123,4 @@ if ($badge->status != BADGE_STATUS_INACTIVE) {
$json['error'] = get_string('error:relatedbadgedoesntexist', 'badges');
}
echo $OUTPUT->header();
echo json_encode($json);
echo json_encode($json);
+1 -1
View File
@@ -150,7 +150,7 @@ class core_badges_external extends external_api {
// Create a badge instance to be able to get the endorsement and other info.
$badgeinstance = new badge($badge->id);
$endorsement = $badgeinstance->get_endorsement();
$competencies = $badgeinstance->get_alignment();
$competencies = $badgeinstance->get_alignments();
$relatedbadges = $badgeinstance->get_related_badges();
if (!$canconfiguredetails) {
+87 -2
View File
@@ -376,18 +376,40 @@ class provider implements
// Export the badges.
$uniqueid = $DB->sql_concat_join("'-'", ['b.id', 'COALESCE(bc.id, 0)', 'COALESCE(bi.id, 0)',
'COALESCE(bma.id, 0)', 'COALESCE(bcm.id, 0)']);
'COALESCE(bma.id, 0)', 'COALESCE(bcm.id, 0)', 'COALESCE(brb.id, 0)', 'COALESCE(ba.id, 0)']);
$sql = "
SELECT $uniqueid AS uniqueid, b.id,
bi.id AS biid, bi.dateissued, bi.dateexpire, bi.uniquehash,
bma.id AS bmaid, bma.datemet, bma.issuerid,
bcm.id AS bcmid,
c.fullname AS coursename,
be.id AS beid,
be.issuername AS beissuername,
be.issuerurl AS beissuerurl,
be.issueremail AS beissueremail,
be.claimid AS beclaimid,
be.claimcomment AS beclaimcomment,
be.dateissued AS bedateissued,
brb.id as rbid,
brb.badgeid as rbbadgeid,
brb.relatedbadgeid as rbrelatedbadgeid,
ba.id as baid,
ba.targetname as batargetname,
ba.targeturl as batargeturl,
ba.targetdescription as batargetdescription,
ba.targetframework as batargetframework,
ba.targetcode as batargetcode,
$ctxfields
FROM {badge} b
LEFT JOIN {badge_issued} bi
ON bi.badgeid = b.id
AND bi.userid = :userid1
LEFT JOIN {badge_related} brb
ON ( b.id = brb.badgeid OR b.id = brb.relatedbadgeid )
LEFT JOIN {badge_competencies} ba
ON ( b.id = ba.badgeid )
LEFT JOIN {badge_endorsement} be
ON be.badgeid = b.id
LEFT JOIN {badge_manual_award} bma
ON bma.badgeid = b.id
AND bma.recipientid = :userid2
@@ -421,15 +443,32 @@ class provider implements
if ($carry === null) {
$carry = [
'name' => $badge->name,
'version' => $badge->version,
'language' => $badge->language,
'imageauthorname' => $badge->imageauthorname,
'imageauthoremail' => $badge->imageauthoremail,
'imageauthorurl' => $badge->imageauthorurl,
'imagecaption' => $badge->imagecaption,
'issued' => null,
'manual_award' => null,
'criteria_met' => []
'criteria_met' => [],
'endorsement' => null,
];
if ($badge->type == BADGE_TYPE_COURSE) {
context_helper::preload_from_record($record);
$carry['course'] = format_string($record->coursename, true, ['context' => $badge->get_context()]);
}
if (!empty($record->beid)) {
$carry['endorsement'] = [
'issuername' => $record->beissuername,
'issuerurl' => $record->beissuerurl,
'issueremail' => $record->beissueremail,
'claimid' => $record->beclaimid,
'claimcomment' => $record->beclaimcomment,
'dateissued' => $record->bedateissued ? transform::datetime($record->bedateissued) : null
];
}
if (!empty($record->biid)) {
$carry['issued'] = [
@@ -446,6 +485,52 @@ class provider implements
];
}
}
if (!empty($record->rbid)) {
if (empty($carry['related_badge'])) {
$carry['related_badge'] = [];
}
$rbid = $record->rbbadgeid;
if ($rbid == $record->id) {
$rbid = $record->rbrelatedbadgeid;
}
$exists = false;
foreach ($carry['related_badge'] as $related) {
if ($related['badgeid'] == $rbid) {
$exists = true;
break;
}
}
if (!$exists) {
$relatedbadge = new badge($rbid);
$carry['related_badge'][] = [
'badgeid' => $rbid,
'badgename' => $relatedbadge->name
];
}
}
if (!empty($record->baid)) {
if (empty($carry['alignment'])) {
$carry['alignment'] = [];
}
$exists = false;
$newalignment = [
'targetname' => $record->batargetname,
'targeturl' => $record->batargeturl,
'targetdescription' => $record->batargetdescription,
'targetframework' => $record->batargetframework,
'targetcode' => $record->batargetcode,
];
foreach ($carry['alignment'] as $alignment) {
if ($alignment == $newalignment) {
$exists = true;
break;
}
}
if (!$exists) {
$carry['alignment'][] = $newalignment;
}
}
// Export the details of the criteria met.
// We only do that once, when we find that a least one criteria was met.
+1 -1
View File
@@ -97,7 +97,7 @@ if ($alignmentid || $action == 'add' || $action == 'edit') {
$urlbutton = new moodle_url('/badges/competency.php', array('id' => $badge->id, 'action' => 'add'));
echo $OUTPUT->box($OUTPUT->single_button($urlbutton, get_string('addalignment', 'badges')), 'clearfix mdl-align');
}
$alignment = $badge->get_alignment();
$alignment = $badge->get_alignments();
if (count($alignment) > 0) {
$renderrelated = new badge_competencies_alignment($alignment, $badgeid);
echo $output->render($renderrelated);
+6 -5
View File
@@ -50,14 +50,15 @@ function badge_review_cron() {
if (empty($CFG->badges_allowcoursebadges)) {
$coursesql = '';
} else {
$coursesql = ' OR EXISTS (SELECT id FROM {course} WHERE visible = :visible AND startdate < :current) ';
$coursesql = ' OR EXISTS (SELECT c.id FROM {course} c WHERE c.visible = :visible AND c.startdate < :current'
. ' AND c.id = b.courseid)';
$courseparams = array('visible' => true, 'current' => time());
}
$sql = 'SELECT id
FROM {badge}
WHERE (status = :active OR status = :activelocked)
AND (type = :site ' . $coursesql . ')';
$sql = 'SELECT b.id
FROM {badge} b
WHERE (b.status = :active OR b.status = :activelocked)
AND (b.type = :site ' . $coursesql . ')';
$badgeparams = array(
'active' => BADGE_STATUS_ACTIVE,
'activelocked' => BADGE_STATUS_ACTIVE_LOCKED,
+1
View File
@@ -96,6 +96,7 @@ if (!has_any_capability(array(
'moodle/badges:viewawarded',
'moodle/badges:createbadge',
'moodle/badges:awardbadge',
'moodle/badges:configurecriteria',
'moodle/badges:configuremessages',
'moodle/badges:configuredetails',
'moodle/badges:deletebadge'), $PAGE->context)) {
+2 -2
View File
@@ -428,7 +428,7 @@ class core_badges_renderer extends plugin_renderer_base {
}
$output .= html_writer::alist($items, array(), 'ul');
}
$competencies = $badge->get_alignment();
$competencies = $badge->get_alignments();
if (!empty($competencies)) {
$output .= $this->heading(get_string('alignment', 'badges'), 3);
$items = array();
@@ -1106,7 +1106,7 @@ class core_badges_renderer extends plugin_renderer_base {
protected function print_badge_competencies(badge $badge) {
$output = '';
$output .= $this->heading(get_string('alignment', 'badges'), 3);
$competencies = $badge->get_alignment();
$competencies = $badge->get_alignments();
if (!empty($competencies)) {
$items = array();
foreach ($competencies as $competency) {
+3 -3
View File
@@ -813,7 +813,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
$competency2 = clone $competency1;
$newid1 = $badge->save_alignment($competency1);
$newid2 = $badge->save_alignment($competency2);
$competencies1 = $badge->get_alignment();
$competencies1 = $badge->get_alignments();
$this->assertCount(2, $competencies1);
$this->assertEquals($competency1->badgeid, $competencies1[$newid1]->badgeid);
@@ -826,12 +826,12 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
// Update competency aligment.
$competencies1[$newid1]->targetname = 'CCSS.ELA-Literacy.RST.11-12.3 update';
$badge->save_alignment($competencies1[$newid1], $competencies1[$newid1]->id);
$competencies2 = $badge->get_alignment();
$competencies2 = $badge->get_alignments();
$this->assertEquals($competencies1[$newid1]->id, $competencies2[$newid1]->id);
$this->assertEquals($competencies1[$newid1]->targetname, $competencies2[$newid1]->targetname);
// Delete competency alignment.
$badge->delete_alignment($competencies1[$newid2]->id);
$this->assertCount(1, $badge->get_alignment());
$this->assertCount(1, $badge->get_alignments());
}
}
+1 -1
View File
@@ -165,7 +165,7 @@ class core_badges_external_testcase extends externallib_advanced_testcase {
// Get the endorsement, competencies and related badges.
$badgeinstance = new badge($badge->id);
$endorsement = $badgeinstance->get_endorsement();
$competencies = $badgeinstance->get_alignment();
$competencies = $badgeinstance->get_alignments();
$relatedbadges = $badgeinstance->get_related_badges();
$badge->competencies = array();
$badge->relatedbadges = array();
+104
View File
@@ -306,7 +306,11 @@ class core_badges_privacy_testcase extends provider_testcase {
$u2ctx = context_user::instance($u2->id);
$b1 = $this->create_badge(['usercreated' => $u3->id]);
$this->endorse_badge(['badgeid' => $b1->id]);
$this->align_badge(['badgeid' => $b1->id], ' (1)');
$this->align_badge(['badgeid' => $b1->id], ' (2)');
$b2 = $this->create_badge(['type' => BADGE_TYPE_COURSE, 'courseid' => $c1->id, 'usermodified' => $u3->id]);
$this->relate_badge($b1->id, $b2->id);
$b3 = $this->create_badge();
$b3crit = $this->create_criteria_manual($b3->id);
$b4 = $this->create_badge();
@@ -333,6 +337,12 @@ class core_badges_privacy_testcase extends provider_testcase {
$path = [get_string('badges', 'core_badges'), "{$b1->name} ({$b1->id})"];
$data = writer::with_context($u1ctx)->get_data($path);
$this->assertEquals($b1->name, $data->name);
$this->assertEquals($b1->version, $data->version);
$this->assertEquals($b1->language, $data->language);
$this->assertEquals($b1->imageauthorname, $data->imageauthorname);
$this->assertEquals($b1->imageauthoremail, $data->imageauthoremail);
$this->assertEquals($b1->imageauthorurl, $data->imageauthorurl);
$this->assertEquals($b1->imagecaption, $data->imagecaption);
$this->assertNotEmpty($data->issued);
$this->assertEmpty($data->manual_award);
$this->assertEmpty($data->criteria_met);
@@ -340,6 +350,33 @@ class core_badges_privacy_testcase extends provider_testcase {
$this->assertEquals('yoohoo', $data->issued['unique_hash']);
$this->assertNull($data->issued['expires_on']);
$this->assertNotEmpty($data->endorsement);
$this->assertNotEmpty($data->endorsement['issuername']);
$this->assertNotEmpty($data->endorsement['issuerurl']);
$this->assertNotEmpty($data->endorsement['issueremail']);
$this->assertNotEmpty($data->endorsement['claimid']);
$this->assertNotEmpty($data->endorsement['claimcomment']);
$this->assertNotEmpty($data->endorsement['dateissued']);
$this->assertNotEmpty($data->related_badge);
$this->assertNotEmpty($data->related_badge[0]);
$this->assertEquals($data->related_badge[0]['badgeid'], $b2->id);
$this->assertEquals($data->related_badge[0]['badgename'], $b2->name);
$this->assertNotEmpty($data->alignment);
$this->assertNotEmpty($data->alignment[0]);
$this->assertNotEmpty($data->alignment[0]['targetname']);
$this->assertNotEmpty($data->alignment[0]['targeturl']);
$this->assertNotEmpty($data->alignment[0]['targetdescription']);
$this->assertNotEmpty($data->alignment[0]['targetframework']);
$this->assertNotEmpty($data->alignment[0]['targetcode']);
$this->assertNotEmpty($data->alignment[1]);
$this->assertNotEmpty($data->alignment[1]['targetname']);
$this->assertNotEmpty($data->alignment[1]['targeturl']);
$this->assertNotEmpty($data->alignment[1]['targetdescription']);
$this->assertNotEmpty($data->alignment[1]['targetframework']);
$this->assertNotEmpty($data->alignment[1]['targetcode']);
$path = [get_string('badges', 'core_badges'), "{$b2->name} ({$b2->id})"];
$data = writer::with_context($u1ctx)->get_data($path);
$this->assertEquals($b2->name, $data->name);
@@ -598,12 +635,79 @@ class core_badges_privacy_testcase extends provider_testcase {
'attachment' => 1,
'notification' => 0,
'status' => BADGE_STATUS_ACTIVE,
'version' => OPEN_BADGES_V2,
'language' => 'en',
'imageauthorname' => 'Image author',
'imageauthoremail' => 'author@example.com',
'imageauthorurl' => 'http://image.example.com/',
'imagecaption' => 'Image caption'
], $params);
$record->id = $DB->insert_record('badge', $record);
return $record;
}
/**
* Relate a badge.
*
* @param int $badgeid The badge ID.
* @param int $relatedbadgeid The related badge ID.
* @return object
*/
protected function relate_badge(int $badgeid, int $relatedbadgeid) {
global $DB;
$record = (object) [
'badgeid' => $badgeid,
'relatedbadgeid' => $relatedbadgeid
];
$record->id = $DB->insert_record('badge_related', $record);
return $record;
}
/**
* Align a badge.
*
* @param array $params Parameters.
* @param string $suffix Option to append to strings.
* @return object
*/
protected function align_badge(array $params = [], $suffix = '') {
global $DB;
$record = (object) array_merge([
'badgeid' => null,
'targetname' => "Alignment name" . $suffix,
'targeturl' => "http://issuer-url.domain.co.nz",
'targetdescription' => "Description" . $suffix,
'targetframework' => "Framework" . $suffix,
'targetcode' => "Code" . $suffix
], $params);
$record->id = $DB->insert_record('badge_competencies', $record);
return $record;
}
/**
* Endorse a badge.
*
* @param array $params Parameters.
* @return object
*/
protected function endorse_badge(array $params = []) {
global $DB, $USER;
$record = (object) array_merge([
'issuername' => "External issuer name",
'issuerurl' => "http://issuer-url.domain.co.nz",
'issueremail' => "issuer@example.com",
'claimid' => "Claim ID",
'claimcomment' => "Claim comment",
'dateissued' => time()
], $params);
$record->id = $DB->insert_record('badge_endorsement', $record);
return $record;
}
/**
* Create a backpack.
*
+2 -2
View File
@@ -429,13 +429,13 @@ class block_base {
function html_attributes() {
$attributes = array(
'id' => 'inst' . $this->instance->id,
'class' => 'block_' . $this->name(). ' block',
'class' => 'block_' . $this->name() . ' block',
'role' => $this->get_aria_role()
);
if ($this->hide_header()) {
$attributes['class'] .= ' no-header';
}
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_'.$this->instance->id, 0)) {
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_' . $this->instance->id, 0)) {
$attributes['class'] .= ' dock_on_load';
}
return $attributes;
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -499,7 +499,7 @@ function(
}
// Set the last page to either the current or next page
if (loadedPages[currentPage].courses.length < pageData.limit) {
if (loadedPages[currentPage].courses.length < pageData.limit || !remainingCourses.length) {
lastPage = currentPage;
actions.allItemsLoaded(currentPage);
} else if (loadedPages[currentPage + 1] != undefined
@@ -27,7 +27,7 @@
$string['everything'] = 'Everything';
$string['courses'] = 'Categories and courses';
$string['coursestructures'] = 'Categories, courses, and course structures';
$string['courseactivities'] = 'Categories, courses, and course Activities';
$string['courseactivities'] = 'Categories, courses, and course activities';
$string['enabledockdesc'] = 'Allow the user to dock this block';
$string['expansionlimit'] = 'Generate navigation for the following';
$string['linkcategoriesdesc'] = 'Display categories as links';
@@ -23,20 +23,27 @@
{}
}}
<div data-region="day-filter" class="dropdown">
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
aria-label="{{#str}} ariadayfilter, block_timeline {{/str}}" aria-controls="menudayfilter">
{{#pix}} i/duration {{/pix}}
<span class="sr-only">
{{#str}} ariadayfilter, block_timeline {{/str}}
<span data-active-item-text>{{#str}} next30days, block_timeline {{/str}}</span>
<span class="sr-only" data-active-item-text>
{{#all}} {{#str}} all, core {{/str}} {{/all}}
{{#overdue}} {{#str}} overdue, block_timeline {{/str}} {{/overdue}}
{{#next7days}} {{#str}}next7days, block_timeline {{/str}} {{/next7days}}
{{#next30days}} {{#str}}next30days, block_timeline {{/str}} {{/next30days}}
{{#next3months}} {{#str}}next3months, block_timeline {{/str}} {{/next3months}}
{{#next6months}} {{#str}}next6months, block_timeline {{/str}} {{/next6months}}
</span>
</button>
<div role="menu" class="dropdown-menu" data-show-active-item>
<div id="menudayfilter" role="menu" class="dropdown-menu" data-show-active-item>
<a
class="dropdown-item {{#all}} active {{/all}}"
href="#"
data-from="-14"
data-filtername="all"
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} all, core {{/str}}{{/str}}"
role="menuitem"
{{#all}}aria-current="true"{{/all}}
>
{{#str}} all, core {{/str}}
</a>
@@ -47,10 +54,12 @@
data-to="0"
data-filtername="overdue"
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} overdue, block_timeline {{/str}}{{/str}}"
role="menuitem"
{{#overdue}}aria-current="true"{{/overdue}}
>
{{#str}} overdue, block_timeline {{/str}}
</a>
<div class="dropdown-divider"></div>
<div class="dropdown-divider" role="separator"></div>
<h6 class="dropdown-header">{{#str}} duedate, block_timeline {{/str}}</h6>
<a
class="dropdown-item {{#next7days}} active {{/next7days}}"
@@ -59,6 +68,8 @@
data-to="7"
data-filtername="next7days"
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
role="menuitem"
{{#next7days}}aria-current="true"{{/next7days}}
>
{{#str}} next7days, block_timeline {{/str}}
</a>
@@ -69,6 +80,8 @@
data-to="30"
data-filtername="next30days"
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
role="menuitem"
{{#next30days}}aria-current="true"{{/next30days}}
>
{{#str}} next30days, block_timeline {{/str}}
</a>
@@ -79,6 +92,8 @@
data-to="90"
data-filtername="next3months"
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
role="menuitem"
{{#next3months}}aria-current="true"{{/next3months}}
>
{{#str}} next3months, block_timeline {{/str}}
</a>
@@ -89,6 +104,8 @@
data-to="180"
data-filtername="next6months"
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
role="menuitem"
{{#next6months}}aria-current="true"{{/next6months}}
>
{{#str}} next6months, block_timeline {{/str}}
</a>
@@ -23,20 +23,23 @@
{}
}}
<div data-region="view-selector" class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
aria-label="{{#str}} ariaviewselector, block_timeline{{/str}}" aria-controls="menusortby">
{{#pix}} i/filter {{/pix}}
<span class="sr-only">
{{#sorttimelinecourses}}<span data-active-item-text>{{/sorttimelinecourses}}{{#str}} ariaviewselector, block_timeline{{/str}}{{#sorttimelinecourses}}</span>{{/sorttimelinecourses}}
{{#sorttimelinedates}}<span data-active-item-text>{{/sorttimelinedates}}{{#str}} sortbydates, block_timeline {{/str}}{{#sorttimelinedates}}</span>{{/sorttimelinedates}}
<span class="sr-only" data-active-item-text>
{{#sorttimelinecourses}}{{#str}} sortbycourses, block_timeline{{/str}}{{/sorttimelinecourses}}
{{#sorttimelinedates}}{{#str}} sortbydates, block_timeline {{/str}}{{/sorttimelinedates}}
</span>
</button>
<div role="menu" class="dropdown-menu dropdown-menu-right list-group hidden" data-show-active-item data-skip-active-class="true" >
<div id="menusortby" role="menu" class="dropdown-menu dropdown-menu-right list-group hidden" data-show-active-item data-skip-active-class="true">
<a
class="dropdown-item {{#sorttimelinedates}}active{{/sorttimelinedates}}"
href="#view_dates_{{uniqid}}"
data-toggle="tab"
data-filtername="sortbydates"
aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbydates, block_timeline {{/str}}{{/str}}"
role="menuitem"
{{#sorttimelinedates}}aria-current="true"{{/sorttimelinedates}}
>
{{#str}} sortbydates, block_timeline {{/str}}
</a>
@@ -46,6 +49,8 @@
data-toggle="tab"
data-filtername="sortbycourses"
aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbycourses, block_timeline {{/str}}{{/str}}"
role="menuitem"
{{#sorttimelinecourses}}aria-current="true"{{/sorttimelinecourses}}
>
{{#str}} sortbycourses, block_timeline {{/str}}
</a>
@@ -36,7 +36,7 @@ Feature: The timeline block allows users to see upcoming courses
Scenario: Next 30 days in course view
Given I log in as "student1"
And I click on "Sort" "button" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
When I click on "Sort by courses" "link" in the "Timeline" "block"
Then I should see "Course 1" in the "Timeline" "block"
And I should see "Course 2" in the "Timeline" "block"
@@ -52,9 +52,9 @@ Feature: The timeline block allows users to see upcoming courses
Scenario: All in course view
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And I click on "Sort" "button" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
When I click on "More courses" "button" in the "Timeline" "block"
Then I should see "Course 3" in the "Timeline" "block"
@@ -73,9 +73,9 @@ Feature: The timeline block allows users to see upcoming courses
Scenario: Persistent sort filter
Given I log in as "student1"
And I click on "Sort" "button" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
And I click on "Sort" "button" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I reload the page
Then I should see "Course 1" in the "Timeline" "block"
@@ -33,7 +33,7 @@ Feature: The timeline block allows users to see upcoming activities
Scenario: Next 7 days in date view
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
When I click on "Next 7 days" "link" in the "Timeline" "block"
Then I should see "Test choice 1 closes" in the "Timeline" "block"
And I should see "Test feedback 1 closes" in the "Timeline" "block"
@@ -44,7 +44,7 @@ Feature: The timeline block allows users to see upcoming activities
Scenario: Overdue in date view
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
Then I should see "Test assign 1 is due" in the "Timeline" "block"
And I should not see "Test choice 2 closes" in the "Timeline" "block"
@@ -55,7 +55,7 @@ Feature: The timeline block allows users to see upcoming activities
Scenario: All in date view
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
When I click on "All" "link" in the "Timeline" "block"
Then I should see "Test assign 1 is due" in the "Timeline" "block"
And I should see "Test feedback 1 closes" in the "Timeline" "block"
@@ -75,7 +75,7 @@ Feature: The timeline block allows users to see upcoming activities
Scenario: All in date view no next
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And I click on "5" "button" in the "Timeline" "block"
When I click on "25" "link" in the "Timeline" "block"
@@ -89,7 +89,7 @@ Feature: The timeline block allows users to see upcoming activities
Scenario: Persistent All in date view
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
When I click on "All" "link" in the "Timeline" "block"
And I reload the page
Then I should see "Test assign 1 is due" in the "Timeline" "block"
@@ -110,7 +110,7 @@ Feature: The timeline block allows users to see upcoming activities
Scenario: Persistent Overdue in date view
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
And I reload the page
Then I should see "Test assign 1 is due" in the "Timeline" "block"
@@ -35,9 +35,9 @@ Feature: The timeline block allows user persistence of their page limits
Scenario: Toggle the page limit 5 - 25
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And I click on "Sort" "button" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
When I click on "5" "button" in the "Timeline" "block"
And I click on "25" "link"
@@ -48,9 +48,9 @@ Feature: The timeline block allows user persistence of their page limits
Scenario: Toggle the page limit 25 - 5
Given I log in as "student1"
And I click on "Next 30 days" "button" in the "Timeline" "block"
And I click on "Filter timeline items" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And I click on "Sort" "button" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
When I click on "5" "button" in the "Timeline" "block"
And I click on "25" "link"
+7
View File
@@ -1284,6 +1284,13 @@ class cache implements cache_loader {
return -1;
}
}
/**
* Subclasses may support purging cache of all data belonging to the
* current user.
*/
public function purge_current_user() {
}
}
/**
+1
View File
@@ -49,6 +49,7 @@ class core_calendar_export_form extends moodleform {
$export = array();
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsall', 'calendar'), 'all');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocategories', 'calendar'), 'categories');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocourses', 'calendar'), 'courses');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtogroups', 'calendar'), 'groups');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventspersonal', 'calendar'), 'user');
+1 -1
View File
@@ -125,7 +125,7 @@ class event_exporter_base extends exporter {
'null' => NULL_ALLOWED
],
'location' => [
'type' => PARAM_RAW_TRIMMED,
'type' => PARAM_RAW,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
+1 -2
View File
@@ -532,8 +532,7 @@ class provider implements
AND ctx.contextlevel = :categorycontext)
OR (e.courseid = ctx.instanceid
AND (e.eventtype = 'course'
OR e.eventtype = 'group'
OR e.modulename != '0')
OR e.eventtype = 'group')
AND ctx.contextlevel = :coursecontext)
OR (e.userid = ctx.instanceid
AND e.eventtype = 'user'
+13 -5
View File
@@ -40,8 +40,8 @@ $time = optional_param('preset_time', 'weeknow', PARAM_ALPHA);
$now = $calendartype->timestamp_to_date_array(time());
// Let's see if we have sufficient and correct data
$allowed_what = array('all', 'user', 'groups', 'courses');
$allowed_time = array('weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming', 'custom');
$allowedwhat = ['all', 'user', 'groups', 'courses', 'categories'];
$allowedtime = ['weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming', 'custom'];
if (!empty($generateurl)) {
$authtoken = sha1($user->id . $user->password . $CFG->calendar_exportsalt);
@@ -56,9 +56,9 @@ if (!empty($generateurl)) {
redirect($link->out());
die;
}
$paramcategory = false;
if(!empty($what) && !empty($time)) {
if(in_array($what, $allowed_what) && in_array($time, $allowed_time)) {
if(in_array($what, $allowedwhat) && in_array($time, $allowedtime)) {
$courses = enrol_get_users_courses($user->id, true, 'id, visible, shortname');
// Array of courses that we will pass to calendar_get_legacy_events() which
// is initially set to the list of the user's courses.
@@ -78,6 +78,7 @@ if(!empty($what) && !empty($time)) {
$courses[SITEID] = new stdClass;
$courses[SITEID]->shortname = get_string('globalevents', 'calendar');
$paramcourses[SITEID] = $courses[SITEID];
$paramcategory = true;
} else if ($what == 'groups') {
$users = false;
$paramcourses = array();
@@ -85,6 +86,11 @@ if(!empty($what) && !empty($time)) {
$users = $user->id;
$groups = false;
$paramcourses = array();
} else if ($what == 'categories') {
$users = $user->id;
$groups = false;
$paramcourses = array();
$paramcategory = true;
} else {
$users = false;
$groups = false;
@@ -180,7 +186,9 @@ if(!empty($what) && !empty($time)) {
die();
}
}
$events = calendar_get_legacy_events($timestart, $timeend, $users, $groups, array_keys($paramcourses), false);
$events = calendar_get_legacy_events($timestart, $timeend, $users, $groups, array_keys($paramcourses), false, true,
$paramcategory);
$ical = new iCalendar;
$ical->add_property('method', 'PUBLISH');
+36 -41
View File
@@ -608,49 +608,44 @@ class calendar_event {
$updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall));
if ($updaterepeated) {
// Update all.
$sqlset = 'name = ?,
description = ?,
timeduration = ?,
timemodified = ?,
groupid = ?,
courseid = ?';
// Note: Group and course id may not be set. If not, keep their current values.
$params = [
$this->properties->name,
$this->properties->description,
$this->properties->timeduration,
time(),
isset($this->properties->groupid) ? $this->properties->groupid : $event->groupid,
isset($this->properties->courseid) ? $this->properties->courseid : $event->courseid,
];
// Note: Only update start date, if it was changed by the user.
if ($this->properties->timestart != $event->timestart) {
$timestartoffset = $this->properties->timestart - $event->timestart;
$sql = "UPDATE {event}
SET name = ?,
description = ?,
timestart = timestart + ?,
timeduration = ?,
timemodified = ?,
groupid = ?,
courseid = ?
WHERE repeatid = ?";
// Note: Group and course id may not be set. If not, keep their current values.
$params = [
$this->properties->name,
$this->properties->description,
$timestartoffset,
$this->properties->timeduration,
time(),
isset($this->properties->groupid) ? $this->properties->groupid : $event->groupid,
isset($this->properties->courseid) ? $this->properties->courseid : $event->courseid,
$event->repeatid
];
} else {
$sql = "UPDATE {event}
SET name = ?,
description = ?,
timeduration = ?,
timemodified = ?,
groupid = ?,
courseid = ?
WHERE repeatid = ?";
// Note: Group and course id may not be set. If not, keep their current values.
$params = [
$this->properties->name,
$this->properties->description,
$this->properties->timeduration,
time(),
isset($this->properties->groupid) ? $this->properties->groupid : $event->groupid,
isset($this->properties->courseid) ? $this->properties->courseid : $event->courseid,
$event->repeatid
];
$sqlset .= ', timestart = timestart + ?';
$params[] = $timestartoffset;
}
// Note: Only update location, if it was changed by the user.
$updatelocation = (!empty($this->properties->location) && $this->properties->location !== $event->location);
if ($updatelocation) {
$sqlset .= ', location = ?';
$params[] = $this->properties->location;
}
// Update all.
$sql = "UPDATE {event}
SET $sqlset
WHERE repeatid = ?";
$params[] = $event->repeatid;
$DB->execute($sql, $params);
// Trigger an update event for each of the calendar event.
@@ -2851,7 +2846,7 @@ function calendar_add_icalendar_event($event, $unused = null, $subscriptionid, $
}
$eventrecord->location = empty($event->properties['LOCATION'][0]->value) ? '' :
str_replace('\\', '', $event->properties['LOCATION'][0]->value);
trim(str_replace('\\', '', $event->properties['LOCATION'][0]->value));
$eventrecord->uuid = $event->properties['UID'][0]->value;
$eventrecord->timemodified = time();
+8
View File
@@ -48,6 +48,14 @@ Feature: Export calendar events
When I click on "Get calendar URL" "button"
Then I should see "&preset_what=groups&"
Scenario: Generating calendar URL for category events
Given I follow "This month"
And I click on "Export calendar" "button"
And I set the field "Events related to categories" to "1"
And I set the field "Recent and next 60 days" to "1"
When I click on "Get calendar URL" "button"
Then I should see "&preset_what=categories&"
Scenario: Generating calendar URL for user events
Given I follow "This month"
And I click on "Export calendar" "button"
+13 -2
View File
@@ -365,12 +365,23 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Delete all Calendar Events for all Users by Context for Course 2.
provider::delete_data_for_all_users_in_context($course2context);
// Verify all Calendar Events for Course 2 were deleted.
$events = $DB->get_records('event', array('courseid' => $course2->id));
// Verify all Calendar Events for Course 2 context were deleted.
$events = $DB->get_records('event', array('courseid' => $course2->id, 'modulename' => '0'));
$this->assertCount(0, $events);
// Verify all Calendar Subscriptions for Course 2 were deleted.
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course2->id));
$this->assertCount(0, $subscriptions);
// Verify all Calendar Events for the assignment exists still.
$events = $DB->get_records('event', array('modulename' => 'assign'));
$this->assertCount(2, $events);
// Delete all Calendar Events for all Users by Context for the assignment.
provider::delete_data_for_all_users_in_context($modulecontext);
// Verify all Calendar Events for the assignment context were deleted.
$events = $DB->get_records('event', array('modulename' => 'assign'));
$this->assertCount(0, $events);
}
/**
+2 -7
View File
@@ -28,7 +28,6 @@ require_once($CFG->dirroot.'/cohort/upload_form.php');
require_once($CFG->libdir . '/csvlib.class.php');
$contextid = optional_param('contextid', 0, PARAM_INT);
$returnurl = optional_param('returnurl', '', PARAM_URL);
require_login();
@@ -56,13 +55,9 @@ if ($context->contextlevel == CONTEXT_COURSECAT) {
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
}
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id, 'returnurl' => $returnurl));
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id));
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else {
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
}
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
if ($uploadform->is_cancelled()) {
redirect($returnurl);
-3
View File
@@ -48,9 +48,6 @@ class cohort_upload_form extends moodleform {
$mform = $this->_form;
$data = (object)$this->_customdata;
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_URL);
$mform->addElement('header', 'cohortfileuploadform', get_string('uploadafile'));
$filepickeroptions = array();
@@ -192,8 +192,8 @@ class course_module_competency extends persistent {
$sql = 'SELECT COUNT(comp.id)
FROM {' . self::TABLE . '} coursemodulecomp
JOIN {' . competency::TABLE . '} comp
ON coursecomp.competencyid = comp.id
WHERE coursecomp.cmid = ? ';
ON coursemodulecomp.competencyid = comp.id
WHERE coursemodulecomp.cmid = ? ';
$params = array($cmid);
$results = $DB->count_records_sql($sql, $params);
@@ -0,0 +1,72 @@
<?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/>.
/**
* Course module competency persistent class tests.
*
* @package core_competency
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_competency\course_module_competency;
/**
* Course module competency persistent testcase.
*
* @package core_competency
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_competency_course_module_competency_testcase extends advanced_testcase {
public function test_count_competencies() {
global $CFG, $DB;
$this->resetAfterTest(true);
$dg = $this->getDataGenerator();
$lpg = $dg->get_plugin_generator('core_competency');
$c1 = $dg->create_course();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$framework = $lpg->create_framework();
$comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C1, and C2.
$comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C2.
$lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id));
$lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id));
$assign1a = $dg->create_module('assign', ['course' => $c1]);
$assign1b = $dg->create_module('assign', ['course' => $c1]);
$cmc1a = $lpg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]);
$cmc1b = $lpg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]);
$cmc2b = $lpg->create_course_module_competency(['competencyid' => $comp2->get('id'), 'cmid' => $assign1b->cmid]);
// Enrol the user 1 in C1.
$dg->enrol_user($u1->id, $c1->id);
$all = course_module_competency::list_course_module_competencies($assign1a->cmid);
$this->assertEquals(course_module_competency::count_competencies($assign1a->cmid), count($all));
$all = course_module_competency::list_course_module_competencies($assign1b->cmid);
$this->assertEquals(course_module_competency::count_competencies($assign1b->cmid), count($all));
}
}
+4
View File
@@ -60,6 +60,10 @@ class progress {
return null;
}
if (!$completion->is_tracked_user($userid)) {
return null;
}
// Before we check how many modules have been completed see if the course has.
if ($completion->is_course_complete($userid)) {
return 100;
+29
View File
@@ -151,4 +151,33 @@ class core_completion_progress_testcase extends advanced_testcase {
// Check that the result was null.
$this->assertNull(\core_completion\progress::get_course_progress_percentage($course));
}
/**
* Tests that the course progress returns null for a not tracked for completion user in a course.
*/
public function test_course_progress_not_tracked_user() {
global $DB;
// Add a course that supports completion.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Enrol a user in the course.
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Now, mark the course as completed.
$ccompletion = new completion_completion(array('course' => $course->id, 'userid' => $user->id));
$ccompletion->mark_complete();
// The course completion should return 100.
$this->assertEquals('100', \core_completion\progress::get_course_progress_percentage($course, $user->id));
// Now make the user's role to be not tracked for completion.
unassign_capability('moodle/course:isincompletionreports', $studentrole->id);
// Check that the result is null now.
$this->assertNull(\core_completion\progress::get_course_progress_percentage($course, $user->id));
}
}
+6
View File
@@ -798,6 +798,12 @@ $CFG->admin = 'admin';
// Example:
// $CFG->behat_usedeprecated = true;
//
// If you are using a slow machine, it may help to increase the timeouts that Behat uses. The
// following example will increase timeouts by a factor of 3 (using 30 seconds instead of 10
// seconds, for instance).
// Example:
// $CFG->behat_increasetimeout = 3;
//
// Including feature files from directories outside the dirroot is possible if required. The setting
// requires that the running user has executable permissions on all parent directories in the paths.
// Example:
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -13
View File
@@ -352,30 +352,21 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
* @param {String} image new image name ("i/show", "i/hide", etc.)
* @param {String} stringname new string for the action menu item
* @param {String} stringcomponent
* @param {String} titlestr string for "title" attribute (if different from stringname)
* @param {String} titlecomponent
* @param {String} titlestr not used
* @param {String} titlecomponent not used
* @param {String} newaction new value for data-action attribute of the link
* @return {Promise} promise which is resolved when the replacement has completed
*/
var replaceActionItem = function(actionitem, image, stringname,
stringcomponent, titlestr, titlecomponent, newaction) {
var stringRequests = [{key: stringname, component: stringcomponent}];
if (titlestr) {
stringRequests.push({key: titlestr, component: titlecomponent});
}
// Do not provide an icon with duplicate, different text to the menu item.
return str.get_strings(stringRequests).then(function(strings) {
actionitem.find('span.menu-action-text').html(strings[0]);
actionitem.attr('title', strings[0]);
var title = '';
if (titlestr) {
title = strings[1];
actionitem.attr('title', title);
}
return templates.renderPix(image, 'core', title);
return templates.renderPix(image, 'core');
}).then(function(pixhtml) {
actionitem.find('.icon').replaceWith(pixhtml);
actionitem.attr('data-action', newaction);
+1 -1
View File
@@ -766,7 +766,7 @@ class helper {
*/
public static function get_management_viewmodes() {
return array(
'combined' => new \lang_string('categoriesandcoures'),
'combined' => new \lang_string('categoriesandcourses'),
'categories' => new \lang_string('categories'),
'courses' => new \lang_string('courses')
);
+22
View File
@@ -3447,6 +3447,17 @@ class core_course_external extends external_api {
$id = $params['id'];
$sectionreturn = $params['sectionreturn'];
// Set of permissions an editing user may have.
$contextarray = [
'moodle/course:update',
'moodle/course:manageactivities',
'moodle/course:activityvisibility',
'moodle/course:sectionvisibility',
'moodle/course:movesections',
'moodle/course:setcurrentsection',
];
$PAGE->set_other_editing_capability($contextarray);
list($course, $cm) = get_course_and_cm_from_cmid($id);
$modcontext = context_module::instance($cm->id);
$coursecontext = context_course::instance($course->id);
@@ -3554,6 +3565,17 @@ class core_course_external extends external_api {
$id = $params['id'];
$sectionreturn = $params['sectionreturn'];
// Set of permissions an editing user may have.
$contextarray = [
'moodle/course:update',
'moodle/course:manageactivities',
'moodle/course:activityvisibility',
'moodle/course:sectionvisibility',
'moodle/course:movesections',
'moodle/course:setcurrentsection',
];
$PAGE->set_other_editing_capability($contextarray);
// Validate access to the course (note, this is html for the course view page, we don't validate access to the module).
list($course, $cm) = get_course_and_cm_from_cmid($id);
self::validate_context(context_course::instance($course->id));
+14 -15
View File
@@ -117,10 +117,9 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
$name = empty($value['name']) ? '' : $value['name'];
$attr = empty($value['attr']) ? array() : $value['attr'];
$class = empty($value['pixattr']['class']) ? '' : $value['pixattr']['class'];
$alt = empty($value['pixattr']['alt']) ? '' : $value['pixattr']['alt'];
$al = new action_menu_link_secondary(
new moodle_url($url),
new pix_icon($icon, $alt, null, array('class' => "smallicon " . $class)),
new pix_icon($icon, '', null, array('class' => "smallicon " . $class)),
$name,
$attr
);
@@ -328,8 +327,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'url' => new moodle_url('/course/editsection.php', array('id' => $section->id, 'sr' => $sectionreturn)),
'icon' => 'i/settings',
'name' => $streditsection,
'pixattr' => array('class' => '', 'alt' => $streditsection),
'attr' => array('class' => 'icon edit', 'title' => $streditsection));
'pixattr' => array('class' => ''),
'attr' => array('class' => 'icon edit'));
}
if ($section->section) {
@@ -343,8 +342,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'url' => $url,
'icon' => 'i/hide',
'name' => $strhidefromothers,
'pixattr' => array('class' => '', 'alt' => $strhidefromothers),
'attr' => array('class' => 'icon editing_showhide', 'title' => $strhidefromothers,
'pixattr' => array('class' => ''),
'attr' => array('class' => 'icon editing_showhide',
'data-sectionreturn' => $sectionreturn, 'data-action' => 'hide'));
} else {
$strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
@@ -353,8 +352,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'url' => $url,
'icon' => 'i/show',
'name' => $strshowfromothers,
'pixattr' => array('class' => '', 'alt' => $strshowfromothers),
'attr' => array('class' => 'icon editing_showhide', 'title' => $strshowfromothers,
'pixattr' => array('class' => ''),
'attr' => array('class' => 'icon editing_showhide',
'data-sectionreturn' => $sectionreturn, 'data-action' => 'show'));
}
}
@@ -370,8 +369,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'url' => $url,
'icon' => 'i/up',
'name' => $strmoveup,
'pixattr' => array('class' => '', 'alt' => $strmoveup),
'attr' => array('class' => 'icon moveup', 'title' => $strmoveup));
'pixattr' => array('class' => ''),
'attr' => array('class' => 'icon moveup'));
}
$url = clone($baseurl);
@@ -383,8 +382,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'url' => $url,
'icon' => 'i/down',
'name' => $strmovedown,
'pixattr' => array('class' => '', 'alt' => $strmovedown),
'attr' => array('class' => 'icon movedown', 'title' => $strmovedown));
'pixattr' => array('class' => ''),
'attr' => array('class' => 'icon movedown'));
}
}
}
@@ -405,8 +404,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'url' => $url,
'icon' => 'i/delete',
'name' => $strdelete,
'pixattr' => array('class' => '', 'alt' => $strdelete),
'attr' => array('class' => 'icon editing_delete', 'title' => $strdelete));
'pixattr' => array('class' => ''),
'attr' => array('class' => 'icon editing_delete'));
}
}
@@ -1009,7 +1008,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
if ($sectionreturn !== null) {
$url->param('sectionreturn', $sectionreturn);
}
$icon = $this->output->pix_icon('t/add', $straddsections);
$icon = $this->output->pix_icon('t/add', '');
$newsections = $maxsections - $lastsection;
echo html_writer::link($url, $icon . $straddsections,
array('class' => 'add-sections', 'data-add-sections' => $straddsections, 'new-sections' => $newsections));
+4 -6
View File
@@ -123,21 +123,19 @@ class format_topics_renderer extends format_section_renderer_base {
if ($section->section && has_capability('moodle/course:setcurrentsection', $coursecontext)) {
if ($course->marker == $section->section) { // Show the "light globe" on/off.
$url->param('marker', 0);
$markedthistopic = get_string('markedthistopic');
$highlightoff = get_string('highlightoff');
$controls['highlight'] = array('url' => $url, "icon" => 'i/marked',
'name' => $highlightoff,
'pixattr' => array('class' => '', 'alt' => $markedthistopic),
'attr' => array('class' => 'editing_highlight', 'title' => $markedthistopic,
'pixattr' => array('class' => ''),
'attr' => array('class' => 'editing_highlight',
'data-action' => 'removemarker'));
} else {
$url->param('marker', $section->section);
$markthistopic = get_string('markthistopic');
$highlight = get_string('highlight');
$controls['highlight'] = array('url' => $url, "icon" => 'i/marker',
'name' => $highlight,
'pixattr' => array('class' => '', 'alt' => $markthistopic),
'attr' => array('class' => 'editing_highlight', 'title' => $markthistopic,
'pixattr' => array('class' => ''),
'attr' => array('class' => 'editing_highlight',
'data-action' => 'setmarker'));
}
}
@@ -182,7 +182,7 @@ class format_topics_testcase extends advanced_testcase {
$tmpl = component_callback('format_weeks', 'inplace_editable', array('sectionname', $section->id, 'New name'));
$this->fail('Exception expected');
} catch (moodle_exception $e) {
$this->assertEquals(1, preg_match('/^Can not find data record in database/', $e->getMessage()));
$this->assertEquals(1, preg_match('/^Can\'t find data record in database/', $e->getMessage()));
}
}
@@ -188,7 +188,7 @@ class format_weeks_testcase extends advanced_testcase {
$tmpl = component_callback('format_topics', 'inplace_editable', array('sectionname', $section->id, 'New name'));
$this->fail('Exception expected');
} catch (moodle_exception $e) {
$this->assertEquals(1, preg_match('/^Can not find data record in database/', $e->getMessage()));
$this->assertEquals(1, preg_match('/^Can\'t find data record in database/', $e->getMessage()));
}
}
+11 -11
View File
@@ -1891,7 +1891,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
if ($hasmanageactivities) {
$actions['update'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('update' => $mod->id)),
new pix_icon('t/edit', $str->editsettings, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/edit', '', 'moodle', array('class' => 'iconsmall')),
$str->editsettings,
array('class' => 'editing_update', 'data-action' => 'update')
);
@@ -1917,7 +1917,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
}
$actions['moveright'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon($rightarrow, '', 'moodle', array('class' => 'iconsmall')),
$str->moveright,
array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright',
'data-keepopen' => true, 'data-sectionreturn' => $sr)
@@ -1930,7 +1930,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
}
$actions['moveleft'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon($leftarrow, '', 'moodle', array('class' => 'iconsmall')),
$str->moveleft,
array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft',
'data-keepopen' => true, 'data-sectionreturn' => $sr)
@@ -1953,7 +1953,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
if ($displayedoncoursepage) {
$actions['hide'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('hide' => $mod->id)),
new pix_icon('t/hide', $str->modhide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/hide', '', 'moodle', array('class' => 'iconsmall')),
$str->modhide,
array('class' => 'editing_hide', 'data-action' => 'hide')
);
@@ -1961,7 +1961,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
// Offer to "show" only if the section is visible.
$actions['show'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('show' => $mod->id)),
new pix_icon('t/show', $str->modshow, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/show', '', 'moodle', array('class' => 'iconsmall')),
$str->modshow,
array('class' => 'editing_show', 'data-action' => 'show')
);
@@ -1971,7 +1971,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
// When making the "stealth" module unavailable we perform the same action as hiding the visible module.
$actions['hide'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('hide' => $mod->id)),
new pix_icon('t/unblock', $str->makeunavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/unblock', '', 'moodle', array('class' => 'iconsmall')),
$str->makeunavailable,
array('class' => 'editing_makeunavailable', 'data-action' => 'hide', 'data-sectionreturn' => $sr)
);
@@ -1982,7 +1982,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
$action = $sectionvisible ? 'stealth' : 'show';
$actions[$action] = new action_menu_link_secondary(
new moodle_url($baseurl, array($action => $mod->id)),
new pix_icon('t/block', $str->makeavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/block', '', 'moodle', array('class' => 'iconsmall')),
$str->makeavailable,
array('class' => 'editing_makeavailable', 'data-action' => $action, 'data-sectionreturn' => $sr)
);
@@ -1995,7 +1995,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
course_allowed_module($mod->get_course(), $mod->modname)) {
$actions['duplicate'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('duplicate' => $mod->id)),
new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/copy', '', 'moodle', array('class' => 'iconsmall')),
$str->duplicate,
array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sectionreturn' => $sr)
);
@@ -2026,7 +2026,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
$actions[$actionname] = new action_menu_link_primary(
new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall')),
new pix_icon($groupimage, '', 'moodle', array('class' => 'iconsmall')),
$grouptitle,
array('class' => 'editing_'. $actionname, 'data-action' => $nextactionname,
'aria-live' => 'assertive', 'data-sectionreturn' => $sr)
@@ -2040,7 +2040,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
if (has_capability('moodle/role:assign', $modcontext)){
$actions['assign'] = new action_menu_link_secondary(
new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/assignroles', '', 'moodle', array('class' => 'iconsmall')),
$str->assign,
array('class' => 'editing_assign', 'data-action' => 'assignroles', 'data-sectionreturn' => $sr)
);
@@ -2050,7 +2050,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
if ($hasmanageactivities) {
$actions['delete'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('delete' => $mod->id)),
new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
new pix_icon('t/delete', '', 'moodle', array('class' => 'iconsmall')),
$str->delete,
array('class' => 'editing_delete', 'data-action' => 'delete', 'data-sectionreturn' => $sr)
);
+1 -1
View File
@@ -648,7 +648,7 @@ class core_course_renderer extends plugin_renderer_base {
// Display link itself.
$activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
'class' => 'iconlarge activityicon', 'alt' => ' ', 'role' => 'presentation')) .
'class' => 'iconlarge activityicon', 'alt' => '', 'role' => 'presentation', 'aria-hidden' => 'true')) .
html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
if ($mod->uservisible) {
$output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick));
+1 -1
View File
@@ -53,7 +53,7 @@ require_capability('moodle/course:request', $context);
// Set up the form.
$data = course_request::prepare();
$requestform = new course_request_form($url, compact('editoroptions'));
$requestform = new course_request_form($url);
$requestform->set_data($data);
$strtitle = get_string('courserequest');
+9 -10
View File
@@ -83,7 +83,7 @@ class behat_course extends behat_base {
// Select Miscellaneous category.
$this->i_click_on_category_in_the_management_interface(get_string('miscellaneous'));
$this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcoures'));
$this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcourses'));
// Click create new course.
$this->execute('behat_general::i_click_on_in_the',
@@ -304,7 +304,7 @@ class behat_course extends behat_base {
// Click on highlight topic link.
$this->execute('behat_general::i_click_on_in_the',
array(get_string('markthistopic'), "link", $this->escape($xpath), "xpath_element")
array(get_string('highlight'), "link", $this->escape($xpath), "xpath_element")
);
}
@@ -326,7 +326,7 @@ class behat_course extends behat_base {
// Click on un-highlight topic link.
$this->execute('behat_general::i_click_on_in_the',
array(get_string('markedthistopic'), "link", $this->escape($xpath), "xpath_element")
array(get_string('highlightoff'), "link", $this->escape($xpath), "xpath_element")
);
}
@@ -346,7 +346,7 @@ class behat_course extends behat_base {
$showlink->click();
if ($this->running_javascript()) {
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
$this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
$this->i_wait_until_section_is_available($sectionnumber);
}
}
@@ -380,7 +380,7 @@ class behat_course extends behat_base {
);
if ($this->running_javascript()) {
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
$this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
$this->i_wait_until_section_is_available($sectionnumber);
}
}
@@ -444,7 +444,7 @@ class behat_course extends behat_base {
$xpath = $this->section_exists($sectionnumber);
// The important checking, we can not check the img.
$this->execute('behat_general::should_exist_in_the', ['This topic is highlighted as the current topic', 'icon', $xpath, 'xpath_element']);
$this->execute('behat_general::should_exist_in_the', ['Remove highlight', 'link', $xpath, 'xpath_element']);
}
/**
@@ -775,11 +775,10 @@ class behat_course extends behat_base {
// Ensure the destination is valid.
$sectionxpath = $this->section_exists($sectionnumber);
$activitynode = $this->get_activity_element('Move', 'icon', $activityname);
// JS enabled.
if ($this->running_javascript()) {
$activitynode = $this->get_activity_element('Move', 'icon', $activityname);
$destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]";
$this->execute("behat_general::i_drag_and_i_drop_it_in",
@@ -1165,9 +1164,9 @@ class behat_course extends behat_base {
// Checking the show button alt text and show icon.
$showtext = get_string('showfromothers', $courseformat);
$linkxpath = $xpath . "/descendant::a[@title=" . behat_context_helper::escape($showtext) . "]";
$linkxpath = $xpath . "//a[*[contains(text(), " . behat_context_helper::escape($showtext) . ")]]";
$exception = new ElementNotFoundException($this->getSession(), 'Show section link ');
$exception = new ElementNotFoundException($this->getSession(), 'Show section link');
// Returing the link so both Non-JS and JS browsers can interact with it.
return $this->find('xpath', $linkxpath, $exception);
+37 -1
View File
@@ -3426,7 +3426,9 @@ class core_course_courselib_testcase extends advanced_testcase {
}
/**
* Test reset_course_userdata() with reset_roles_overrides enabled.
* Test reset_course_userdata()
* - with reset_roles_overrides enabled
* - with selective role unenrolments
*/
public function test_course_roles_reset() {
global $DB;
@@ -3441,6 +3443,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
$generator->enrol_user($user->id, $course->id, $roleid);
// Test case with reset_roles_overrides enabled.
// Override course so it does NOT allow students 'mod/forum:viewdiscussion'.
$coursecontext = context_course::instance($course->id);
assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id);
@@ -3456,6 +3459,39 @@ class core_course_courselib_testcase extends advanced_testcase {
// Check new expected capabilities - override at the course level should be reset.
$this->assertTrue(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
// Test case with selective role unenrolments.
$roles = array();
$roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
$roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST);
// We enrol a user with student and teacher roles.
$generator->enrol_user($user->id, $course->id, $roles['student']);
$generator->enrol_user($user->id, $course->id, $roles['teacher']);
// When we reset only student role, we expect to keep teacher role.
$resetdata = new stdClass();
$resetdata->id = $course->id;
$resetdata->unenrol_users = array($roles['student']);
reset_course_userdata($resetdata);
$usersroles = enrol_get_course_users_roles($course->id);
$this->assertArrayHasKey($user->id, $usersroles);
$this->assertArrayHasKey($roles['teacher'], $usersroles[$user->id]);
$this->assertArrayNotHasKey($roles['student'], $usersroles[$user->id]);
$this->assertCount(1, $usersroles[$user->id]);
// We reenrol user as student.
$generator->enrol_user($user->id, $course->id, $roles['student']);
// When we reset student and teacher roles, we expect no roles left.
$resetdata = new stdClass();
$resetdata->id = $course->id;
$resetdata->unenrol_users = array($roles['student'], $roles['teacher']);
reset_course_userdata($resetdata);
$usersroles = enrol_get_course_users_roles($course->id);
$this->assertEmpty($usersroles);
}
public function test_course_check_module_updates_since() {
+7
View File
@@ -98,6 +98,13 @@ class enrol_database_testcase extends advanced_testcase {
case 'mssql':
set_config('dbtype', 'mssqlnative', 'enrol_database');
set_config('dbsybasequoting', '1', 'enrol_database');
// The native sqlsrv driver uses a comma as separator between host and port.
$dbhost = $CFG->dbhost;
if (!empty($dboptions['dbport'])) {
$dbhost .= ',' . $dboptions['dbport'];
}
set_config('dbhost', $dbhost, 'enrol_database');
break;
default:
+52
View File
@@ -1028,4 +1028,56 @@ class core_enrollib_testcase extends advanced_testcase {
$this->assertEquals($expectedcourses, $actual);
}
/**
* Test enrol_get_course_users_roles function.
*
* @return void
*/
public function test_enrol_get_course_users_roles() {
global $DB;
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
$roles = array();
$roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
$roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST);
$manual = enrol_get_plugin('manual');
$this->assertNotEmpty($manual);
$enrol = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'), '*', MUST_EXIST);
// Test without enrolments.
$this->assertEmpty(enrol_get_course_users_roles($course->id));
// Test with 1 user, 1 role.
$manual->enrol_user($enrol, $user1->id, $roles['student']);
$return = enrol_get_course_users_roles($course->id);
$this->assertArrayHasKey($user1->id, $return);
$this->assertArrayHasKey($roles['student'], $return[$user1->id]);
$this->assertArrayNotHasKey($roles['teacher'], $return[$user1->id]);
// Test with 1 user, 2 role.
$manual->enrol_user($enrol, $user1->id, $roles['teacher']);
$return = enrol_get_course_users_roles($course->id);
$this->assertArrayHasKey($user1->id, $return);
$this->assertArrayHasKey($roles['student'], $return[$user1->id]);
$this->assertArrayHasKey($roles['teacher'], $return[$user1->id]);
// Test with another user, 1 role.
$manual->enrol_user($enrol, $user2->id, $roles['student']);
$return = enrol_get_course_users_roles($course->id);
$this->assertArrayHasKey($user1->id, $return);
$this->assertArrayHasKey($roles['student'], $return[$user1->id]);
$this->assertArrayHasKey($roles['teacher'], $return[$user1->id]);
$this->assertArrayHasKey($user2->id, $return);
$this->assertArrayHasKey($roles['student'], $return[$user2->id]);
$this->assertArrayNotHasKey($roles['teacher'], $return[$user2->id]);
}
}
@@ -58,7 +58,7 @@ trait gradingform_legacy_polyfill {
* This method is used to export any user data this sub-plugin has using the object to get the context and userid.
*
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
* @todo MDL-63137 remove this method.
* @todo MDL-63167 remove this method.
*
* @param context $context Context owner of the data.
* @param stdClass $definition Grading definition entry to export.
@@ -75,7 +75,7 @@ trait gradingform_legacy_polyfill {
* Any call to this method should delete all user data for the context defined.
*
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
* @todo MDL-63137 remove this method.
* @todo MDL-63167 remove this method.
*
* @param context $context Context owner of the data.
*/
@@ -88,7 +88,7 @@ trait gradingform_legacy_polyfill {
* A call to this method should delete user data (where practicle) from the userid and context.
*
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
* @todo MDL-63137 remove this method.
* @todo MDL-63167 remove this method.
*
* @param int $userid The user whose information is to be deleted.
* @param context $context Context owner of the data.
+4
View File
@@ -237,3 +237,7 @@
margin: 0;
width: auto;
}
.gradingform_guide_comment_chooser {
max-height: 80vh;
overflow-y: auto;
}
@@ -174,7 +174,7 @@ class test_legacy_polyfill_gradingform_provider implements
* This method is used to export any user data this sub-plugin has using the object to get the context and userid.
*
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
* @todo MDL-63137 remove this method.
* @todo MDL-63167 remove this method.
*
* @param context $context Context owner of the data.
* @param stdClass $definition Grading definition entry to export.
@@ -190,7 +190,7 @@ class test_legacy_polyfill_gradingform_provider implements
* Any call to this method should delete all user data for the context defined.
*
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
* @todo MDL-63137 remove this method.
* @todo MDL-63167 remove this method.
*
* @param context $context Context owner of the data.
*/
@@ -202,7 +202,7 @@ class test_legacy_polyfill_gradingform_provider implements
* A call to this method should delete user data (where practicle) from the userid and context.
*
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
* @todo MDL-63137 remove this method.
* @todo MDL-63167 remove this method.
*
* @param int $userid The user whose information is to be deleted.
* @param context $context Context owner of the data.
+24 -6
View File
@@ -221,20 +221,38 @@ class gradeimport_csv_load_data {
protected function check_user_exists($value, $userfields) {
global $DB;
$usercheckproblem = false;
$user = null;
$errorkey = false;
// The user may use the incorrect field to match the user. This could result in an exception.
try {
$user = $DB->get_record('user', array($userfields['field'] => $value));
} catch (Exception $e) {
$usercheckproblem = true;
$field = $userfields['field'];
// Fields that can be queried in a case-insensitive manner.
$caseinsensitivefields = [
'email',
'username',
];
// Build query predicate.
if (in_array($field, $caseinsensitivefields)) {
// Case-insensitive.
$select = $DB->sql_equal($field, ':' . $field, false);
} else {
// Exact-value.
$select = "{$field} = :{$field}";
}
// Make sure the record exists and that there's only one matching record found.
$user = $DB->get_record_select('user', $select, array($userfields['field'] => $value), '*', MUST_EXIST);
} catch (dml_missing_record_exception $missingex) {
$errorkey = 'usermappingerror';
} catch (dml_multiple_records_exception $multiex) {
$errorkey = 'usermappingerrormultipleusersfound';
}
// Field may be fine, but no records were returned.
if (!$user || $usercheckproblem) {
if ($errorkey) {
$usermappingerrorobj = new stdClass();
$usermappingerrorobj->field = $userfields['label'];
$usermappingerrorobj->value = $value;
$this->cleanup_import(get_string('usermappingerror', 'grades', $usermappingerrorobj));
$this->cleanup_import(get_string($errorkey, 'grades', $usermappingerrorobj));
unset($usermappingerrorobj);
return null;
}
+105 -40
View File
@@ -243,57 +243,122 @@ Bobby,Bunce,,"Moodle HQ","Rock on!",student5@example.com,75.00,,75.00,{exportdat
}
/**
* Check that the user matches a user in the system.
* Data provider for \gradeimport_csv_load_data_testcase::test_check_user_exists().
*
* @return array
*/
public function test_check_user_exists() {
public function check_user_exists_provider() {
return [
'Fetch by email' => [
'email', 's1@example.com', true
],
'Fetch by email, different case' => [
'email', 'S1@EXAMPLE.COM', true
],
'Fetch data using a non-existent email' => [
'email', 's2@example.com', false
],
'Multiple accounts with the same email' => [
'email', 's1@example.com', false, 1
],
'Fetch data using a valid user ID' => [
'id', true, true
],
'Fetch data using a non-existent user ID' => [
'id', false, false
],
'Fetch data using a valid username' => [
'username', 's1', true
],
'Fetch data using a valid username, different case' => [
'username', 'S1', true
],
'Fetch data using an invalid username' => [
'username', 's2', false
],
'Fetch data using a valid ID Number' => [
'idnumber', 's1', true
],
'Fetch data using an invalid ID Number' => [
'idnumber', 's2', false
],
];
}
/**
* Check that the user matches a user in the system.
*
* @dataProvider check_user_exists_provider
* @param string $field The field to use for the query.
* @param string|boolean $value The field value. When fetching by ID, set true to fetch valid user ID, false otherwise.
* @param boolean $successexpected Whether we expect for a user to be found or not.
* @param int $allowaccountssameemail Value for $CFG->allowaccountssameemail
*/
public function test_check_user_exists($field, $value, $successexpected, $allowaccountssameemail = 0) {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
// Need to add one of the users into the system.
$user = new stdClass();
$user->firstname = 'Anne';
$user->lastname = 'Able';
$user->email = 'student7@example.com';
$userdetail = $this->getDataGenerator()->create_user($user);
$user = $generator->create_user([
'firstname' => 'Anne',
'lastname' => 'Able',
'email' => 's1@example.com',
'idnumber' => 's1',
'username' => 's1',
]);
if ($allowaccountssameemail) {
// Create another user with the same email address.
$generator->create_user(['email' => 's1@example.com']);
}
// Since the data provider can't know what user ID to use, do a special handling for ID field tests.
if ($field === 'id') {
if ($value) {
// Test for fetching data using a valid user ID. Use the generated user's ID.
$value = $user->id;
} else {
// Test for fetching data using a non-existent user ID.
$value = $user->id + 1;
}
}
$userfields = [
'field' => $field,
'label' => 'Field label: ' . $field
];
$testobject = new phpunit_gradeimport_csv_load_data();
$testarray = $this->csv_load($this->oktext);
// Check whether the user exists. If so, then the user id is returned. Otherwise, it returns null.
$userid = $testobject->test_check_user_exists($value, $userfields);
$userfields = array('field' => 'email', 'label' => 'Email address');
// If the user exists then the user id is returned.
$userid = $testobject->test_check_user_exists($testarray[0][5] , $userfields);
// Check that the user id returned matches with the user that we created.
$this->assertEquals($userid, $userdetail->id);
if ($successexpected) {
// Check that the user id returned matches with the user that we created.
$this->assertEquals($user->id, $userid);
// Check for failure.
// Try for an exception.
$userfields = array('field' => 'id', 'label' => 'userid');
$userid = $testobject->test_check_user_exists($testarray[0][0], $userfields);
// Check that the userid is null.
$this->assertNull($userid);
// Check that there are no errors.
$this->assertEmpty($testobject->get_gradebookerrors());
// Expected error message.
$mappingobject = new stdClass();
$mappingobject->field = $userfields['label'];
$mappingobject->value = $testarray[0][0];
$expectederrormessage = get_string('usermappingerror', 'grades', $mappingobject);
// Check that expected error message and actual message match.
$gradebookerrors = $testobject->get_gradebookerrors();
$this->assertEquals($expectederrormessage, $gradebookerrors[0]);
} else {
// Check that the userid is null.
$this->assertNull($userid);
// The field mapping is correct, but the student does not exist.
$userid = $testobject->test_check_user_exists($testarray[1][5], $userfields);
// Check that the userid is null.
$this->assertNull($userid);
// Check that expected error message and actual message match.
$gradebookerrors = $testobject->get_gradebookerrors();
$mappingobject = (object)[
'field' => $userfields['label'],
'value' => $value,
];
if ($allowaccountssameemail) {
$expectederrormessage = get_string('usermappingerrormultipleusersfound', 'grades', $mappingobject);
} else {
$expectederrormessage = get_string('usermappingerror', 'grades', $mappingobject);
}
// Expected error message.
$mappingobject = new stdClass();
$mappingobject->field = $userfields['label'];
$mappingobject->value = $testarray[1][5];
$expectederrormessage = get_string('usermappingerror', 'grades', $mappingobject);
// Check that expected error message and actual message match.
$gradebookerrors = $testobject->get_gradebookerrors();
// This is the second error in the array of gradebook errors.
$this->assertEquals($expectederrormessage, $gradebookerrors[1]);
$this->assertEquals($expectederrormessage, $gradebookerrors[0]);
}
}
/**
@@ -22,7 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['direct:view'] = 'Import grades from CSV';
$string['direct:view'] = 'Import grades from spreadsheet';
$string['pluginname'] = 'Paste from spreadsheet';
$string['privacy:metadata'] = 'The import grades from copied spreadsheed plugin does not store any personal data.';
$string['userdata'] = 'Help copying data into this form.';
@@ -61,7 +61,7 @@ Feature: A teacher checks the grade history report in a course
# Test filtering by student.
And I press "Select users"
And I set the field with xpath "//form/input[@class='usp-search-field']" to "Student 1"
And I press "Search"
And I click on "Search" "button" in the "//div[@class='usp-search']" "xpath_element"
And I set the field with xpath "//div[@class='usp-checkbox']/input" to "1"
And I press "Finish selecting users"
And I press "Submit"

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