Compare commits

..

286 Commits

Author SHA1 Message Date
Jun Pataleta b21d86f807 Moodle release 3.8.5 2020-09-11 10:41:55 +08:00
Sara Arjona 472d293a16 weekly release 3.8.4+ 2020-09-10 08:52:05 +02:00
Sara Arjona 168fa1dda1 Merge branch 'MDL-69466-3.8' of https://github.com/JBThong/moodle into MOODLE_38_STABLE 2020-09-09 11:39:42 +02:00
Thong Bui a8e70bad23 MDL-69466 Autocomplete widget: Improve keyboard accessibility 2020-09-09 13:52:53 +07:00
Eloy Lafuente (stronk7) 7ed55774f1 Merge branch 'MDL-69116-38' of git://github.com/rezaies/moodle into MOODLE_38_STABLE 2020-09-08 23:16:52 +02:00
Sara Arjona 784156217a Merge branch 'MDL-69525-38-1' of git://github.com/mihailges/moodle into MOODLE_38_STABLE 2020-09-08 14:22:16 +02:00
Michael Hawkins 0425db5a80 MDL-69340 tag: Correct the filter input HTML in the tag manager 2020-09-08 08:48:24 +02:00
Mihail Geshoski 6e1451fb39 MDL-69048 book: Sanitize previous chapter value in add new chapter page 2020-09-08 08:48:24 +02:00
Andrew Nicols 8c274e82f4 MDL-68974 calendar: Use page context for block check 2020-09-08 08:48:24 +02:00
Andrew Nicols 4a488b9a5c MDL-68974 blocks: Check permission before generating content 2020-09-08 08:48:24 +02:00
Mihail Geshoski 11e49d6676 MDL-65115 repository: Prevent unzipping if it will exceed allowed quota 2020-09-08 08:48:24 +02:00
Mihail Geshoski 6f4e2cb799 MDL-65115 core_files: Add get_total_content_size() in stored_file class
New function get_total_content_size() is introduced in the stored_file
class. The puprose of this function is to calculate and return the
total size (in bytes) of the content of an archive file.
2020-09-08 08:48:24 +02:00
Andrew Nicols 7790b6727a MDL-68974 admin: Unit tests for context parent/child test 2020-09-08 08:48:24 +02:00
Andrew Nicols 20f7e23af4 MDL-68974 admin: Unit tests for loginas 2020-09-08 08:48:24 +02:00
Andrew Nicols 52b6d73c03 MDL-68974 admin: Prevent login as outside of the desired context 2020-09-08 08:48:24 +02:00
Sara Arjona 78eacf034a Merge branch 'MDL-68344-3.8' of https://github.com/JBThong/moodle into MOODLE_38_STABLE 2020-09-08 08:45:28 +02:00
Jun Pataleta 67e3dbab7f Merge branch 'MDL-69492-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-09-08 12:56:09 +08:00
Eloy Lafuente (stronk7) ed3482f9cc Merge branch 'MDL-69262-38-2' of git://github.com/junpataleta/moodle into MOODLE_38_STABLE 2020-09-07 23:43:17 +02:00
Sara Arjona 3852238df4 Merge branch 'MDL-67468-MOODLE_38_STABLE' of https://github.com/tuanngocnguyen/moodle into MOODLE_38_STABLE 2020-09-07 13:20:17 +02:00
Paul Holden 1c51eaa50c MDL-69492 auth_cas: normalise member distinguished name config. 2020-09-04 08:39:11 +01:00
Paul Holden c329d99d8a MDL-69492 auth_ldap: normalise member distinguished name config. 2020-09-04 08:39:11 +01:00
Nathan Nguyen f48f213e40 MDL-67468 filter_activitynames: use MUC 2020-09-04 15:41:36 +10:00
Thong Bui c2686daae4 MDL-68344 filepicker: File Picker focus lost on upload 2020-09-04 09:50:30 +07:00
Sara Arjona 28a155e712 weekly release 3.8.4+ 2020-09-03 17:09:22 +02:00
Sara Arjona f90459df6f NOBUG: Fixed SVG browser compatibility 2020-09-03 17:09:20 +02:00
Andrew Nicols bf0778595f Merge branch 'MDL-69391-38' of git://github.com/junpataleta/moodle into MOODLE_38_STABLE 2020-09-03 11:49:48 +08:00
Andrew Nicols 3a257a2808 Merge branch 'MDL-69360-38' of git://github.com/NoelDeMartin/moodle into MOODLE_38_STABLE 2020-09-03 10:52:27 +08:00
Andrew Nicols 45f5453b2a Merge branch 'MDL-69246_38' of https://github.com/timhunt/moodle into MOODLE_38_STABLE 2020-09-03 10:40:33 +08:00
Andrew Nicols b74d8568eb Merge branch 'MDL-55340-38' of git://github.com/sarjona/moodle into MOODLE_38_STABLE 2020-09-03 10:14:46 +08:00
Andrew Nicols 9f770c1be6 Merge branch 'MDL-69069-38' of git://github.com/bmbrands/moodle into MOODLE_38_STABLE 2020-09-03 09:37:37 +08:00
Eloy Lafuente (stronk7) 049bb1d7a7 Merge branch 'MDL-69608-38' of https://github.com/marinaglancy/moodle into MOODLE_38_STABLE 2020-09-03 00:03:51 +02:00
Sara Arjona 687047148f Merge branch 'MDL-68680-MOODLE_38_STABLE' of https://github.com/tuanngocnguyen/moodle into MOODLE_38_STABLE 2020-09-02 17:16:21 +02:00
Marina Glancy ab48f37f68 MDL-69608 core_form: frozen forms do not have form tag 2020-09-02 11:27:29 +02:00
Noel De Martin 6180264ae8 MDL-69360 behat: Fix js ready checks for non-moodle sites 2020-09-02 10:21:09 +02:00
Eloy Lafuente (stronk7) b7905dd4e0 Merge branch 'wip_MDL-69491_m38' of git://github.com/gjb2048/moodle into MOODLE_38_STABLE 2020-09-02 00:27:50 +02:00
Sara Arjona 8d470c5802 Merge branch 'MDL-64525-m38' of https://github.com/NeillM/moodle into MOODLE_38_STABLE 2020-09-01 14:20:44 +02:00
Mihail Geshoski 1d183e38b1 MDL-69525 core: Publish a single event when toggling slave checkboxes 2020-09-01 15:07:49 +08:00
Sara Arjona 80efaa9828 Merge branch 'MDL-51709-38' of git://github.com/lucaboesch/moodle into MOODLE_38_STABLE 2020-09-01 09:04:37 +02:00
Sara Arjona 25c47ba407 Merge branch 'MDL-69394-38' of git://github.com/junpataleta/moodle into MOODLE_38_STABLE 2020-09-01 06:45:38 +02:00
Tim Hunt 33f880fba7 MDL-69507 duration form field: should return an int number of seconds 2020-09-01 11:38:36 +08:00
Andrew Nicols 9e9a1db47a Merge branch 'MDL-69387-38-2' of git://github.com/junpataleta/moodle into MOODLE_38_STABLE 2020-09-01 11:00:04 +08:00
Andrew Nicols a711c69d8f Merge branch 'MDL-69505-38' of https://github.com/snake/moodle into MOODLE_38_STABLE 2020-09-01 07:30:40 +08:00
Andrew Nicols 2a98eedef0 Merge branch 'MDL-56595-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-09-01 07:30:40 +08:00
Luca Bösch dadccb9237 MDL-51709 gradebook: Name display in single view. 2020-08-31 18:26:01 +02:00
Shamim Rezaie 2ce0766cfc MDL-69116 qtype_multichoice: accessibility improvement
- Screen-readers should not see 'clear my choice' when it is not visible
- 'clear my choice' option should only become visible when a choice is
selected
2020-09-01 00:09:38 +10:00
Shamim Rezaie d456737d72 MDL-69116 qtype_mutichoice: Use label for the "clear my choice" link 2020-08-31 23:59:22 +10:00
Bas Brands cef948595b MDL-69069 theme_boost: color contrast for custom alerts 2020-08-31 08:47:25 +02:00
Jake Dallimore 24bf9c39d9 MDL-69505 core_form: fix js min/max date restriction in dateselector
This was using the yui2-calendar attribute, not the yui 3 attribute.
2020-08-31 08:11:59 +08:00
Gareth Barnard adca4fe505 MDL-69491 themes: No 'fontsize' setting in Boost causes unit conflict. 2020-08-28 14:08:27 +01:00
Marina Glancy a44ce9a024 MDL-55340 mod_feedback: export question labels
This was present in export in 3.0 but got lost during export
refactoring in 3.1
2020-08-28 12:25:24 +02:00
Andrew Nicols fde001145b Merge branch 'MDL-69112-38' of git://github.com/merrill-oakland/moodle into MOODLE_38_STABLE 2020-08-28 13:27:39 +08:00
Nathan Nguyen 85ffac56e8 MDL-68680 core_grades: check grade item before trigger delete event 2020-08-28 11:40:14 +10:00
Paul Holden 11b4e810fb MDL-56595 tool_usertours: set page params when configuring tours/steps. 2020-08-27 23:07:59 +01:00
Tim Hunt ab1545b20e MDL-69246 questions: allow for float issues when validating manual marks
As well as fixing the bug, I also rewrote the test to use
data providers, which should lead to more useful failure messages.

And, I moved the magic number we used as the float tolerence to
be a named constant.
2020-08-27 17:40:45 +01:00
Sara Arjona c3673a9be1 weekly release 3.8.4+ 2020-08-27 17:36:26 +02:00
Sara Arjona 9283ba7380 Merge branch 'install_38_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_38_STABLE 2020-08-27 17:36:24 +02:00
Jun Pataleta bb41dd7921 MDL-69262 core: Use core/action_menu_link in core/action_menu_trigger
* Introduce the "actionmenulinkclasses" block in the action_menu_link
template so other templates importing it can have the option to override
its classes.
* Have the action menu trigger template use the action menu link
template to reduce code duplication.
2020-08-27 13:23:24 +08:00
Jun Pataleta 8305c3ce9a MDL-69262 core: Make instance count variable as a static class variable
With the static function variable $instance, calls to
\action_menu_link::export_for_template() from its subclasses are stored
in different variables. This causes duplicate IDs when different
implementations of action menu links are rendered on the action menu
trigger/link template. (e.g. action_menu_link and
action_menu_link_secondary both rendered on the same page).
To make the incrementing uniform for the action_menu_link class and its
implementations, the $instance variable is moved out of the
export_to_template() method and is now made to a static class variable.
2020-08-27 13:17:45 +08:00
Andrew Nicols 1592b68f0b Merge branch 'MDL-69458-38' of git://github.com/mihailges/moodle into MOODLE_38_STABLE 2020-08-27 12:43:48 +08:00
Andrew Nicols faf7b144a1 Merge branch 'MDL-69501-38-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_38_STABLE 2020-08-27 11:31:35 +08:00
eWallah 08c359f366 MDL-67413 competencies: do nothing upon activity completion 2020-08-27 11:09:09 +08:00
Andrew Nicols b03196fe78 Merge branch 'MOODLE_38_MDL-69358' of https://github.com/golenkovm/moodle into MOODLE_38_STABLE 2020-08-27 10:58:17 +08:00
Justin Hunt ec92de9187 MDL-66810 mod_lti: Reinstate deprecated default permissions on iframe
microphone, webcam, camera, midi, encrypted-media, geolocation
2020-08-27 10:27:02 +08:00
AMOS bot c3fcedf205 Automatically generated installer lang files 2020-08-27 00:07:14 +00:00
Adrian Greeve d2dec45ec8 Merge branch 'MDL-69419-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-08-27 08:06:30 +08:00
Sara Arjona 84564c42c8 Merge branch 'MDL-69389-38' of git://github.com/junpataleta/moodle into MOODLE_38_STABLE 2020-08-26 18:03:13 +02:00
Sara Arjona 1084eba9a8 Merge branch 'MDL-69114-38' of git://github.com/bmbrands/moodle into MOODLE_38_STABLE 2020-08-26 16:29:01 +02:00
Jun Pataleta 6895074a62 MDL-69389 theme_classic: Make default body font colour darker
Links and normal text should have at least a colour contrast ratio
of 3:1. To achieve this, $body-color will be using the darker
shade of gray $gray-900 (#212529).
2020-08-26 11:07:41 +08:00
Jun Pataleta d79cd16ae7 MDL-69389 theme_boost: Make default body font colour darker
Links and normal text should have at least a colour contrast ratio
of 3:1. To achieve this, $body-color will be using the darker
shade of gray $gray-900 (#212529).
2020-08-26 11:07:41 +08:00
Jun Pataleta 9579c751ab MDL-69387 pix: Create PNG versions of the darkened completion icons
Thanks to Barbara Ramiro <barbara@moodle.com> for the help in
generating these PNG versions of the darkened completion icons!
2020-08-26 11:03:28 +08:00
Jun Pataleta 1cb1763216 MDL-69387 pix: Darken completion icons
Darkened the fills of completion icons in order to meet the required
minimum contrast ratio of 3:1.
* Changed gray fills (#999) to #949494.
* Changed blue fills (#76A1F0) to #6393ee
* Changed green fills (#9C3) to #79a128

Thanks to Barbara Ramiro <barbara@moodle.com> for the help in
updating the SVG icons!
2020-08-26 11:03:28 +08:00
Jun Pataleta 2fb006bfb1 MDL-69394 calendar: Fix event colour indicator contrast
* Darkened the link and clickable icon colours inside coloured events.
* Added borders around event colour indicators inside the month-view of
  the calendar for better contrast against the white background.
2020-08-26 11:01:17 +08:00
Andrew Nicols fbc937f4b4 MDL-69419 core: Task reset tests cannot use random test times
If a scheduled task which uses a 'R' field is picked, then the reset of
updated task times will not necessarily be correctly determined as the
randomisation is picked during reset.

This can lead to some random test failures.

Actively specifying a test which does not make use of the 'R' random
field time addresses this issue.
2020-08-26 09:58:49 +08:00
AMOS bot e30b6117f7 Automatically generated installer lang files 2020-08-26 00:07:33 +00:00
Jun Pataleta d0d16bd8fb MDL-69391 theme: Darken course selector text colour
Use $gray-600 for a darker text colour in the course select box.
This also makes the colour consistent with the view selector which
also uses $gray-600.
2020-08-25 14:48:40 +08:00
Jun Pataleta dc378c70e3 MDL-69391 theme: Darken navbar-light colour
Increase the opacity for the sass variable $navbar-light-color a
little bit in order to achieve an acceptable colour contrast against
the white background.
2020-08-25 14:47:51 +08:00
Neill Magill 15aa7e0b20 MDL-64525 comments: Should not be able to delete frozen comments
Before this change in a frozen context a user would be able to delete
their own comments.

After this change a user will only be able to delete their own comments
if they still have the capability to post new comments or the capability
to delete comments from any user.
2020-08-24 08:01:26 +01:00
Neill Magill 5192605a11 MDL-64525 comments: Tests for deleting frozen comments 2020-08-24 08:01:26 +01:00
Mihail Geshoski c6d2515534 MDL-69458 report_insights: Respect perpage value in pagination bar 2020-08-24 09:30:57 +08:00
Eloy Lafuente (stronk7) 4f3271c536 Merge branch 'MDL-69493-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-22 17:09:54 +02:00
AMOS bot b4025d8655 Automatically generated installer lang files 2020-08-22 00:07:58 +00:00
Eloy Lafuente (stronk7) a4ab0db4bd weekly release 3.8.4+ 2020-08-21 20:43:15 +02:00
Helen Foster 972f50ff90 MDL-69501 lang: Import fixed English strings (en_fix)
Significant string change: error:nobadges,core_badges - Additional info
explaining that site badges can only have other site badges as criteria
2020-08-21 13:58:56 +02:00
Jun Pataleta 5aeec826f8 Merge branch 'MDL-66818-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-08-21 11:32:57 +08:00
Andrew Nicols 94d2dcb8e1 MDL-66818 mod_forum: Use cap manager to check exportability 2020-08-21 08:17:27 +08:00
Andrew Nicols c8273f7d7f MDL-42434 chat: Disable stylelint important check 2020-08-21 07:38:06 +08:00
Sara Arjona affb1ae2ff MDL-42434 mod_chat: styles for chat themes to view help-icon properly
The styles for the bubble and the compact themes have been reviewed
because there weren't enough contrast for the help icon.
2020-08-20 23:44:00 +01:00
Sara Arjona fea72a6eb3 MDL-42434 mod_chat: display the whole help text
When testing this issue was raised that the help text was cut-off.
This patch should fix this issue changing the placement from
right/left to top.
2020-08-20 23:44:00 +01:00
Andrew Nicols 64d0d94aeb Merge branch 'MDL-69375_38' of https://github.com/Cvmcosta/moodle into MOODLE_38_STABLE 2020-08-20 11:51:24 +08:00
Adrian Greeve 9b4d87bcc2 Merge branch 'MDL-68964-38' of git://github.com/ferranrecio/moodle into MOODLE_38_STABLE 2020-08-20 11:43:33 +08:00
Adrian Greeve 18740e4938 Merge branch 'MDL-42434-38' of git://github.com/sarjona/moodle into MOODLE_38_STABLE 2020-08-20 11:25:35 +08:00
Adrian Greeve 261be42ab5 Merge branch 'MDL-68683-38-fix' of git://github.com/lameze/moodle into MOODLE_38_STABLE 2020-08-20 10:25:59 +08:00
Andrew Nicols 181bef2ed6 Merge branch 'MDL-69449-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-20 09:26:59 +08:00
Andrew Nicols 94bd3452e4 Merge branch 'MDL-68889-38' of git://github.com/bmbrands/moodle into MOODLE_38_STABLE 2020-08-20 09:20:01 +08:00
Simey Lameze fb9670a9b2 MDL-68683 competencies: fix query to delete stale files 2020-08-20 09:17:42 +08:00
Andrew Nicols 00cbbbc930 Merge branch 'MDL-67415_38' of https://github.com/stronk7/moodle into MOODLE_38_STABLE 2020-08-20 08:31:12 +08:00
Andrew Nicols bb31c5eb3e Merge branch 'MDL-66818-38' of git://github.com/lameze/moodle into MOODLE_38_STABLE 2020-08-20 08:17:17 +08:00
Simey Lameze 9d5a987a97 MDL-66818 mod_forum: remove unnecessary behat steps 2020-08-20 08:05:13 +08:00
gthomas2 a1b7b8b98f MDL-66818 mod_forum: hide portfolio export button based on caps 2020-08-20 08:05:13 +08:00
Andrew Nicols 1e157fb528 Merge branch 'MDL-66900-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-20 07:55:34 +08:00
Andrew Nicols b5c1e5379e Merge branch 'MDL-69271_38' of https://github.com/stronk7/moodle into MOODLE_38_STABLE 2020-08-20 07:36:44 +08:00
Andrew Nicols c78a136be4 Merge branch 'MDL-61215-38' of git://github.com/sarjona/moodle into MOODLE_38_STABLE 2020-08-20 07:17:37 +08:00
Andrew Nicols 978fff44ab Merge branch 'MDL-69401-38' of git://github.com/mihailges/moodle into MOODLE_38_STABLE 2020-08-20 07:03:54 +08:00
Victor Deniz Falcon 3b212136de Merge branch 'MDL-69297-38' of git://github.com/peterRd/moodle into MOODLE_38_STABLE 2020-08-19 23:48:56 +01:00
Eloy Lafuente (stronk7) 9ffab782f7 MDL-63375 workshop: apply specific IE10/11 fix
Credit goes to Matt Davidson (Syxton)
2020-08-19 17:54:18 +02:00
Peter Dias 30ccbb075c MDL-69297 assign: Check whether there is content for a submission
Confirm there is some content to be submitted for an assignment when processing it.
2020-08-19 15:48:33 +08:00
Adrian Greeve 793823bca8 Merge branch 'MDL-66665-38-POC2' of git://github.com/peterRd/moodle into MOODLE_38_STABLE 2020-08-19 14:29:58 +08:00
Peter Dias dd8b086494 MDL-66665 assign: Check whether a modified user could actually grade 2020-08-19 13:41:02 +08:00
Adrian Greeve 30b2b58c27 Merge branch 'MDL-68683-38' of git://github.com/lameze/moodle into MOODLE_38_STABLE 2020-08-19 12:58:27 +08:00
Adrian Greeve 8c5a8a25b5 Merge branch 'MDL-69475_38' of https://github.com/stronk7/moodle into MOODLE_38_STABLE 2020-08-19 10:46:01 +08:00
Andrew Nicols f6c9f72a76 Merge branch 'MDL-26401-38' of git://github.com/lameze/moodle into MOODLE_38_STABLE 2020-08-19 08:52:43 +08:00
Eloy Lafuente (stronk7) 7e1223958c Merge branch 'MDL-65847-auth_db_error_handling-38' of git://github.com/leonstr/moodle into MOODLE_38_STABLE 2020-08-19 00:35:48 +02:00
Eloy Lafuente (stronk7) 43362649e5 Merge branch 'MDL-63375-38' of https://github.com/syxton/moodle into MOODLE_38_STABLE 2020-08-19 00:13:24 +02:00
Eloy Lafuente (stronk7) bddf4eecf6 Merge branch 'MDL-68444-38' of git://github.com/rezaies/moodle into MOODLE_38_STABLE 2020-08-18 19:15:23 +02:00
Eloy Lafuente (stronk7) e881a516d0 MDL-69475 backup: Proper handling of backup::RELEASE versions
1) Remove any floatval() casting. They are breaking / killing
   .10 versions (converting them to .1). Since Moodle 2.0 all the
   backup::RELEASE have been 100% numerical values.
2) Use version_compare() always to compare backup::RELEASE values.
   They are always versions and the function is aware of versions
   > .9, able to clean/ignore alpha chars... and everything else.

Note that I've also changed a couple of cases in formats (topics and
weeks) that were correct, but just added the same comment and used
the same version_compare() comparison parameters style, so all uses
in core are consistent (and safe to be copied out there).
2020-08-18 16:25:55 +02:00
Eloy Lafuente (stronk7) 62c1a7b816 Merge branch 'MDL-69273-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-08-18 10:59:50 +02:00
Simey Lameze 0bca22b30f MDL-68683 competencies: step to delete orphaned evidence files 2020-08-18 16:07:08 +08:00
Simey Lameze 45d06fe9b6 MDL-68683 core: fix DML exception on upgrade step 2020-08-18 16:04:34 +08:00
Andrew Nicols 43930d5e8a MDL-69273 travis: Use exttests for https too 2020-08-18 07:50:48 +08:00
Andrew Nicols b226665e80 MDL-69273 travis: Move exttests check to after_script
The result of the final command `script` phase determines the build
result.

The cleanup belongs in the `after_script` phase.
2020-08-18 07:40:14 +08:00
Paul Holden edd91d16fa MDL-69493 cohort: remove escaping of return URL params. 2020-08-17 21:30:15 +01:00
Mihail Geshoski 5c3bdc9259 MDL-69401 book: Limit character length in chapter title 2020-08-17 09:04:36 +08:00
carlos 8dc60e1926 MDL-69375 mod_lti: Fix Names and Roles Link header format
- Changed line 469 of memberships.php to build Link header according to the Web Linking specification RFC8288.
2020-08-14 17:12:54 -03:00
Sara Arjona b91869f889 MDL-42434 mod_chat: add again the usingchat help icon
In Moodle 2.4 the help icon displayed in the chat window was
removed. It has been added to gui_ajax and gui_basic in order to help users.
2020-08-14 17:13:13 +02:00
Sara Arjona 25128d16aa MDL-61215 core_user: use the new optimised_image filetype group
For the user profile picture (instead of the web_image which includes
SVG files).
2020-08-14 17:06:51 +02:00
Sara Arjona 62cdf371df MDL-61215 core_badges: use the new optimised_image filetype group
For the badge picture (instead of the web_image which includes
SVG files).
2020-08-14 17:06:50 +02:00
Sara Arjona e6bf955210 MDL-61215 core_files: add new optimised_image group
Currently, PHP getimagesize method doesn't support SVG images.
As some features, such as badges, processs and optimise the images
before using them, a new filetype group has been created to exclude
SVG from there: optimised_image.
SVG can't be removed from web_image because then users won't be
able to add SVG images to their courses using labels, pages...
2020-08-14 17:06:50 +02:00
Eloy Lafuente (stronk7) 4ec7c29b30 MDL-69271 auth_ldap: Don't assume any ordering, just verify matches
Both ldap or the DB can return information in a non-consistent
ordering leading to events to be generated in different order.

And current tests are, right now, assuming a given order.

Note this is a rare random, but it's happening, so better
fix it, see the issue for some more details.

So we just do the tests ordering immune, verifying that all the
expected events have been triggered and done. Irrespectively of their order.
2020-08-14 13:40:13 +02:00
Andrew Nicols cf6cdd99d4 weekly release 3.8.4+ 2020-08-14 14:42:31 +08:00
Andrew Nicols 1ed9fa8f4a NOBUG: Fixed SVG browser compatibility 2020-08-14 14:42:29 +08:00
Andrew Nicols f520dbe674 Merge branch 'MDL-68666-38' of git://github.com/bmbrands/moodle into MOODLE_38_STABLE 2020-08-13 15:21:52 +08:00
Bas Brands b61392fa53 MDL-68666 core_pix: add missing fallback icons 2020-08-13 08:51:18 +02:00
Andrew Nicols c4fc929e3f Merge branch 'MDL-69253_38' of https://github.com/timhunt/moodle into MOODLE_38_STABLE 2020-08-13 11:53:06 +08:00
Andrew Nicols ae320424ef Merge branch 'MDL-60492-tagcloud_html-38' of https://github.com/leonstr/moodle into MOODLE_38_STABLE 2020-08-13 11:45:28 +08:00
Adrian Greeve 2d14596f5a Merge branch 'MDL-68423-38' of git://github.com/ferranrecio/moodle into MOODLE_38_STABLE 2020-08-13 11:06:30 +08:00
Andrew Nicols 11e171ffe5 Merge branch 'MDL-69421-38' of git://github.com/noeldemartin/moodle into MOODLE_38_STABLE 2020-08-13 09:36:14 +08:00
Andrew Nicols 3d5679803d Merge branch 'MDL-60260-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-13 07:55:52 +08:00
Eloy Lafuente (stronk7) bdea78ba61 MDL-67415 environment: change requirements
- mysql requirement up to 5.7
- postgres requirement up to 9.6
2020-08-13 01:30:26 +02:00
Eloy Lafuente (stronk7) c6fb7763bb MDL-67415 environment: 3.10 base information
This is a direct copy of the 3.9 branch to have
changes under control easier for next commits.
2020-08-13 01:30:26 +02:00
Eloy Lafuente (stronk7) 1d412ff1df Merge branch 'MDL-68860-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-13 01:08:43 +02:00
Eloy Lafuente (stronk7) 2e93f26f2f Merge branch 'MDL-68558-38-plugindepscheckdeadend' of git://github.com/mudrd8mz/moodle into MOODLE_38_STABLE 2020-08-13 00:40:25 +02:00
Eloy Lafuente (stronk7) 81dfa11ba8 Merge branch 'MDL-67394-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-13 00:29:24 +02:00
Eloy Lafuente (stronk7) 6a6caa019f Merge branch 'MDL-67002_38' of https://github.com/timhunt/moodle into MOODLE_38_STABLE 2020-08-13 00:26:20 +02:00
Ferran Recio c1b3ba3e07 MDL-68964 mod_chat: fix theme param 2020-08-12 17:31:42 +02:00
Bas Brands 5d412e18e7 MDL-68889 block_recentlyaccessedcourses: small viewport issues
- on small devices the recentlyaccessedcourses does not show any courses
2020-08-12 13:06:20 +02:00
Paul Holden b73bb7b9c4 MDL-66900 course: don't clean category description when editing. 2020-08-12 11:48:35 +01:00
Simey Lameze d4d744d544 MDL-26401 group: change import to use csv_import_reader class 2020-08-12 16:36:05 +08:00
Simey Lameze 4cdcf3a407 MDL-26401 group: add delimiter and encoding fields to import 2020-08-12 16:36:05 +08:00
Paul Holden a0283e7bb3 MDL-69449 block_timeline: use dayslimit context data in template.
Ensures the correct value is used when initially rendering the
course timeline.
2020-08-12 09:19:47 +01:00
Andrew Nicols 0ae11f6123 Merge branch 'MDL-68618-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-12 13:41:21 +08:00
Andrew Nicols c1efa9acf4 Merge branch 'MDL-69353-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-12 11:22:59 +08:00
Andrew Nicols 5ed2aef513 Merge branch 'MDL-68334-38' of git://github.com/lucaboesch/moodle into MOODLE_38_STABLE 2020-08-12 11:14:35 +08:00
Andrew Nicols 1466bef278 Merge branch 'MDL-69296-m38' of https://github.com/NeillM/moodle into MOODLE_38_STABLE 2020-08-12 10:31:43 +08:00
Mikhail Golenkov 7132e1a398 MDL-69358 backup: Do not clean up records for incomplete adhoc tasks.
Co-authored-by: Heena Agheda <heenaagheda@catalyst-au.net>
2020-08-12 12:09:30 +10:00
Andrew Nicols 4328b93712 Merge branch 'MDL-68731_38' of https://github.com/jonof/moodle into MOODLE_38_STABLE 2020-08-12 09:12:37 +08:00
Andrew Nicols bdfc738c6f Merge branch 'MDL-69168_recently-accessed-items-block-uses-stock-lti-icon' of https://github.com/stopfstedt/moodle into MOODLE_38_STABLE 2020-08-11 13:00:54 +08:00
Adrian Greeve 07d4b70470 Merge branch 'MDL-69204-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-11 08:46:22 +08:00
Paul Holden de58d4b0a1 MDL-68618 mod_forum: stop reverting idnumber when updating instance. 2020-08-10 09:11:20 +01:00
Paul Holden 349ebe7f45 MDL-68860 mod_lti: Oracle support for retrieving shared secrets.
Thanks to Mark van Hoek!
2020-08-10 09:06:33 +01:00
Adrian Greeve fe39303913 Merge branch 'MDL-60493-grader_span_tag-38' of https://github.com/leonstr/moodle into MOODLE_38_STABLE 2020-08-10 09:23:13 +08:00
Luca Bösch 7abc2da93b MDL-68334 user: Display name in footer as elsewhere. 2020-08-08 10:30:04 +02:00
Stefan Topfstedt bf9d8d79d5 MDL-69168 block_recentlyaccesseditems: apply custom icon
Overwrite the default mod icon with the configured icon url if
applicable.
2020-08-07 09:13:06 -07:00
Matt Davidson 155332f4aa MDL-63375 workshop: fix rubric grid view 2020-08-07 08:49:27 -04:00
Eloy Lafuente (stronk7) 24c68767e5 weekly release 3.8.4+ 2020-08-06 20:51:27 +02:00
Noel De Martin 5b38dd9e1e MDL-69421 behat: Revert app behat navigation to call session visit 2020-08-06 12:15:07 +02:00
Jun Pataleta 72f730972a Merge branch 'MDL-60501-h4_close_tag-38' of https://github.com/leonstr/moodle into MOODLE_38_STABLE 2020-08-06 12:11:07 +08:00
Jun Pataleta f5c7fcd181 Merge branch 'MDL-69111-38' of git://github.com/bmbrands/moodle into MOODLE_38_STABLE 2020-08-06 11:48:34 +08:00
Adrian Greeve 2d436520dc Merge branch 'MDL-40227-38-1' of git://github.com/peterRd/moodle into MOODLE_38_STABLE 2020-08-06 11:29:35 +08:00
Peter eb5d0d7d59 MDL-40227 mod_lesson: Use localised float formatting
* Leverage PARAM_LOCALISEDFLOAT
* Store all numbers with standard '.' dec formatting
* Show all numbers based on locale settings
* Behat test to cover different cases using numeric questions and modified locale setting
2020-08-06 10:44:48 +08:00
Adrian Greeve d158589158 Merge branch 'MOODLE_38_MDL-66626' of https://github.com/golenkovm/moodle into MOODLE_38_STABLE 2020-08-06 09:08:26 +08:00
Eloy Lafuente (stronk7) 268e3b893a Merge branch 'MDL-66670-38' of git://github.com/bmbrands/moodle into MOODLE_38_STABLE 2020-08-06 00:58:33 +02:00
Leon Stringer 54004f286e MDL-60492 core_tag: Fix title attribute
Attribute 'title' was specified inside a condition but closing `"` was
outside condition possibly resulting in <a href="..." class="..." ">.
Also moved </a> outside of condition to match <a>.
2020-08-05 12:56:02 +01:00
Adrian Greeve 12baca4b15 Merge branch 'MDL-68647-38-forumprivacysql' of git://github.com/mudrd8mz/moodle into MOODLE_38_STABLE 2020-08-05 14:23:28 +08:00
Adrian Greeve 08e86bd7b1 Merge branch 'MDL-60932-38' of git://github.com/aanabit/moodle into MOODLE_38_STABLE 2020-08-05 10:09:44 +08:00
Eloy Lafuente (stronk7) 36e03f1596 Merge branch 'MDL-67831-38' of git://github.com/bmbrands/moodle into MOODLE_38_STABLE 2020-08-05 00:48:31 +02:00
Tim Hunt b1341895b1 MDL-69253 mod_quiz: unit test for the last commit 2020-08-04 14:51:13 +01:00
Josh Ebarvia 6d3dca55f8 MDL-69253 mod_quiz: Fix timecreated is 0 when creating a quiz activity 2020-08-04 14:51:12 +01:00
Jun Pataleta d86741395e Merge branch 'MDL-60574_38' of https://github.com/t-schroeder/moodle into MOODLE_38_STABLE 2020-08-04 19:50:20 +08:00
Eloy Lafuente (stronk7) 0f6c21c836 Merge branch 'MDL-55299-38' of git://github.com/ferranrecio/moodle into MOODLE_38_STABLE 2020-08-04 12:34:15 +02:00
Tim Schroeder 8ae39ac69e MDL-60574 atto_undo: fixed redo erasing content
When you'd hit ctrl-y with nothing on the redo stack the entire
content of Atto would get deleted with no way of getting it back.
2020-08-04 09:25:23 +02:00
Leon Stringer 957baa0871 MDL-65847 auth_db: user_update() handle errors
The external database authentication plugin (auth_db) can update the
external database if mapped fields are set to Update external: On
update.  This change adds error handling in case this update fails.
2020-08-03 11:42:50 +01:00
Eloy Lafuente (stronk7) 678bcecc0d Merge branch 'MDL-62980-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-08-03 12:38:37 +02:00
Bas Brands 06f6b499eb MDL-69114 theme_boost: improve the color contrast for .*-info classes 2020-08-03 11:41:40 +02:00
Eric Merrill 88aa4e2071 MDL-69112 assign: Improve parsing of uploaded feedback names 2020-07-31 19:08:43 -04:00
Amaia Anabitarte 49403f979a MDL-60932 core_user: Fix docs link for participants page
Co-authored by: David Mudrák <david@moodle.com>
2020-07-31 10:30:43 +02:00
Neill Magill 28047fa3b1 MDL-69296 resource: Pressing cancel should always exit settings form
Before this change if a user used navigated to a resource activity
from another activity and then edited the settings it was likely
that using the cancel button would result in file downloading,
rather than being returned to their last page.

After this change the user will be back on the page they came from.
2020-07-31 07:58:16 +01:00
Paul Holden 98292864e8 MDL-67394 dml: improve thrown exception when Oracle can't parse query. 2020-07-30 16:23:02 +01:00
Victor Deniz Falcon 71f3a7fca9 weekly release 3.8.4+ 2020-07-30 12:54:08 +01:00
Victor Deniz Falcon 8a416d4e63 Merge branch 'install_38_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_38_STABLE 2020-07-30 12:54:02 +01:00
Paul Holden 94f4f39ab7 MDL-62980 tool_dataprivacy: show persistent errors inside form. 2020-07-30 08:23:26 +01:00
Eloy Lafuente (stronk7) ad30b41f91 Merge branch 'MDL-63252-38' of git://github.com/lameze/moodle into MOODLE_38_STABLE 2020-07-30 00:48:15 +02:00
Victor Deniz Falcon 7803827502 Merge branch 'MDL-66707-38' of git://github.com/rezaies/moodle into MOODLE_38_STABLE 2020-07-29 23:18:09 +01:00
Victor Deniz Falcon a6986b3e36 Merge branch 'MDL-68968-38' of git://github.com/lameze/moodle into MOODLE_38_STABLE 2020-07-29 22:03:16 +01:00
Paul Holden 73b7597a8c MDL-60260 course: validate external fields on create/update course.
Ensure course fullname/shortname fields are not empty.
2020-07-29 19:57:30 +01:00
Victor Deniz Falcon 9b54ddd465 Merge branch 'MDL-68706-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-07-29 19:04:18 +01:00
Victor Deniz Falcon 39dc97aef9 Merge branch 'MDL-69199-38' of git://github.com/aanabit/moodle into MOODLE_38_STABLE 2020-07-29 11:56:08 +01:00
Jonathon Fowler 8351210984 MDL-68731 forum: fix digests not correctly updating post read statuses 2020-07-29 15:41:21 +10:00
Jun Pataleta bfce37757f Merge branch 'MDL-69252-close_tag-38' of https://github.com/leonstr/moodle into MOODLE_38_STABLE 2020-07-29 11:17:32 +08:00
Eloy Lafuente (stronk7) b4d8be2042 Merge branch 'MDL-69319-38' of https://github.com/snake/moodle into MOODLE_38_STABLE 2020-07-29 00:20:53 +02:00
Leon Stringer 7d3b148e94 MDL-60493 mod_assign: Unused JS variables
Removed unused JavaScript variables fixing CI warning "ESLint error
[no-unused-vars]".
2020-07-28 12:05:49 +01:00
Leon Stringer 595e05d2a7 MDL-60493 mod_assign: Remove rogue </span>
In the grading interface (/mod/assign/view.php?id=2&action=grader)
there was a rogue </span> with no opening <span>.  Also changed
<span><form> to <div><form> in included template fixing CiBoT HTML
Validation error.
2020-07-28 12:05:12 +01:00
Jun Pataleta 4a776d2f67 Merge branch 'MDL-65453-38' of git://github.com/lameze/moodle into MOODLE_38_STABLE 2020-07-28 11:41:46 +08:00
Simey Lameze 975c1d4438 MDL-65453 core: fix behaviour of --skip-database option on install 2020-07-28 11:27:39 +08:00
Paul Holden 019e7dd26c MDL-69204 tool_dataprivacy: summary link only on current user profile. 2020-07-27 17:24:48 +01:00
Jun Pataleta 1b479562cd Merge branch 'MDL-66755-38' of https://github.com/SergioComeron/moodle into MOODLE_38_STABLE 2020-07-27 17:07:38 +08:00
AMOS bot d2b35aea26 Automatically generated installer lang files 2020-07-27 00:07:37 +00:00
AMOS bot 3678ad0949 Automatically generated installer lang files 2020-07-27 00:07:37 +00:00
Mikhail Golenkov 8c4893ffdc MDL-66626 assignfeedback_editpdf: Check page count for attempt.
Co-Authored-By: John Yao <johnyao@catalyst-au.net>
2020-07-27 09:51:54 +10:00
Mikhail Golenkov bab00d9b8c MDL-66626 assignfeedback_editpdf: Count files outside of foreach loop.
Co-Authored-By: John Yao <johnyao@catalyst-au.net>
2020-07-27 09:51:54 +10:00
AMOS bot 88723a6a1d Automatically generated installer lang files 2020-07-25 00:08:02 +00:00
Paul Holden 035c042f1e MDL-68706 customfield_textarea: allow field content to be cleared. 2020-07-24 13:44:58 +01:00
Paul Holden 6ed3f966b7 MDL-69353 admin: set page title when prompting for upgrade key. 2020-07-24 13:30:34 +01:00
Tim Hunt a7f70278c8 MDL-67002 quiz reports: fix sorting on Oracle in the responses report 2020-07-24 11:44:43 +01:00
Sergio Comerón ae1e1dfbd6 MDL-66755 forum: Subscription cancel when edit a message 2020-07-24 09:03:07 +02:00
Sam Marshall 5f417739f3 MDL-63252 tool_xmldb: convert default to float to avoid false positives 2020-07-24 07:54:02 +08:00
Simey Lameze 0d2f34b40d MDL-68968 core: fix wrong phpdoc on admin_externalpage 2020-07-24 07:46:33 +08:00
Amaia Anabitarte e0aa753fe1 MDL-69199 mod_quiz: Show timefinish in user Complete report 2020-07-23 18:54:25 +02:00
Victor Deniz Falcon f001dc447e weekly release 3.8.4+ 2020-07-23 16:10:30 +01:00
Victor Deniz Falcon cc80dec71e Merge branch 'install_38_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_38_STABLE 2020-07-23 16:10:25 +01:00
David Mudrák b89ebd255c MDL-68558 admin: Allow to go back to plugins check page on failed deps
Once the admin reaches the Plugin dependencies check with a failed
plugin dependency, the "Continue" button should take him/her to the
previous page where the dependency can be sorted out, or the plugin
installation cancelled. Also the "Cancel this installation" should work
on this page, too.

None of this was happening correctly because the page with failed
dependencies is loaded with "confirmplugincheck" set to 1 as a result
of confirming the previous step. We must explicitly override it back to
0 to navigate the user back to the previous step.
2020-07-23 17:05:03 +02:00
Bas Brands 0c773a6a16 MDL-67831 core_message: use space available for message index page
- the prevents the message reply box from being out of view when
a user has many conversations.
2020-07-23 11:34:52 +02:00
AMOS bot b45835b7b8 Automatically generated installer lang files 2020-07-23 00:07:56 +00:00
Victor Deniz Falcon 5aacc83bc4 Merge branch 'MDL-65819-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-07-22 19:01:36 +01:00
Ferran Recio ebb0e91226 MDL-55299 mod_feedback: fix quoted values donwload 2020-07-22 19:23:35 +02:00
Andrew Nicols 3ecf4dff52 MDL-69138 behat: Update session->visit() to use visit step 2020-07-22 16:48:37 +08:00
Andrew Nicols 3f2b9661d8 MDL-69138 behat: Add "I visit :localurl" step 2020-07-22 16:48:37 +08:00
Jun Pataleta 6b3d7a34dd Merge branch 'MDL-52052-38' of git://github.com/mihailges/moodle into MOODLE_38_STABLE 2020-07-22 13:46:59 +08:00
Jun Pataleta 2e38a7cc40 Merge branch 'MDL-68178-38' of git://github.com/mihailges/moodle into MOODLE_38_STABLE 2020-07-22 12:58:38 +08:00
Eloy Lafuente (stronk7) 95269fdb77 Merge branch 'MDL-69160-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-07-21 23:29:48 +02:00
Bas Brands 5a1745b254 MDL-69111 mod_forum: forum grading on small viewports
- changes to make the forum grading UI accessible in small
viewports to meet criterion 1.4.10 Reflow.
2020-07-21 17:42:55 +02:00
David Mudrák e271e28e78 MDL-68647 forum: Add index over posts privatereplyto field
This is to further improve the performance of searching of posts related
to the given user. Once both userid and privatereplyto are indexed, the
query planner can use the merged index to search for posts records.
2020-07-21 11:45:56 +02:00
David Mudrák 9cfcb4bf8a MDL-68647 forum: Improve the SQL performance in the privacy provider
The idea here is to replace the existing LEFT JOINs and
OR / IS NOT NULL conditions with INNER JOINs and ANDs. So we gather the
discussions data from all three areas in individual queries, make their
UNION. The GROUP BY was present before and maybe it is not even needed
any more (due to how UNION works) but it should not hurt to keep it.

This leads to significantly improved performance.
2020-07-21 11:45:56 +02:00
David Mudrák f25f8f0701 MDL-68647 forum: Add missing advanced grading library inclusion
The method get_grading_manager() is called in the file so make sure the
library is loaded. Without it, the forum's privacy provider unit test
ended with error.

Not directly related to the issue, but noticed while working on it.
2020-07-21 11:45:55 +02:00
Adrian Greeve 03670af6da Merge branch 'MDL-67657-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-07-21 14:49:14 +08:00
Ferran Recio 13f06f7a8e MDL-68423 message: avoid self-xss when send a message 2020-07-20 19:56:13 +02:00
Eloy Lafuente (stronk7) a080d4c0df Merge branch 'm38_MDL-67440' of https://github.com/danmarsden/moodle into MOODLE_38_STABLE 2020-07-20 17:08:31 +02:00
Leon Stringer ab7849c105 MDL-60501 themes: Fix closing <h4> in template
In site admin pages (/admin/search.php) <h4> elements were generated
with an incorrect second opening <h4> instead of a closing </h4>.
2020-07-20 15:20:42 +01:00
Bas Brands 756e148f31 MDL-66670 theme_boost: list bullet styling
Ensure lists with bullets are not cut off
2020-07-20 14:49:04 +02:00
Jake Dallimore 5a2ca41bba MDL-69319 mod_lti: style and docs changes in external tests 2020-07-20 15:04:10 +08:00
Jake Dallimore c9d1d8141f MDL-69319 mod_lti: replace try/catch with expectException in ext tests 2020-07-20 15:04:10 +08:00
Jake Dallimore 34616581c5 MDL-69319 mod_lti: remove unused vars from externallib_test 2020-07-20 15:04:10 +08:00
Jake Dallimore 33ec9e3a37 MDL-69319 mod_lti: move test data generation out of common setup 2020-07-20 15:04:10 +08:00
Jake Dallimore 147b984630 MDL-69319 mod_lti: call clean_returnvalue in external tests 2020-07-20 15:04:09 +08:00
Mihail Geshoski 4a2a779e12 MDL-68178 auth: CSRF protection for the resend confirmation email form
Credit to Chandra Kishor.
2020-07-20 12:31:06 +08:00
Adrian Greeve 6364b15e24 Merge branch 'MDL-69113_38' of https://github.com/jonof/moodle into MOODLE_38_STABLE 2020-07-20 10:37:49 +08:00
Mihail Geshoski b71744bb4d MDL-52052 gradeimport_csv: Do not skip rows with blank user fields 2020-07-20 08:55:51 +08:00
Eloy Lafuente (stronk7) 241eacaa7a Merge branch 'MDL-69278-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-07-18 11:56:15 +02:00
Paul Holden 37f3c318ea MDL-65819 message: send contact requests using recipient language. 2020-07-17 13:38:08 +01:00
Sara Arjona 4a6de87cae weekly release 3.8.4+ 2020-07-16 21:28:30 +02:00
Andrew Nicols a89aaec1af MDL-67657 behat: Open course with editing using URL
This is a huge performance improvement for behat.

The current set of steps loads the page
It then looks for the Classic/Clean version of the settings menu
If it fails to find it looks for the Boost Cog
Then it clicks the "Turn editing on" button

This can take a substantial period.

We do not actually need to run these steps as we are able to jump
straight to the URL. We already have access to the sesskey value
required to do this.

There is not loss in testing functionality because the actual testing of
the Turn editing functionality is tested in other places sufficiently.
2020-07-16 11:06:43 +08:00
Andrew Nicols 00df049b9a MDL-67657 behat: Convert existing uses of executeScript 2020-07-16 11:06:31 +08:00
Andrew Nicols a37313a6d1 MDL-67657 behat: Add wrapper for JS execution 2020-07-16 11:05:32 +08:00
Jun Pataleta b3c5e4f5d7 Merge branch 'MDL-69215-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-07-16 09:42:42 +08:00
Andrew Nicols 5eb38638bb MDL-69278 behat: Correct variable name 2020-07-16 08:30:13 +08:00
Andrew Nicols aeb85b66f2 MDL-69278 behat: Mark tests as failed when unable to start Session 2020-07-16 08:30:13 +08:00
Andrew Nicols 7ba288d1d0 MDL-69215 output: Correct use of icon_system::instance 2020-07-16 07:30:32 +08:00
Andrew Nicols d7e8f35e91 MDL-69215 output: Correct font awesome for themes 2020-07-16 07:30:32 +08:00
Eloy Lafuente (stronk7) db6f80a784 Merge branch 'MDL-69165-38' of https://github.com/HuongNV13/moodle into MOODLE_38_STABLE 2020-07-15 23:47:52 +02:00
Charles Fulton b20a875381 MDL-69074 admin: ensure consistent default for getremoteaddrconf 2020-07-15 19:06:06 +02:00
Sara Arjona f3d6fa182b Merge branch 'MDL-68605_38' of https://github.com/t-schroeder/moodle into MOODLE_38_STABLE 2020-07-15 18:52:57 +02:00
Sara Arjona 68a6b6db8d Merge branch 'MDL-68210-38' of https://github.com/snake/moodle into MOODLE_38_STABLE 2020-07-15 18:19:54 +02:00
Shamim Rezaie 9a66680ae1 MDL-66707 mod_forum: do not blindly mark discussion replies as read 2020-07-15 22:27:22 +10:00
Sara Arjona 8d90570067 Merge branch 'MDL-67671_38_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_38_STABLE 2020-07-15 14:11:55 +02:00
Sara Arjona a30f7f13ea Merge branch 'MDL-57678-38' of git://github.com/junpataleta/moodle into MOODLE_38_STABLE 2020-07-15 12:56:54 +02:00
Jun Pataleta d780b1866e MDL-57678 core_course: Remove padding of nbsp's on category names
Padding nested course categories by space to make them render in a tree
structure does not really make a lot of sense as they are already being
rendered as full paths. It's simpler to show them without the
indentation.
2020-07-15 18:28:10 +08:00
Sara Arjona 273ef0b4e3 Merge branch 'MDL-69068-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-07-15 12:20:39 +02:00
Andrew Nicols 8402724494 MDL-69068 behat: Allow singular, pivotted data generators 2020-07-15 15:27:31 +08:00
Andrew Nicols 83b5aea43c MDL-69068 behat: New step to support singular generators 2020-07-15 15:25:27 +08:00
Sara Arjona 51f4f8c612 Merge branch 'MDL-68715-38' of https://github.com/paulholden/moodle into MOODLE_38_STABLE 2020-07-14 14:32:10 +02:00
Shamim Rezaie 5d9fb70f70 MDL-68444 calendar: Improve how screenreaders say week titles
Also removed the scope attribute from the mini-calendar table
because according to https://www.w3.org/TR/WCAG20-TECHS/H63.html
it was not needed:
  Note 1: For simple tables that have the headers in the first row
  or column then it is sufficient to simply use the TH elements without
  scope.
2020-07-14 22:12:59 +10:00
Shamim Rezaie 0368c8e2a8 MDL-68444 calendar: Improve how screenreaders read day titles
Screenreaders now know if each day has any event or not
2020-07-14 22:12:59 +10:00
Paul Holden 9fea1cc6e4 MDL-68715 completion: fix form default value for course prerequisites.
Adds filtering on the completion criteria form to ensure we are only
selecting actual course instances of this type. Prior to this change
we would also get NULL values from unrelated criteria types.
2020-07-14 12:45:54 +01:00
Sara Arjona b09e569189 Merge branch 'MDL-69228-38' of git://github.com/andrewnicols/moodle into MOODLE_38_STABLE 2020-07-14 12:41:33 +02:00
Sara Arjona 9afc404947 Merge branch 'MDL-69115-38' of git://github.com/junpataleta/moodle into MOODLE_38_STABLE 2020-07-13 14:24:26 +02:00
Andrew Nicols 70d6af63ba MDL-69160 workshop: Ensure steps are chained 2020-07-13 14:22:31 +08:00
Andrew Nicols 4338982098 MDL-69160 behat: Add form field setter step 2020-07-13 14:22:31 +08:00
Leon Stringer 678ca3ded7 MDL-69252 templates: Invalid HTML
Invalid HTML generated when the page count exceeded the maximum number
of page links to display: <span>...</a>.  Also added example context.
2020-07-11 12:54:43 +01:00
Mark Johnson 2b36e5be2e MDL-67671 backup: Fix modname display on backup form 2020-07-09 10:13:08 +01:00
Huong Nguyen 82ddfd7ea6 MDL-69165 Questions: Incorrect id used in init_submit_button function 2020-07-08 11:48:56 +07:00
Andrew Nicols da91dd3ea0 MDL-69228 tool_mobile: Hide dependant settings 2020-07-08 11:50:18 +08:00
Jake Dallimore 121373ec8f MDL-68210 mod_lesson: fix random failure in override behat test 2020-07-08 09:10:20 +08:00
Jake Dallimore 8fb613ee9f MDL-68210 overrides: allow copy and delete of inactive overrides
This brings lesson and assign in line with the expected behaviour
already implemented in quiz. When an override is inactive, all controls
are shown.
2020-07-08 09:10:20 +08:00
Matt Clarkson 63b646acb1 MDL-67440 analytics: optimse delete's in clean up task. 2020-07-08 11:19:09 +12:00
Tim Schroeder 8c3e24b46c MDL-68605 mod_assign: return grp submissns from ws
Co-authored-by: Septatrix <24257556+Septatrix@users.noreply.github.com>
2020-07-07 12:56:28 +02:00
Jun Pataleta 7a54747c9a MDL-69115 course: More course management accessibility fixes
* Added aria-label for category single select element when on Courses
view mode.
* Fix correct labelling of custom control checkboxes. Instead of adding
aria-label in the label element, add an sr-only span within the label.
* Remove inappropriate 'group' role for the list of courses.
The list of courses is not being rendered as a tree structure
so adding a 'group' role to the list of courses is not really
necessary.
* Add aria-label for the search courses field.
* Add legend for the search courses fieldset.
* Use the primary colour for the category selection highlight instead
of the info colour.
* Changed the labels for the up/down icons using moveup/movedown
lang strings.
2020-07-01 14:45:37 +08:00
Jonathon Fowler cd8c136c70 MDL-69113 assign: save grader app workflow/marker filters correctly 2020-06-25 14:39:54 +10:00
301 changed files with 5639 additions and 1491 deletions
+10 -2
View File
@@ -174,7 +174,9 @@ before_script:
# Enable test external resources
sed -i \
-e "/require_once/i \\define('TEST_EXTERNAL_FILES_HTTP_URL', 'http://127.0.0.1:8080');" \
-e "/require_once/i \\define('TEST_EXTERNAL_FILES_HTTPS_URL', 'http://127.0.0.1:8080');" \
config.php ;
# Redis cache store tests
sed -i \
-e "/require_once/i \\define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');" \
@@ -254,8 +256,6 @@ script:
if [ "$TASK" = 'PHPUNIT' ];
then
vendor/bin/phpunit --fail-on-risky --disallow-test-output --verbose;
EXTTESTS_HITS=$(docker logs exttests 2>&1 | grep -Fv -e 'AH00558' -e '[pid 1]' | wc -l)
echo -e "\nTest local resources number of hits: ${EXTTESTS_HITS}.\n"
fi
- >
@@ -292,3 +292,11 @@ script:
exit 1 ;
fi
fi
after_script:
- >
if [ "$TASK" = 'PHPUNIT' ];
then
EXTTESTS_HITS=$(docker logs exttests 2>&1 | grep -Fv -e 'AH00558' -e '[pid 1]' | wc -l)
echo -e "\nTest local resources number of hits: ${EXTTESTS_HITS}.\n"
fi
+99 -90
View File
@@ -266,6 +266,7 @@ list($options, $unrecognized) = cli_get_params(
);
$interactive = empty($options['non-interactive']);
$skipdatabase = $options['skip-database'];
// set up language
$lang = clean_param($options['lang'], PARAM_SAFEDIR);
@@ -637,96 +638,100 @@ do {
}
} while ($hintdatabase !== '');
// ask for fullname
if ($interactive) {
cli_separator();
cli_heading(get_string('fullsitename', 'moodle'));
// If --skip-database option is provided, we do not need to ask for site fullname, shortname, adminuser, adminpass, adminemail.
// These fields will be requested during the database install part.
if (!$skipdatabase) {
// Ask for fullname.
if ($interactive) {
cli_separator();
cli_heading(get_string('fullsitename', 'moodle'));
if ($options['fullname'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['fullname']);
if ($options['fullname'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['fullname']);
} else {
$prompt = get_string('clitypevalue', 'admin');
}
do {
$options['fullname'] = cli_input($prompt, $options['fullname']);
} while (empty($options['fullname']));
} else {
$prompt = get_string('clitypevalue', 'admin');
if (empty($options['fullname'])) {
$a = (object)['option' => 'fullname', 'value' => $options['fullname']];
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
do {
$options['fullname'] = cli_input($prompt, $options['fullname']);
} while (empty($options['fullname']));
} else {
if (empty($options['fullname'])) {
$a = (object)array('option'=>'fullname', 'value'=>$options['fullname']);
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
// Ask for shortname.
if ($interactive) {
cli_separator();
cli_heading(get_string('shortsitename', 'moodle'));
// ask for shortname
if ($interactive) {
cli_separator();
cli_heading(get_string('shortsitename', 'moodle'));
if ($options['shortname'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['shortname']);
} else {
$prompt = get_string('clitypevalue', 'admin');
}
if ($options['shortname'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['shortname']);
do {
$options['shortname'] = cli_input($prompt, $options['shortname']);
} while (empty($options['shortname']));
} else {
$prompt = get_string('clitypevalue', 'admin');
if (empty($options['shortname'])) {
$a = (object)['option' => 'shortname', 'value' => $options['shortname']];
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
do {
$options['shortname'] = cli_input($prompt, $options['shortname']);
} while (empty($options['shortname']));
} else {
if (empty($options['shortname'])) {
$a = (object)array('option'=>'shortname', 'value'=>$options['shortname']);
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
// ask for admin user name
if ($interactive) {
cli_separator();
cli_heading(get_string('cliadminusername', 'install'));
if (!empty($options['adminuser'])) {
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminuser']);
// Ask for admin user name.
if ($interactive) {
cli_separator();
cli_heading(get_string('cliadminusername', 'install'));
if (!empty($options['adminuser'])) {
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminuser']);
} else {
$prompt = get_string('clitypevalue', 'admin');
}
do {
$options['adminuser'] = cli_input($prompt, $options['adminuser']);
} while (empty($options['adminuser']) or $options['adminuser'] === 'guest');
} else {
if ((empty($options['adminuser']) || $options['adminuser'] === 'guest')) {
$a = (object)['option' => 'adminuser', 'value' => $options['adminuser']];
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
// Ask for admin user password.
if ($interactive) {
cli_separator();
cli_heading(get_string('cliadminpassword', 'install'));
$prompt = get_string('clitypevalue', 'admin');
do {
$options['adminpass'] = cli_input($prompt);
} while (empty($options['adminpass']) or $options['adminpass'] === 'admin');
} else {
if ((empty($options['adminpass']) or $options['adminpass'] === 'admin')) {
$a = (object)['option' => 'adminpass', 'value' => $options['adminpass']];
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
do {
$options['adminuser'] = cli_input($prompt, $options['adminuser']);
} while (empty($options['adminuser']) or $options['adminuser'] === 'guest');
} else {
if (empty($options['adminuser']) or $options['adminuser'] === 'guest') {
$a = (object)array('option'=>'adminuser', 'value'=>$options['adminuser']);
// Ask for the admin email address.
if ($interactive) {
cli_separator();
cli_heading(get_string('cliadminemail', 'install'));
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminemail']);
$options['adminemail'] = cli_input($prompt, $options['adminemail']);
}
// Validate that the address provided was an e-mail address.
if (!empty($options['adminemail']) && !validate_email($options['adminemail'])) {
$a = (object)['option' => 'adminemail', 'value' => $options['adminemail']];
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
// ask for admin user password
if ($interactive) {
cli_separator();
cli_heading(get_string('cliadminpassword', 'install'));
$prompt = get_string('clitypevalue', 'admin');
do {
$options['adminpass'] = cli_input($prompt);
} while (empty($options['adminpass']) or $options['adminpass'] === 'admin');
} else {
if (empty($options['adminpass']) or $options['adminpass'] === 'admin') {
$a = (object)array('option'=>'adminpass', 'value'=>$options['adminpass']);
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
}
// Ask for the admin email address.
if ($interactive) {
cli_separator();
cli_heading(get_string('cliadminemail', 'install'));
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminemail']);
$options['adminemail'] = cli_input($prompt, $options['adminemail']);
}
// Validate that the address provided was an e-mail address.
if (!empty($options['adminemail']) && !validate_email($options['adminemail'])) {
$a = (object) array('option' => 'adminemail', 'value' => $options['adminemail']);
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
}
// Ask for the upgrade key.
if ($interactive) {
cli_separator();
@@ -745,22 +750,26 @@ if ($options['upgradekey'] !== '') {
$CFG->upgradekey = $options['upgradekey'];
}
if ($interactive) {
if (!$options['agree-license']) {
cli_separator();
cli_heading(get_string('copyrightnotice'));
echo "Moodle - Modular Object-Oriented Dynamic Learning Environment\n";
echo get_string('gpl3')."\n\n";
echo get_string('doyouagree')."\n";
$prompt = get_string('cliyesnoprompt', 'admin');
$input = cli_input($prompt, '', array(get_string('clianswerno', 'admin'), get_string('cliansweryes', 'admin')));
if ($input == get_string('clianswerno', 'admin')) {
exit(1);
// The user does not also need to pass agree-license when --skip-database is provided as the user will need to accept
// the license again in the database install part.
if (!$skipdatabase) {
if ($interactive) {
if (!$options['agree-license']) {
cli_separator();
cli_heading(get_string('copyrightnotice'));
echo "Moodle - Modular Object-Oriented Dynamic Learning Environment\n";
echo get_string('gpl3')."\n\n";
echo get_string('doyouagree')."\n";
$prompt = get_string('cliyesnoprompt', 'admin');
$input = cli_input($prompt, '', array(get_string('clianswerno', 'admin'), get_string('cliansweryes', 'admin')));
if ($input == get_string('clianswerno', 'admin')) {
exit(1);
}
}
} else {
if (!$options['agree-license'] && !$skipdatabase) {
cli_error(get_string('climustagreelicense', 'install'));
}
}
} else {
if (!$options['agree-license']) {
cli_error(get_string('climustagreelicense', 'install'));
}
}
@@ -809,7 +818,7 @@ if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
cli_error(get_string('pluginschecktodo', 'admin'));
}
if (!$options['skip-database']) {
if (!$skipdatabase) {
install_cli_database($options, $interactive);
// This needs to happen at the end to ensure it occurs after all caches
// have been purged for the last time.
+181
View File
@@ -3163,4 +3163,185 @@
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="3.10" requires="3.5">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="10.2.29" />
<VENDOR name="mysql" version="5.7" />
<VENDOR name="postgres" version="9.6" />
<VENDOR name="mssql" version="11.0" />
<VENDOR name="oracle" version="11.2" />
</DATABASE>
<PHP version="7.2.0" level="required">
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<ON_CHECK message="pcreunicodewarning" />
</FEEDBACK>
</PCREUNICODE>
<PHP_EXTENSIONS>
<PHP_EXTENSION name="iconv" level="required">
<FEEDBACK>
<ON_ERROR message="iconvrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="mbstring" level="required">
<FEEDBACK>
<ON_ERROR message="mbstringrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="curl" level="required">
<FEEDBACK>
<ON_ERROR message="curlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="openssl" level="required">
<FEEDBACK>
<ON_ERROR message="opensslrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="tokenizer" level="optional">
<FEEDBACK>
<ON_CHECK message="tokenizerrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlrpc" level="optional">
<FEEDBACK>
<ON_CHECK message="xmlrpcrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="soap" level="optional">
<FEEDBACK>
<ON_CHECK message="soaprecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="ctype" level="required">
<FEEDBACK>
<ON_ERROR message="ctyperequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zip" level="required">
<FEEDBACK>
<ON_ERROR message="ziprequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zlib" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="gd" level="required">
<FEEDBACK>
<ON_ERROR message="gdrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="simplexml" level="required">
<FEEDBACK>
<ON_ERROR message="simplexmlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="spl" level="required">
<FEEDBACK>
<ON_ERROR message="splrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="pcre" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="dom" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xml" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlreader" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="intl" level="required">
<FEEDBACK>
<ON_ERROR message="intlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="json" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="hash" level="required"/>
<PHP_EXTENSION name="fileinfo" level="required"/>
</PHP_EXTENSIONS>
<PHP_SETTINGS>
<PHP_SETTING name="memory_limit" value="96M" level="required">
<FEEDBACK>
<ON_ERROR message="settingmemorylimit" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="file_uploads" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="settingfileuploads" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="opcache.enable" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="opcacherecommended" />
</FEEDBACK>
</PHP_SETTING>
</PHP_SETTINGS>
<CUSTOM_CHECKS>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbstorageengine" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
<FEEDBACK>
<ON_ERROR message="quizattemptsupgradedmessage" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
<FEEDBACK>
<ON_CHECK message="slashargumentswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
<FEEDBACK>
<ON_CHECK message="unsupporteddbtablerowformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
<FEEDBACK>
<ON_CHECK message="unoconvwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
<FEEDBACK>
<ON_CHECK message="libcurlwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfileformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfilepertable" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddblargeprefix" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
<FEEDBACK>
<ON_CHECK message="ishttpswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
<FEEDBACK>
<ON_CHECK message="incompleteunicodesupport" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
<FEEDBACK>
<ON_CHECK message="sixtyfourbitswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+4 -2
View File
@@ -509,7 +509,8 @@ if (!$cache and $version > $CFG->version) { // upgrade
// Always verify plugin dependencies!
$failed = array();
if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url,
array('confirmplugincheck' => 0)));
die();
}
unset($failed);
@@ -701,7 +702,8 @@ if (!$cache and moodle_needs_upgrading()) {
$failed = array();
if (!$pluginman->all_plugins_ok($version, $failed)) {
$output = $PAGE->get_renderer('core', 'admin');
echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url,
array('confirmplugincheck' => 0)));
die();
}
unset($failed);
+2 -2
View File
@@ -1098,7 +1098,7 @@ class core_admin_renderer extends plugin_renderer_base {
if (!empty($installabortable[$plugin->component])) {
$status .= $this->output->single_button(
new moodle_url($this->page->url, array('abortinstall' => $plugin->component)),
new moodle_url($this->page->url, array('abortinstall' => $plugin->component, 'confirmplugincheck' => 0)),
get_string('cancelinstallone', 'core_plugin'),
'post',
array('class' => 'actionbutton cancelinstallone d-block mt-1')
@@ -1194,7 +1194,7 @@ class core_admin_renderer extends plugin_renderer_base {
if ($installabortable) {
$out .= $this->output->single_button(
new moodle_url($this->page->url, array('abortinstallx' => 1)),
new moodle_url($this->page->url, array('abortinstallx' => 1, 'confirmplugincheck' => 0)),
get_string('cancelinstallall', 'core_plugin', count($installabortable)),
'post',
array('class' => 'singlebutton cancelinstallall mr-1')
+1 -1
View File
@@ -99,7 +99,7 @@ $options = array(
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'REMOTE_ADDR');
$temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'),
new lang_string('configgetremoteaddrconf', 'admin'),
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP, $options));
GETREMOTEADDR_SKIP_DEFAULT, $options));
$temp->add(new admin_setting_configtext('reverseproxyignore', new lang_string('reverseproxyignore', 'admin'), new lang_string('configreverseproxyignore', 'admin'), ''));
$temp->add(new admin_setting_heading('webproxy', new lang_string('webproxy', 'admin'), new lang_string('webproxyinfo', 'admin')));
@@ -26,10 +26,11 @@ namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
use lang_string;
use core\persistent;
/**
* Class for loading/storing competencies from the DB.
* Class for loading/storing data requests from the DB.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -62,6 +63,7 @@ class data_request extends persistent {
],
'comments' => [
'type' => PARAM_TEXT,
'message' => new lang_string('errorinvalidrequestcomments', 'tool_dataprivacy'),
'default' => ''
],
'commentsformat' => [
@@ -75,7 +77,10 @@ class data_request extends persistent {
'default' => FORMAT_PLAIN
],
'userid' => [
'default' => 0,
'default' => function() {
global $USER;
return $USER->id;
},
'type' => PARAM_INT
],
'requestedby' => [
+2 -2
View File
@@ -67,8 +67,8 @@ if (!$manage && !\tool_dataprivacy\api::can_contact_dpo()) {
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), 0, \core\output\notification::NOTIFY_ERROR);
}
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage)]);
$mform->set_data(['type' => $requesttype]);
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage),
'persistent' => new \tool_dataprivacy\data_request(0, (object) ['type' => $requesttype])]);
// Data request cancelled.
if ($mform->is_cancelled()) {
@@ -23,6 +23,7 @@
*/
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
use tool_dataprivacy\local\helper;
defined('MOODLE_INTERNAL') || die();
@@ -36,7 +37,10 @@ require_once($CFG->libdir.'/formslib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
class tool_dataprivacy_data_request_form extends moodleform {
class tool_dataprivacy_data_request_form extends \core\form\persistent {
/** @var string Name of the persistent class. */
protected static $persistentclass = data_request::class;
/** @var bool Flag to indicate whether this form is being rendered for managing data requests or for regular requests. */
protected $manage = false;
@@ -96,14 +100,13 @@ class tool_dataprivacy_data_request_form extends moodleform {
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy')
];
$mform->addElement('select', 'type', get_string('requesttype', 'tool_dataprivacy'), $options);
$mform->setType('type', PARAM_INT);
$mform->addHelpButton('type', 'requesttype', 'tool_dataprivacy');
// Request comments text area.
$textareaoptions = ['cols' => 60, 'rows' => 10];
$mform->addElement('textarea', 'comments', get_string('requestcomments', 'tool_dataprivacy'), $textareaoptions);
$mform->setType('type', PARAM_ALPHANUM);
$mform->addHelpButton('comments', 'requestcomments', 'tool_dataprivacy');
// Action buttons.
@@ -129,34 +132,49 @@ class tool_dataprivacy_data_request_form extends moodleform {
}
}
/**
* Get the default data. Unset the default userid if managing data requests
*
* @return stdClass
*/
protected function get_default_data() {
$data = parent::get_default_data();
if ($this->manage) {
unset($data->userid);
}
return $data;
}
/**
* Form validation.
*
* @param array $data
* @param stdClass $data
* @param array $files
* @param array $errors
* @return array
* @throws coding_exception
* @throws dml_exception
*/
public function validation($data, $files) {
public function extra_validation($data, $files, array &$errors) {
global $USER;
$errors = [];
$validrequesttypes = [
api::DATAREQUEST_TYPE_EXPORT,
api::DATAREQUEST_TYPE_DELETE
];
if (!in_array($data['type'], $validrequesttypes)) {
if (!in_array($data->type, $validrequesttypes)) {
$errors['type'] = get_string('errorinvalidrequesttype', 'tool_dataprivacy');
}
if (api::has_ongoing_request($data['userid'], $data['type'])) {
$userid = $data->userid;
if (api::has_ongoing_request($userid, $data->type)) {
$errors['type'] = get_string('errorrequestalreadyexists', 'tool_dataprivacy');
}
// Check if current user can create data deletion request.
$userid = $data['userid'];
if ($data['type'] == api::DATAREQUEST_TYPE_DELETE) {
if ($data->type == api::DATAREQUEST_TYPE_DELETE) {
if ($userid == $USER->id) {
if (!api::can_create_data_deletion_request_for_self()) {
$errors['type'] = get_string('errorcannotrequestdeleteforself', 'tool_dataprivacy');
@@ -130,6 +130,7 @@ $string['effectiveretentionperioduser'] = '{$a} (since the last time the user ac
$string['emailsalutation'] = 'Dear {$a},';
$string['errorcannotrequestdeleteforself'] = 'You don\'t have permission to create deletion request for yourself.';
$string['errorcannotrequestdeleteforother'] = 'You don\'t have permission to create deletion request for this user.';
$string['errorinvalidrequestcomments'] = 'The comments field may contain plain text only.';
$string['errorinvalidrequestcreationmethod'] = 'Invalid request creation method!';
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
$string['errorinvalidrequesttype'] = 'Invalid request type!';
+1 -1
View File
@@ -95,7 +95,7 @@ function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser
$showsummary = true;
}
if ($showsummary) {
if ($showsummary && $iscurrentuser) {
$summaryurl = new moodle_url('/admin/tool/dataprivacy/summary.php');
$summarynode = new core_user\output\myprofile\node('privacyandpolicies', 'retentionsummary',
get_string('dataretentionsummary', 'tool_dataprivacy'), null, $summaryurl);
@@ -12,8 +12,8 @@ Feature: See the competencies for an activity on the course competencies page.
| Test-Comp1 | ID-FW1 |
| Test-Comp2 | ID-FW1 |
Given the following "courses" exist:
| shortname | fullname |
| C1 | Course 1 |
| shortname | fullname | enablecompletion |
| C1 | Course 1 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
@@ -21,9 +21,9 @@ Feature: See the competencies for an activity on the course competencies page.
| user | course | role |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| page | PageName1 | PageDesc1 | C1 | PAGE1 |
| page | PageName2 | PageDesc2 | C1 | PAGE2 |
| activity | name | intro | course | idnumber | completion | completionview |
| page | PageName1 | PageDesc1 | C1 | PAGE1 | 1 | 1 |
| page | PageName2 | PageDesc2 | C1 | PAGE2 | 1 | 1 |
And I log in as "admin"
And I am on site homepage
And I follow "Course 1"
@@ -61,3 +61,15 @@ Feature: See the competencies for an activity on the course competencies page.
And I should not see "Test-Comp1"
And I should not see "Test-Comp2"
And I should see "No competencies have been linked to this activity or resource."
@javascript
Scenario: None course competencies page.
When I log in as "student1"
And I am on site homepage
And I follow "Course 1"
And I follow "PageName1"
Then I should see "Test page content"
And I am on site homepage
And I follow "Course 1"
And I follow "PageName1"
Then I should see "Test page content"
+99 -86
View File
@@ -48,100 +48,113 @@ if ($hassiteconfig) {
$ADMIN->add('mobileapp', $temp);
// Show only mobile settings if the mobile service is enabled.
if (!empty($CFG->enablemobilewebservice)) {
// General notification about limited features due to app restrictions.
$notify = new \core\output\notification(
get_string('moodleappsportalfeatureswarning', 'tool_mobile', tool_mobile\api::MOODLE_APPS_PORTAL_URL),
\core\output\notification::NOTIFY_WARNING);
$featuresnotice = $OUTPUT->render($notify);
// General notification about limited features due to app restrictions.
$notify = new \core\output\notification(
get_string('moodleappsportalfeatureswarning', 'tool_mobile', tool_mobile\api::MOODLE_APPS_PORTAL_URL),
\core\output\notification::NOTIFY_WARNING);
$featuresnotice = $OUTPUT->render($notify);
// Type of login.
$temp = new admin_settingpage('mobileauthentication', new lang_string('mobileauthentication', 'tool_mobile'));
$options = array(
tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
);
$temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
new lang_string('typeoflogin', 'tool_mobile'),
new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
// Type of login.
$temp = new admin_settingpage(
'mobileauthentication',
new lang_string('mobileauthentication', 'tool_mobile'),
'moodle/site:config',
empty($CFG->enablemobilewebservice)
);
$temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
new lang_string('forcedurlscheme_key', 'tool_mobile'),
new lang_string('forcedurlscheme', 'tool_mobile'), 'moodlemobile', PARAM_NOTAGS));
$options = array(
tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
);
$temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
new lang_string('typeoflogin', 'tool_mobile'),
new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
$temp->add(new admin_setting_configtext('tool_mobile/minimumversion',
new lang_string('minimumversion_key', 'tool_mobile'),
new lang_string('minimumversion', 'tool_mobile'), '', PARAM_NOTAGS));
$temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
new lang_string('forcedurlscheme_key', 'tool_mobile'),
new lang_string('forcedurlscheme', 'tool_mobile'), 'moodlemobile', PARAM_NOTAGS));
$ADMIN->add('mobileapp', $temp);
$temp->add(new admin_setting_configtext('tool_mobile/minimumversion',
new lang_string('minimumversion_key', 'tool_mobile'),
new lang_string('minimumversion', 'tool_mobile'), '', PARAM_NOTAGS));
// Appearance related settings.
$temp = new admin_settingpage('mobileappearance', new lang_string('mobileappearance', 'tool_mobile'));
$ADMIN->add('mobileapp', $temp);
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesappearance', '', $featuresnotice));
// Appearance related settings.
$temp = new admin_settingpage(
'mobileappearance',
new lang_string('mobileappearance', 'tool_mobile'),
'moodle/site:config',
empty($CFG->enablemobilewebservice)
);
$temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'tool_mobile'),
new lang_string('configmobilecssurl', 'tool_mobile'), '', PARAM_URL));
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesappearance', '', $featuresnotice));
// Reference to Branded Mobile App.
if (empty($CFG->disableserviceads_branded)) {
$temp->add(new admin_setting_description('moodlebrandedappreference',
new lang_string('moodlebrandedapp', 'admin'),
new lang_string('moodlebrandedappreference', 'admin')
));
}
$temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'tool_mobile'),
new lang_string('configmobilecssurl', 'tool_mobile'), '', PARAM_URL));
$temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
new lang_string('smartappbanners', 'tool_mobile'), ''));
$temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
new lang_string('enablesmartappbanners', 'tool_mobile'),
new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
$temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
$temp->add(new admin_setting_configtext('tool_mobile/androidappid', new lang_string('androidappid', 'tool_mobile'),
new lang_string('androidappid_desc', 'tool_mobile'), 'com.moodle.moodlemobile', PARAM_NOTAGS));
$temp->add(new admin_setting_configtext('tool_mobile/setuplink', new lang_string('setuplink', 'tool_mobile'),
new lang_string('setuplink_desc', 'tool_mobile'), 'https://download.moodle.org/mobile', PARAM_URL));
$ADMIN->add('mobileapp', $temp);
// Features related settings.
$temp = new admin_settingpage('mobilefeatures', new lang_string('mobilefeatures', 'tool_mobile'));
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeatures', '', $featuresnotice));
$temp->add(new admin_setting_heading('tool_mobile/logout',
new lang_string('logout'), ''));
$temp->add(new admin_setting_configcheckbox('tool_mobile/forcelogout',
new lang_string('forcelogout', 'tool_mobile'),
new lang_string('forcelogout_desc', 'tool_mobile'), 0));
$temp->add(new admin_setting_heading('tool_mobile/features',
new lang_string('mobilefeatures', 'tool_mobile'), ''));
$options = tool_mobile\api::get_features_list();
$temp->add(new admin_setting_configmultiselect('tool_mobile/disabledfeatures',
new lang_string('disabledfeatures', 'tool_mobile'),
new lang_string('disabledfeatures_desc', 'tool_mobile'), array(), $options));
$temp->add(new admin_setting_configtextarea('tool_mobile/custommenuitems',
new lang_string('custommenuitems', 'tool_mobile'),
new lang_string('custommenuitems_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
$temp->add(new admin_setting_heading('tool_mobile/language',
new lang_string('language'), ''));
$temp->add(new admin_setting_configtextarea('tool_mobile/customlangstrings',
new lang_string('customlangstrings', 'tool_mobile'),
new lang_string('customlangstrings_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
$ADMIN->add('mobileapp', $temp);
// Reference to Branded Mobile App.
if (empty($CFG->disableserviceads_branded)) {
$temp->add(new admin_setting_description('moodlebrandedappreference',
new lang_string('moodlebrandedapp', 'admin'),
new lang_string('moodlebrandedappreference', 'admin')
));
}
$temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
new lang_string('smartappbanners', 'tool_mobile'), ''));
$temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
new lang_string('enablesmartappbanners', 'tool_mobile'),
new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
$temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
$temp->add(new admin_setting_configtext('tool_mobile/androidappid', new lang_string('androidappid', 'tool_mobile'),
new lang_string('androidappid_desc', 'tool_mobile'), 'com.moodle.moodlemobile', PARAM_NOTAGS));
$temp->add(new admin_setting_configtext('tool_mobile/setuplink', new lang_string('setuplink', 'tool_mobile'),
new lang_string('setuplink_desc', 'tool_mobile'), 'https://download.moodle.org/mobile', PARAM_URL));
$ADMIN->add('mobileapp', $temp);
// Features related settings.
$temp = new admin_settingpage(
'mobilefeatures',
new lang_string('mobilefeatures', 'tool_mobile'),
'moodle/site:config',
empty($CFG->enablemobilewebservice)
);
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeatures', '', $featuresnotice));
$temp->add(new admin_setting_heading('tool_mobile/logout',
new lang_string('logout'), ''));
$temp->add(new admin_setting_configcheckbox('tool_mobile/forcelogout',
new lang_string('forcelogout', 'tool_mobile'),
new lang_string('forcelogout_desc', 'tool_mobile'), 0));
$temp->add(new admin_setting_heading('tool_mobile/features',
new lang_string('mobilefeatures', 'tool_mobile'), ''));
$options = tool_mobile\api::get_features_list();
$temp->add(new admin_setting_configmultiselect('tool_mobile/disabledfeatures',
new lang_string('disabledfeatures', 'tool_mobile'),
new lang_string('disabledfeatures_desc', 'tool_mobile'), array(), $options));
$temp->add(new admin_setting_configtextarea('tool_mobile/custommenuitems',
new lang_string('custommenuitems', 'tool_mobile'),
new lang_string('custommenuitems_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
$temp->add(new admin_setting_heading('tool_mobile/language',
new lang_string('language'), ''));
$temp->add(new admin_setting_configtextarea('tool_mobile/customlangstrings',
new lang_string('customlangstrings', 'tool_mobile'),
new lang_string('customlangstrings_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
$ADMIN->add('mobileapp', $temp);
}
+16 -1
View File
@@ -138,13 +138,28 @@ class manager {
*/
const CONFIG_SHIPPED_VERSION = 'shipped_version';
/**
* Helper method to initialize admin page, setting appropriate extra URL parameters
*
* @param string $action
*/
protected function setup_admin_externalpage(string $action): void {
admin_externalpage_setup('tool_usertours/tours', '', array_filter([
'action' => $action,
'id' => optional_param('id', 0, PARAM_INT),
'tourid' => optional_param('tourid', 0, PARAM_INT),
'direction' => optional_param('direction', 0, PARAM_INT),
]));
}
/**
* This is the entry point for this controller class.
*
* @param string $action The action to perform.
*/
public function execute($action) {
admin_externalpage_setup('tool_usertours/tours');
$this->setup_admin_externalpage($action);
// Add the main content.
switch($action) {
case self::ACTION_NEWTOUR:
Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

@@ -93,6 +93,17 @@ class check_defaults extends XMLDBCheckAction {
$physicaldefault = null;
}
// For number fields there are issues with type differences, so let's convert
// everything to a float.
if ($xmldbfield->getType() === XMLDB_TYPE_NUMBER) {
if ($physicaldefault !== null) {
$physicaldefault = (float) $physicaldefault;
}
if ($xmldbdefault !== null) {
$xmldbdefault = (float) $xmldbdefault;
}
}
// There *is* a default and it's wrong.
if ($physicaldefault !== $xmldbdefault) {
$xmldbtext = self::display_default($xmldbdefault);
+19 -3
View File
@@ -624,9 +624,25 @@ class manager {
LEFT JOIN {context} ctx ON ap.contextid = ctx.id
WHERE ctx.id IS NULL)");
$contextsql = "SELECT id FROM {context} ctx";
$DB->delete_records_select('analytics_predictions', "contextid NOT IN ($contextsql)");
$DB->delete_records_select('analytics_indicator_calc', "contextid NOT IN ($contextsql)");
// Cleanup analaytics predictions/calcs with MySQL friendly sub-select.
$DB->execute("DELETE FROM {analytics_predictions} WHERE id IN (
SELECT oldpredictions.id
FROM (
SELECT p.id
FROM {analytics_predictions} p
LEFT JOIN {context} ctx ON p.contextid = ctx.id
WHERE ctx.id IS NULL
) oldpredictions
)");
$DB->execute("DELETE FROM {analytics_indicator_calc} WHERE id IN (
SELECT oldcalcs.id FROM (
SELECT c.id
FROM {analytics_indicator_calc} c
LEFT JOIN {context} ctx ON c.contextid = ctx.id
WHERE ctx.id IS NULL
) oldcalcs
)");
// Clean up stuff that depends on analysable ids that do not exist anymore.
+8
View File
@@ -57,5 +57,13 @@ function xmldb_auth_cas_upgrade($oldversion) {
// Automatically generated Moodle v3.8.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2019111801) {
// Normalize the memberattribute_isdn plugin config.
set_config('memberattribute_isdn',
!empty(get_config('auth_cas', 'memberattribute_isdn')), 'auth_cas');
upgrade_plugin_savepoint(true, 2019111801, 'auth', 'cas');
}
return true;
}
+2 -2
View File
@@ -239,9 +239,9 @@ if ($ADMIN->fulltree) {
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute uses dn.
$settings->add(new admin_setting_configtext('auth_cas/memberattribute_isdn',
$settings->add(new admin_setting_configselect('auth_cas/memberattribute_isdn',
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), '', PARAM_RAW));
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
// Object class.
$settings->add(new admin_setting_configtext('auth_cas/objectclass',
+1 -1
View File
@@ -26,7 +26,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2019111800; // The current plugin version (Date: YYYYMMDDXX)
$plugin->version = 2019111801; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2019111200; // Requires this Moodle version
$plugin->component = 'auth_cas'; // Full name of the plugin (used for diagnostics)
+6 -3
View File
@@ -603,9 +603,12 @@ class auth_plugin_db extends auth_plugin_base {
}
}
if (!empty($update)) {
$authdb->Execute("UPDATE {$this->config->table}
SET ".implode(',', $update)."
WHERE {$this->config->fielduser}='".$this->ext_addslashes($extusername)."'");
$sql = "UPDATE {$this->config->table}
SET ".implode(',', $update)."
WHERE {$this->config->fielduser} = ?";
if (!$authdb->Execute($sql, array($this->ext_addslashes($extusername)))) {
print_error('auth_dbupdateerror', 'auth_db');
}
}
$authdb->Close();
return true;
+1
View File
@@ -74,5 +74,6 @@ $string['auth_dbcannotconnect'] = 'Cannot connect to external database.';
$string['auth_dbcannotreadtable'] = 'Cannot read external table.';
$string['auth_dbtableempty'] = 'External table is empty.';
$string['auth_dbcolumnlist'] = 'External table contains the following columns:<br />{$a}';
$string['auth_dbupdateerror'] = 'Error updating external database.';
$string['pluginname'] = 'External database';
$string['privacy:metadata'] = 'The External database authentication plugin does not store any personal data.';
+1 -1
View File
@@ -49,6 +49,6 @@ class behat_auth_email extends behat_base {
$confirmationpath = $confirmationurl->out_as_local_url(false);
$url = $confirmationpath . '?' . 'data='. $secret .'/'. $username;
$this->getSession()->visit($this->locate_path($url));
$this->execute('behat_general::i_visit', [$url]);
}
}
+8
View File
@@ -75,5 +75,13 @@ function xmldb_auth_ldap_upgrade($oldversion) {
// Automatically generated Moodle v3.8.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2019111801) {
// Normalize the memberattribute_isdn plugin config.
set_config('memberattribute_isdn',
!empty(get_config('auth_ldap', 'memberattribute_isdn')), 'auth_ldap');
upgrade_plugin_savepoint(true, 2019111801, 'auth', 'ldap');
}
return true;
}
+1 -1
View File
@@ -57,7 +57,7 @@ $string['auth_ldap_ldap_encoding'] = 'Encoding used by the LDAP server, most lik
$string['auth_ldap_ldap_encoding_key'] = 'LDAP encoding';
$string['auth_ldap_login_settings'] = 'Login settings';
$string['auth_ldap_memberattribute'] = 'Optional: Overrides user member attribute, when users belongs to a group. Usually \'member\'';
$string['auth_ldap_memberattribute_isdn'] = 'Optional: Overrides handling of member attribute values, either 0 or 1';
$string['auth_ldap_memberattribute_isdn'] = 'Overrides handling of member attribute values';
$string['auth_ldap_memberattribute_isdn_key'] = 'Member attribute uses dn';
$string['auth_ldap_memberattribute_key'] = 'Member attribute';
$string['auth_ldap_noconnect'] = 'LDAP-module cannot connect to server: {$a}';
+2 -2
View File
@@ -142,9 +142,9 @@ if ($ADMIN->fulltree) {
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute uses dn.
$settings->add(new admin_setting_configtext('auth_ldap/memberattribute_isdn',
$settings->add(new admin_setting_configselect('auth_ldap/memberattribute_isdn',
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), '', PARAM_RAW));
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
// Object class.
$settings->add(new admin_setting_configtext('auth_ldap/objectclass',
+19 -8
View File
@@ -108,16 +108,19 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
$o['ou'] = 'users';
ldap_add($connection, 'ou='.$o['ou'].','.$topdn, $o);
$createdusers = array();
for ($i=1; $i<=5; $i++) {
$this->create_ldap_user($connection, $topdn, $i);
$createdusers[] = 'username' . $i;
}
// Set up creators group.
$assignedroles = array('username1', 'username2');
$o = array();
$o['objectClass'] = array('posixGroup');
$o['cn'] = 'creators';
$o['gidNumber'] = 1;
$o['memberUid'] = array('username1', 'username2');
$o['memberUid'] = $assignedroles;
ldap_add($connection, 'cn='.$o['cn'].','.$topdn, $o);
$creatorrole = $DB->get_record('role', array('shortname'=>'coursecreator'));
@@ -174,15 +177,23 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
// Check events, 5 users created with 2 users having roles.
$this->assertCount(7, $events);
foreach ($events as $index => $event) {
$usercreatedindex = array(0, 2, 4, 5, 6);
$roleassignedindex = array (1, 3);
if (in_array($index, $usercreatedindex)) {
$this->assertInstanceOf('\core\event\user_created', $event);
}
if (in_array($index, $roleassignedindex)) {
$this->assertInstanceOf('\core\event\role_assigned', $event);
$username = $DB->get_field('user', 'username', array('id' => $event->relateduserid)); // Get username.
if ($event->eventname === '\core\event\user_created') {
$this->assertContains($username, $createdusers);
unset($events[$index]); // Remove matching event.
} else if ($event->eventname === '\core\event\role_assigned') {
$this->assertContains($username, $assignedroles);
unset($events[$index]); // Remove matching event.
} else {
$this->fail('Unexpected event found: ' . $event->eventname);
}
}
// If all the user_created and role_assigned events have matched
// then the $events array should be now empty.
$this->assertCount(0, $events);
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(2, $DB->count_records('role_assignments'));
+1 -1
View File
@@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2019111800; // The current plugin version (Date: YYYYMMDDXX)
$plugin->version = 2019111801; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2019111200; // Requires this Moodle version
$plugin->component = 'auth_ldap'; // Full name of the plugin (used for diagnostics)
+1 -1
View File
@@ -58,7 +58,7 @@ class behat_auth extends behat_base {
}
// Visit login page.
$this->getSession()->visit($this->locate_path($loginurl->out_as_local_url()));
$this->execute('behat_general::i_visit', [$loginurl]);
// Enter username and password.
$this->execute('behat_forms::i_set_the_field_to', array('Username', $this->escape($username)));
+8 -9
View File
@@ -122,7 +122,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
}
// Identify the backup we're dealing with.
$backuprelease = floatval($this->get_task()->get_info()->backup_release); // The major version: 2.9, 3.0, ...
$backuprelease = $this->get_task()->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
$backupbuild = 0;
preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
if (!empty($matches[1])) {
@@ -132,7 +132,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
// On older versions the freeze value has to be converted.
// We do this from here as it is happening right before the file is read.
// This only targets the backup files that can contain the legacy freeze.
if ($backupbuild > 20150618 && ($backuprelease < 3.0 || $backupbuild < 20160527)) {
if ($backupbuild > 20150618 && (version_compare($backuprelease, '3.0', '<') || $backupbuild < 20160527)) {
$this->rewrite_step_backup_file_for_legacy_freeze($fullpath);
}
@@ -505,8 +505,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
$gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid());
preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
$backupbuild = (int)$matches[1];
// The function floatval will return a float even if there is text mixed with the release number.
$backuprelease = floatval($this->get_task()->get_info()->backup_release);
$backuprelease = $this->get_task()->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
// Extra credits need adjustments only for backups made between 2.8 release (20141110) and the fix release (20150619).
if (!$gradebookcalculationsfreeze && $backupbuild >= 20141110 && $backupbuild < 20150619) {
@@ -521,7 +520,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
// Courses from before 3.1 (20160518) may have a letter boundary problem and should be checked for this issue.
// Backups from before and including 2.9 could have a build number that is greater than 20160518 and should
// be checked for this problem.
if (!$gradebookcalculationsfreeze && ($backupbuild < 20160518 || $backuprelease <= 2.9)) {
if (!$gradebookcalculationsfreeze && ($backupbuild < 20160518 || version_compare($backuprelease, '2.9', '<='))) {
require_once($CFG->libdir . '/db/upgradelib.php');
upgrade_course_letter_boundary($this->get_courseid());
}
@@ -4577,11 +4576,11 @@ class restore_create_categories_and_questions extends restore_structure_step {
// Before 3.5, question categories could be created at top level.
// From 3.5 onwards, all question categories should be a child of a special category called the "top" category.
$backuprelease = floatval($this->get_task()->get_info()->backup_release);
$backuprelease = $this->get_task()->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
$backupbuild = (int)$matches[1];
$before35 = false;
if ($backuprelease < 3.5 || $backupbuild < 20180205) {
if (version_compare($backuprelease, '3.5', '<') || $backupbuild < 20180205) {
$before35 = true;
}
if (empty($mapping->info->parent) && $before35) {
@@ -4838,11 +4837,11 @@ class restore_move_module_questions_categories extends restore_execution_step {
protected function define_execution() {
global $DB;
$backuprelease = floatval($this->task->get_info()->backup_release);
$backuprelease = $this->task->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
preg_match('/(\d{8})/', $this->task->get_info()->moodle_release, $matches);
$backupbuild = (int)$matches[1];
$after35 = false;
if ($backuprelease >= 3.5 && $backupbuild > 20180205) {
if (version_compare($backuprelease, '3.5', '>=') && $backupbuild > 20180205) {
$after35 = true;
}
+2 -2
View File
@@ -581,11 +581,11 @@ abstract class restore_dbops {
$rc = restore_controller_dbops::load_controller($restoreid);
$restoreinfo = $rc->get_info();
$rc->destroy(); // Always need to destroy.
$backuprelease = floatval($restoreinfo->backup_release);
$backuprelease = $restoreinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
preg_match('/(\d{8})/', $restoreinfo->moodle_release, $matches);
$backupbuild = (int)$matches[1];
$after35 = false;
if ($backuprelease >= 3.5 && $backupbuild > 20180205) {
if (version_compare($backuprelease, '3.5', '>=') && $backupbuild > 20180205) {
$after35 = true;
}
+2 -1
View File
@@ -402,7 +402,8 @@ abstract class base_moodleform extends moodleform {
// Get list of module types on course.
$modinfo = get_fast_modinfo($COURSE);
$modnames = $modinfo->get_used_module_names(true);
$modnames = array_map('strval', $modinfo->get_used_module_names(true));
core_collator::asort($modnames);
$PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.backupselectall',
array($modnames));
$PAGE->requires->strings_for_js(array('select', 'all', 'none'), 'moodle');
+3 -3
View File
@@ -56,7 +56,7 @@ class behat_backup extends behat_base {
// table elements are used, and we need to catch exceptions contantly.
// Go to homepage.
$this->getSession()->visit($this->locate_path('/?redirect=0'));
$this->execute('behat_general::i_visit', ['/?redirect=0']);
$this->execute("behat_general::wait_until_the_page_is_ready");
// Click the course link.
@@ -98,7 +98,7 @@ class behat_backup extends behat_base {
// table elements are used, and we need to catch exceptions contantly.
// Go to homepage.
$this->getSession()->visit($this->locate_path('/?redirect=0'));
$this->execute('behat_general::i_visit', ['/?redirect=0']);
// Click the course link.
$this->execute("behat_general::click_link", $backupcourse);
@@ -134,7 +134,7 @@ class behat_backup extends behat_base {
// table elements are used, and we need to catch exceptions contantly.
// Go to homepage.
$this->getSession()->visit($this->locate_path('/?redirect=0'));
$this->execute('behat_general::i_visit', ['/?redirect=0']);
$this->execute("behat_general::wait_until_the_page_is_ready");
// Click the course link.
+1 -1
View File
@@ -70,7 +70,7 @@ class badge extends moodleform {
$mform->addRule('description', null, 'required');
$str = $action == 'new' ? get_string('badgeimage', 'badges') : get_string('newimage', 'badges');
$imageoptions = array('maxbytes' => 262144, 'accepted_types' => array('web_image'));
$imageoptions = array('maxbytes' => 262144, 'accepted_types' => array('optimised_image'));
$mform->addElement('filepicker', 'image', $str, null, $imageoptions);
if ($action == 'new') {
@@ -9,9 +9,14 @@ Feature: Random glossary entry block linking to global glossary
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | globalglossary | defaultapproval |
| glossary | Tips and Tricks | Frontpage glossary description | C2 | glossary0 | 1 | 1 |
And the following "activity" exists:
| activity | glossary |
| name | Tips and Tricks |
| intro | Frontpage glossary description |
| course | C2 |
| idnumber | glossary0 |
| globalglossary | 1 |
| defaultapproval | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Sam1 | Student1 | student1@example.com |
+1 -1
View File
@@ -1,2 +1,2 @@
define ("block_recentlyaccessedcourses/main",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/paged_content_paging_bar","core/templates","core_course/events","core_course/repository"],function(a,b,c,d,e,f,g,h){var i={BLOCK_CONTAINER:"[data-region=\"recentlyaccessedcourses\"]",CARD_CONTAINER:"[data-region=\"card-deck\"]",COURSE_IS_FAVOURITE:"[data-region=\"is-favourite\"]",CONTENT:"[data-region=\"view-content\"]",EMPTY_MESSAGE:"[data-region=\"empty-message\"]",LOADING_PLACEHOLDER:"[data-region=\"loading-placeholder\"]",PAGING_BAR:"[data-region=\"paging-bar\"]",PAGING_BAR_NEXT:"[data-control=\"next\"]",PAGING_BAR_PREVIOUS:"[data-control=\"previous\"]"},j=!1,k=[],l=null,m=null,n=0,o=1,p=function(a){a.find(i.EMPTY_MESSAGE).removeClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden");a.find(i.CONTENT).addClass("hidden")},q=function(a){a.find(i.CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden")},r=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",1);b.css("visibility","visible");b.attr("aria-hidden","false")},s=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",0);b.css("visibility","hidden");b.attr("aria-hidden","true")},t=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).removeClass("hidden")}})},u=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).addClass("hidden")}})},v=function(b){var d=a(i.BLOCK_CONTAINER).data("displaycoursecategory"),e=b.map(function(a){a.showcoursecategory=d;return f.render("block_recentlyaccessedcourses/course-card",a)});return a.when.apply(null,e).then(function(){var b=[];e.forEach(function(d){d.then(function(c){b.push(a(c))}).catch(c.exception)});return b})},w=function(a){return h.getLastAccessedCourses(a,10).then(function(a){return v(a)})},x=function(a){var b=a.find(i.CONTENT).find(i.CARD_CONTAINER),c=parseFloat(a.css("width")),d=k.length,f=0;if(!m){b.html(k[0]);m=k[0].outerWidth(!0)}o=Math.floor(c/m);if(n+o<d){f=n}else{var g=n+o-d;f=n-g;f=0<=f?f:0}var h=k.slice(f,f+o),j=h.reduce(function(a,b){return a+b.attr("data-course-id")},"");if(k.length>h.length){b.addClass("justify-content-center");b.removeClass("justify-content-start")}else{b.removeClass("justify-content-center");b.addClass("justify-content-start")}if(l!=j){var p=a.find(e.rootSelector);b.html(h);l=j;if(o>=k.length){s(a)}else{r(a);if(0===n){e.disablePreviousControlButtons(p)}else{e.enablePreviousControlButtons(p)}if(n+o>=k.length){e.disableNextControlButtons(p)}else{e.enableNextControlButtons(p)}}}},y=function(c){var e=null,f=!1;d.subscribe(g.favourited,function(a){t(c,a)});d.subscribe(g.unfavorited,function(a){u(c,a)});d.subscribe("nav-drawer-toggle-start",function(){if(!j||!k.length||f){return}f=!0;var a=0,b=function(){setTimeout(function(){x(c);a++;if(5>a&&f){b()}},100)};b(c)});d.subscribe("nav-drawer-toggle-end",function(){f=!1});a(window).on("resize",function(){if(!j||!k.length){return}if(!e){e=setTimeout(function(){e=null;x(c)},66)}});b.define(c,[b.events.activate]);c.on(b.events.activate,i.PAGING_BAR_NEXT,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_NEXT);if(!e.hasClass("disabled")){n=n+o;x(c)}d.originalEvent.preventDefault()});c.on(b.events.activate,i.PAGING_BAR_PREVIOUS,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_PREVIOUS);if(!e.hasClass("disabled")){n=n-o;n=0>n?0:n;x(c)}d.originalEvent.preventDefault()})};return{init:function init(b,d){d=a(d);y(d);w(b).then(function(a){k=a;j=!0;if(k.length){q(d);x(d)}else{p(d)}}).catch(c.exception)}}});
define ("block_recentlyaccessedcourses/main",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/paged_content_paging_bar","core/templates","core_course/events","core_course/repository"],function(a,b,c,d,e,f,g,h){var i={BLOCK_CONTAINER:"[data-region=\"recentlyaccessedcourses\"]",CARD_CONTAINER:"[data-region=\"card-deck\"]",COURSE_IS_FAVOURITE:"[data-region=\"is-favourite\"]",CONTENT:"[data-region=\"view-content\"]",EMPTY_MESSAGE:"[data-region=\"empty-message\"]",LOADING_PLACEHOLDER:"[data-region=\"loading-placeholder\"]",PAGING_BAR:"[data-region=\"paging-bar\"]",PAGING_BAR_NEXT:"[data-control=\"next\"]",PAGING_BAR_PREVIOUS:"[data-control=\"previous\"]"},j=!1,k=[],l=null,m=null,n=0,o=1,p=function(a){a.find(i.EMPTY_MESSAGE).removeClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden");a.find(i.CONTENT).addClass("hidden")},q=function(a){a.find(i.CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden")},r=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",1);b.css("visibility","visible");b.attr("aria-hidden","false")},s=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",0);b.css("visibility","hidden");b.attr("aria-hidden","true")},t=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).removeClass("hidden")}})},u=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).addClass("hidden")}})},v=function(b){var d=a(i.BLOCK_CONTAINER).data("displaycoursecategory"),e=b.map(function(a){a.showcoursecategory=d;return f.render("block_recentlyaccessedcourses/course-card",a)});return a.when.apply(null,e).then(function(){var b=[];e.forEach(function(d){d.then(function(c){b.push(a(c))}).catch(c.exception)});return b})},w=function(a){return h.getLastAccessedCourses(a,10).then(function(a){return v(a)})},x=function(a){var b=a.find(i.CONTENT).find(i.CARD_CONTAINER),c=parseFloat(a.css("width")),d=k.length,f=0;if(!m){b.html(k[0]);m=k[0].outerWidth(!0)}o=Math.floor(c/m);if(n+o<d){f=n}else{var g=n+o-d;f=n-g;f=0<=f?f:0}if(0===o){o=1}var h=k.slice(f,f+o),j=h.reduce(function(a,b){return a+b.attr("data-course-id")},"");if(k.length>h.length){b.addClass("justify-content-center");b.removeClass("justify-content-start")}else{b.removeClass("justify-content-center");b.addClass("justify-content-start")}if(l!=j){var p=a.find(e.rootSelector);b.html(h);l=j;if(o>=k.length){s(a)}else{r(a);if(0===n){e.disablePreviousControlButtons(p)}else{e.enablePreviousControlButtons(p)}if(n+o>=k.length){e.disableNextControlButtons(p)}else{e.enableNextControlButtons(p)}}}},y=function(c){var e=null,f=!1;d.subscribe(g.favourited,function(a){t(c,a)});d.subscribe(g.unfavorited,function(a){u(c,a)});d.subscribe("nav-drawer-toggle-start",function(){if(!j||!k.length||f){return}f=!0;var a=0,b=function(){setTimeout(function(){x(c);a++;if(5>a&&f){b()}},100)};b(c)});d.subscribe("nav-drawer-toggle-end",function(){f=!1});a(window).on("resize",function(){if(!j||!k.length){return}if(!e){e=setTimeout(function(){e=null;x(c)},66)}});b.define(c,[b.events.activate]);c.on(b.events.activate,i.PAGING_BAR_NEXT,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_NEXT);if(!e.hasClass("disabled")){n=n+o;x(c)}d.originalEvent.preventDefault()});c.on(b.events.activate,i.PAGING_BAR_PREVIOUS,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_PREVIOUS);if(!e.hasClass("disabled")){n=n-o;n=0>n?0:n;x(c)}d.originalEvent.preventDefault()})};return{init:function init(b,d){d=a(d);y(d);w(b).then(function(a){k=a;j=!0;if(k.length){q(d);x(d)}else{p(d)}}).catch(c.exception)}}});
//# sourceMappingURL=main.min.js.map
File diff suppressed because one or more lines are too long
@@ -208,6 +208,11 @@ define(
start = start >= 0 ? start : 0;
}
// At least show one card.
if (availableVisibleCards === 0) {
availableVisibleCards = 1;
}
var coursesToShow = allCourses.slice(start, start + availableVisibleCards);
// Create an id for the list of courses we expect to be displayed.
var newVisibleCoursesId = coursesToShow.reduce(function(carry, course) {
@@ -49,13 +49,18 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
* @return array Additional properties with values
*/
protected function get_other_values(renderer_base $output) {
global $OUTPUT;
global $CFG;
require_once($CFG->libdir.'/modinfolib.php');
return array(
'viewurl' => (new moodle_url('/mod/'.$this->data->modname.'/view.php',
array('id' => $this->data->cmid)))->out(false),
'courseviewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->courseid)))->out(false),
'icon' => $OUTPUT->image_icon('icon', get_string('pluginname', $this->data->modname), $this->data->modname)
'viewurl' => (new moodle_url('/mod/'.$this->data->modname.'/view.php',
array('id' => $this->data->cmid)))->out(false),
'courseviewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->courseid)))->out(false),
'icon' => \html_writer::img(
get_fast_modinfo($this->data->courseid)->cms[$this->data->cmid]->get_icon_url(),
get_string('pluginname', $this->data->modname),
['title' => get_string('pluginname', $this->data->modname), 'class' => 'icon']
)
);
}
@@ -111,4 +116,4 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
)
);
}
}
}
+1 -1
View File
@@ -57,7 +57,7 @@
data-midnight="{{midnight}}"
data-limit="2"
data-offset="0"
data-days-limit="30"
data-days-limit="{{dayslimit}}"
data-days-offset="0"
data-no-events-url="{{urls.noevents}}"
id="view_courses_{{uniqid}}"
+25
View File
@@ -86,6 +86,9 @@ class week_day_exporter extends day_exporter {
'type' => PARAM_RAW,
'default' => '',
],
'daytitle' => [
'type' => PARAM_RAW,
]
]);
return $return;
@@ -104,6 +107,8 @@ class week_day_exporter extends day_exporter {
$return['popovertitle'] = $popovertitle;
}
$return['daytitle'] = $this->get_day_title();
return $return;
}
@@ -141,4 +146,24 @@ class week_day_exporter extends day_exporter {
return $title;
}
/**
* Get the title for this day.
*
* @return string
*/
protected function get_day_title(): string {
$userdate = userdate($this->data[0], get_string('strftimedayshort'));
$numevents = count($this->related['events']);
if ($numevents == 1) {
$title = get_string('dayeventsone', 'calendar', $userdate);
} else if ($numevents) {
$title = get_string('dayeventsmany', 'calendar', ['num' => $numevents, 'day' => $userdate]);
} else {
$title = get_string('dayeventsnone', 'calendar', $userdate);
}
return $title;
}
}
+6
View File
@@ -1274,6 +1274,12 @@ class calendar_information {
* @param string|null $view preference view options (eg: day, month, upcoming)
*/
public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
global $PAGE;
if (!has_capability('moodle/block:view', $PAGE->context) ) {
return;
}
if ($showfilters) {
$filters = new block_contents();
$filters->content = $renderer->event_filter();
+7 -4
View File
@@ -46,8 +46,9 @@
<thead>
<tr>
{{# daynames }}
<th class="header text-xs-center" aria-label="{{fullname}}">
{{shortname}}
<th class="header text-xs-center">
<span class="sr-only">{{fullname}}</span>
<span aria-hidden="true">{{shortname}}</span>
</th>
{{/ daynames }}
</tr>
@@ -71,6 +72,7 @@
data-region="day"
data-new-event-timestamp="{{neweventtimestamp}}">
<div class="d-none d-md-block hidden-phone text-xs-center">
<span class="sr-only">{{daytitle}}</span>
{{#hasevents}}
<a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
@@ -78,7 +80,7 @@
data-timestamp="{{timestamp}}">{{mday}}</a>
{{/hasevents}}
{{^hasevents}}
{{mday}}
<span aria-hidden="true">{{mday}}</span>
{{/hasevents}}
{{#hasevents}}
<div data-region="day-content">
@@ -122,6 +124,7 @@
{{/hasevents}}
</div>
<div class="d-md-none hidden-desktop hidden-tablet">
<span class="sr-only">{{daytitle}}</span>
{{#hasevents}}
<a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
@@ -129,7 +132,7 @@
data-timestamp="{{timestamp}}">{{mday}}</a>
{{/hasevents}}
{{^hasevents}}
{{mday}}
<span aria-hidden="true">{{mday}}</span>
{{/hasevents}}
</div>
</td>
+6 -4
View File
@@ -78,8 +78,9 @@
<thead>
<tr>
{{# daynames }}
<th class="header text-xs-center" scope="col" aria-label="{{fullname}}">
{{shortname}}
<th class="header text-xs-center">
<span class="sr-only">{{fullname}}</span>
<span aria-hidden="true">{{shortname}}</span>
</th>
{{/ daynames }}
</tr>
@@ -128,7 +129,8 @@
This is the timestamp for this month.
}} data-day-timestamp="{{timestamp}}"{{!
}}>{{!
}}{{#popovertitle}}
}}<span class="sr-only">{{daytitle}}</span>
{{#popovertitle}}
{{< core_calendar/minicalendar_day_link }}
{{$day}}{{mday}}{{/day}}
{{$url}}{{viewdaylink}}{{/url}}
@@ -145,7 +147,7 @@
{{/ core_calendar/minicalendar_day_link }}
{{/popovertitle}}{{!
}}{{^popovertitle}}
{{mday}}
<span aria-hidden="true">{{mday}}</span>
{{/popovertitle}}{{!
}}</td>
{{/days}}
+2 -2
View File
@@ -121,7 +121,7 @@ class behat_calendar extends behat_base {
*/
public function i_view_the_calendar_for($month, $year) {
$time = make_timestamp($year, $month, 1);
$this->getSession()->visit($this->locate_path('/calendar/view.php?view=month&course=1&time='.$time));
$this->execute('behat_general::i_visit', ['/calendar/view.php?view=month&course=1&time='.$time]);
}
@@ -134,6 +134,6 @@ class behat_calendar extends behat_base {
*/
public function i_am_viewing_site_calendar() {
$url = new moodle_url('/calendar/view.php', ['view' => 'month']);
$this->getSession()->visit($this->locate_path($url->out_as_local_url(false)));
$this->execute('behat_general::i_visit', [$url]);
}
}
+1 -1
View File
@@ -154,7 +154,7 @@ foreach($cohorts['cohorts'] as $cohort) {
$cohortmanager = has_capability('moodle/cohort:manage', $cohortcontext);
$cohortcanassign = has_capability('moodle/cohort:assign', $cohortcontext);
$urlparams = array('id' => $cohort->id, 'returnurl' => $baseurl->out_as_local_url());
$urlparams = array('id' => $cohort->id, 'returnurl' => $baseurl->out_as_local_url(false));
$showhideurl = new moodle_url('/cohort/edit.php', $urlparams + array('sesskey' => sesskey()));
if ($cohortmanager) {
if ($cohort->visible) {
+1 -1
View File
@@ -337,7 +337,7 @@ class core_comment_external extends external_api {
$args->area = $commentrecord->commentarea;
$manager = new comment($args);
if ($commentrecord->userid != $USER->id && !$manager->can_delete($commentrecord->id)) {
if (!$manager->can_delete($commentrecord)) {
throw new comment_exception('nopermissiontodelentry');
}
+2 -2
View File
@@ -91,8 +91,8 @@ switch ($action) {
}
break;
case 'delete':
$comment_record = $DB->get_record('comments', array('id'=>$commentid));
if ($manager->can_delete($commentid) || $comment_record->userid == $USER->id) {
$comment = $DB->get_record('comments', ['id' => $commentid]);
if ($manager->can_delete($comment)) {
if ($manager->delete($commentid)) {
$result = array(
'client_id' => $client_id,
+39 -12
View File
@@ -589,8 +589,7 @@ class comment {
$c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
$c->userid = $u->id;
$candelete = $this->can_delete($c->id);
if (($USER->id == $u->id) || !empty($candelete)) {
if ($this->can_delete($c)) {
$c->delete = true;
}
$comments[] = $c;
@@ -800,16 +799,22 @@ class comment {
/**
* Delete a comment
*
* @param int $commentid
* @param int|stdClass $comment The id of a comment, or a comment record.
* @return bool
*/
public function delete($commentid) {
global $DB, $USER;
$candelete = has_capability('moodle/comment:delete', $this->context);
if (!$comment = $DB->get_record('comments', array('id'=>$commentid))) {
public function delete($comment) {
global $DB;
if (is_object($comment)) {
$commentid = $comment->id;
} else {
$commentid = $comment;
$comment = $DB->get_record('comments', ['id' => $commentid]);
}
if (!$comment) {
throw new comment_exception('dbupdatefailed');
}
if (!($USER->id == $comment->userid || !empty($candelete))) {
if (!$this->can_delete($comment)) {
throw new comment_exception('nopermissiontocomment');
}
$DB->delete_records('comments', array('id'=>$commentid));
@@ -976,13 +981,35 @@ class comment {
}
/**
* Returns true if the user can delete this comment
* @param int $commentid
* Returns true if the user can delete this comment.
*
* The user can delete comments if it is one they posted and they can still make posts,
* or they have the capability to delete comments.
*
* A database call is avoided if a comment record is passed.
*
* @param int|stdClass $comment The id of a comment, or a comment record.
* @return bool
*/
public function can_delete($commentid) {
public function can_delete($comment) {
global $USER, $DB;
if (is_object($comment)) {
$commentid = $comment->id;
} else {
$commentid = $comment;
}
$this->validate(array('commentid'=>$commentid));
return has_capability('moodle/comment:delete', $this->context);
if (!is_object($comment)) {
// Get the comment record from the database.
$comment = $DB->get_record('comments', array('id' => $commentid), 'id, userid', MUST_EXIST);
}
$hascapability = has_capability('moodle/comment:delete', $this->context);
$owncomment = $USER->id == $comment->userid;
return ($hascapability || ($owncomment && $this->can_post()));
}
/**
+166
View File
@@ -0,0 +1,166 @@
<?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/>.
/**
* Tests for comments when the context is frozen.
*
* @package core_comment
* @copyright 2019 University of Nottingham
* @author Neill Magill <neill.magill@nottingham.ac.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Tests for comments when the context is frozen.
*
* @package core_comment
* @copyright 2019 University of Nottingham
* @author Neill Magill <neill.magill@nottingham.ac.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class comment_context_freeze_testcase extends advanced_testcase {
/**
* Creates a comment by a student.
*
* Returns:
* - The comment object
* - The sudent that wrote the comment
* - The arguments used to create the comment
*
* @param stdClass $course Moodle course from the datagenerator
* @return array
*/
protected function create_student_comment_and_freeze_course($course): array {
set_config('contextlocking', 1);
$context = context_course::instance($course->id);
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$args = new stdClass;
$args->context = $context;
$args->course = $course;
$args->area = 'page_comments';
$args->itemid = 0;
$args->component = 'block_comments';
$args->linktext = get_string('showcomments');
$args->notoggle = true;
$args->autostart = true;
$args->displaycancel = false;
// Create a comment by the student.
$this->setUser($student);
$comment = new comment($args);
$newcomment = $comment->add('New comment');
// Freeze the context.
$this->setAdminUser();
$context->set_locked(true);
return [$newcomment, $student, $args];
}
/**
* Test that a student cannot delete their own comments in frozen contexts via the external service.
*/
public function test_delete_student_external() {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that a student cannot delete their own comment.
$this->setUser($student);
$studentcomment = new comment($args);
$this->assertFalse($studentcomment->can_delete($newcomment->id));
$this->assertFalse($studentcomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
core_comment_external::delete_comments([$newcomment->id]);
}
/**
* Test that a student cannot delete their own comments in frozen contexts.
*/
public function test_delete_student() {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that a student cannot delete their own comment.
$this->setUser($student);
$studentcomment = new comment($args);
$this->assertFalse($studentcomment->can_delete($newcomment->id));
$this->assertFalse($studentcomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
$studentcomment->delete($newcomment->id);
}
/**
* Test that an admin cannot delete comments in frozen contexts via the external service.
*/
public function test_delete_admin_external() {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that the admin user cannot delete the comment.
$admincomment = new comment($args);
$this->assertFalse($admincomment->can_delete($newcomment->id));
$this->assertFalse($admincomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
core_comment_external::delete_comments([$newcomment->id]);
}
/**
* Test that an admin cannot delete comments in frozen contexts.
*/
public function test_delete_admin() {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
// Check that the admin user cannot delete the comment.
$admincomment = new comment($args);
$this->assertFalse($admincomment->can_delete($newcomment->id));
$this->assertFalse($admincomment->can_post());
$this->expectException(comment_exception::class);
$this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
$admincomment->delete($newcomment->id);
}
}
+6
View File
@@ -4660,6 +4660,9 @@ class api {
$recommend = false;
$strdesc = 'evidence_coursemodulecompleted';
if ($outcome == course_module_competency::OUTCOME_NONE) {
continue;
}
if ($outcome == course_module_competency::OUTCOME_EVIDENCE) {
$action = evidence::ACTION_LOG;
@@ -4720,6 +4723,9 @@ class api {
$recommend = false;
$strdesc = 'evidence_coursecompleted';
if ($outcome == course_module_competency::OUTCOME_NONE) {
continue;
}
if ($outcome == course_competency::OUTCOME_EVIDENCE) {
$action = evidence::ACTION_LOG;
@@ -0,0 +1,30 @@
@core @core_completion
Feature: Set completion of other courses as criteria for completion of current course
In order to set completion of other courses as criteria for completion of current course
As a user
I want to select the prerequisite courses in completion settings
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
| Course 2 | C2 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | One | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
@javascript
Scenario: Set completion of prerequisite course as completion criteria of current course
When I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Course completion" in current page administration
And I click on "Condition: Completion of other courses" "link"
And I set the field "Courses available" to "Course 2"
And I press "Save changes"
And I add the "Course completion status" block
And I click on "View course report" "link" in the "Course completion status" "block"
Then I should see "Course 2" in the "completion-progress" "table"
And I should see "Student One" in the "completion-progress" "table"
+3 -1
View File
@@ -75,6 +75,7 @@ class core_course_editcategory_form extends moodleform {
$mform->addElement('editor', 'description_editor', get_string('description'), null,
$this->get_description_editor_options());
$mform->setType('description_editor', PARAM_RAW);
if (!empty($CFG->allowcategorythemes)) {
$themes = array(''=>get_string('forceno'));
@@ -105,7 +106,8 @@ class core_course_editcategory_form extends moodleform {
return array(
'maxfiles' => EDITOR_UNLIMITED_FILES,
'maxbytes' => $CFG->maxbytes,
'trusttext' => true,
'trusttext' => false,
'noclean' => true,
'context' => $context,
'subdirs' => file_area_contains_subdirs($context, 'coursecat', 'description', $itemid),
);
+7 -7
View File
@@ -201,13 +201,13 @@ class helper {
if ($category->can_change_sortorder()) {
$actions['moveup'] = array(
'url' => new \moodle_url($baseurl, array('action' => 'movecategoryup')),
'icon' => new \pix_icon('t/up', new \lang_string('up')),
'string' => new \lang_string('up')
'icon' => new \pix_icon('t/up', new \lang_string('moveup')),
'string' => new \lang_string('moveup')
);
$actions['movedown'] = array(
'url' => new \moodle_url($baseurl, array('action' => 'movecategorydown')),
'icon' => new \pix_icon('t/down', new \lang_string('down')),
'string' => new \lang_string('down')
'icon' => new \pix_icon('t/down', new \lang_string('movedown')),
'string' => new \lang_string('movedown')
);
}
@@ -359,7 +359,7 @@ class helper {
*
* @param \core_course_category $category
* @param \core_course_list_element $course
* @return string
* @return array
*/
public static function get_course_listitem_actions(\core_course_category $category, \core_course_list_element $course) {
$baseurl = new \moodle_url(
@@ -400,12 +400,12 @@ class helper {
if ($category->can_resort_courses()) {
$actions[] = array(
'url' => new \moodle_url($baseurl, array('action' => 'movecourseup')),
'icon' => new \pix_icon('t/up', \get_string('up')),
'icon' => new \pix_icon('t/up', \get_string('moveup')),
'attributes' => array('data-action' => 'moveup', 'class' => 'action-moveup')
);
$actions[] = array(
'url' => new \moodle_url($baseurl, array('action' => 'movecoursedown')),
'icon' => new \pix_icon('t/down', \get_string('down')),
'icon' => new \pix_icon('t/down', \get_string('movedown')),
'attributes' => array('data-action' => 'movedown', 'class' => 'action-movedown')
);
}
+10 -9
View File
@@ -85,6 +85,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$categoryid = '';
}
$select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
$select->attributes['aria-label'] = get_string('selectacategory');
$html .= $this->render($select);
}
$html .= html_writer::end_div();
@@ -264,8 +265,8 @@ class core_course_management_renderer extends plugin_renderer_base {
$html .= html_writer::start_div('float-left ' . $checkboxclass);
$html .= html_writer::start_div('custom-control custom-checkbox mr-1 ');
$html .= html_writer::empty_tag('input', $bcatinput);
$html .= html_writer::tag('label', '', array(
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
$labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
$html .= html_writer::tag('label', $labeltext, array(
'class' => 'custom-control-label',
'for' => 'categorylistitem' . $category->id));
$html .= html_writer::end_div();
@@ -542,7 +543,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$html .= html_writer::start_div('card-body');
$html .= $this->course_listing_actions($category, $course, $perpage);
$html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
$html .= html_writer::start_tag('ul', array('class' => 'ml course-list', 'role' => 'group'));
$html .= html_writer::start_tag('ul', array('class' => 'ml course-list'));
foreach ($category->get_courses($options) as $listitem) {
$html .= $this->course_listitem($category, $listitem, $courseid);
}
@@ -643,8 +644,8 @@ class core_course_management_renderer extends plugin_renderer_base {
$html .= html_writer::start_div('float-left ' . $checkboxclass);
$html .= html_writer::start_div('custom-control custom-checkbox mr-1 ');
$html .= html_writer::empty_tag('input', $bulkcourseinput);
$html .= html_writer::tag('label', '', array(
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
$labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
$html .= html_writer::tag('label', $labeltext, array(
'class' => 'custom-control-label',
'for' => 'courselistitem' . $course->id));
$html .= html_writer::end_div();
@@ -1217,8 +1218,8 @@ class core_course_management_renderer extends plugin_renderer_base {
if ($bulkcourseinput) {
$html .= html_writer::start_div('custom-control custom-checkbox mr-1');
$html .= html_writer::empty_tag('input', $bulkcourseinput);
$html .= html_writer::tag('label', '', array(
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
$labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
$html .= html_writer::tag('label', $labeltext, array(
'class' => 'custom-control-label',
'for' => 'coursesearchlistitem' . $course->id));
$html .= html_writer::end_div();
@@ -1325,12 +1326,12 @@ class core_course_management_renderer extends plugin_renderer_base {
$output .= html_writer::start_tag('form', array('class' => 'card', 'id' => $formid,
'action' => $searchurl, 'method' => 'get'));
$output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
$output .= html_writer::tag('div', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
$output .= html_writer::tag('legend', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
array('class' => 'card-header'));
$output .= html_writer::start_div('card-body');
$output .= html_writer::start_div('input-group col-sm-6 col-lg-4 m-auto');
$output .= html_writer::empty_tag('input', array('class' => 'form-control', 'type' => 'text', 'id' => $inputid,
'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
'size' => $inputsize, 'name' => 'search', 'value' => s($value), 'aria-label' => get_string('searchcourses')));
$output .= html_writer::start_tag('span', array('class' => 'input-group-btn'));
$output .= html_writer::tag('button', get_string('go'), array('class' => 'btn btn-primary', 'type' => 'submit'));
$output .= html_writer::end_tag('span');
+4 -2
View File
@@ -128,14 +128,16 @@ class course_completion_form extends moodleform {
}
// Get applicable courses (prerequisites).
$selectedcourses = $DB->get_fieldset_sql("SELECT cc.courseinstance
FROM {course_completion_criteria} cc WHERE cc.course = ?", [$course->id]);
$hasselectablecourses = core_course_category::search_courses(['onlywithcompletion' => true], ['limit' => 2]);
unset($hasselectablecourses[$course->id]);
if ($hasselectablecourses) {
// Show multiselect box.
$mform->addElement('course', 'criteria_course', get_string('coursesavailable', 'completion'),
array('multiple' => 'multiple', 'onlywithcompletion' => true, 'exclude' => $course->id));
$mform->setType('criteria_course', PARAM_INT);
$selectedcourses = $DB->get_fieldset_select('course_completion_criteria', 'courseinstance',
'course = :course AND criteriatype = :type', ['course' => $course->id, 'type' => COMPLETION_CRITERIA_TYPE_COURSE]);
$mform->setDefault('criteria_course', $selectedcourses);
// Map aggregation methods to context-sensitive human readable dropdown menu.
+15 -2
View File
@@ -855,6 +855,13 @@ class core_course_external extends external_api {
}
require_capability('moodle/course:create', $context);
// Fullname and short name are required to be non-empty.
if (trim($course['fullname']) === '') {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
} else if (trim($course['shortname']) === '') {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
}
// Make sure lang is valid
if (array_key_exists('lang', $course)) {
if (empty($availablelangs[$course['lang']])) {
@@ -1040,14 +1047,20 @@ class core_course_external extends external_api {
$course['category'] = $course['categoryid'];
}
// Check if the user can change fullname.
// Check if the user can change fullname, and the new value is non-empty.
if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
require_capability('moodle/course:changefullname', $context);
if (trim($course['fullname']) === '') {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
}
}
// Check if the user can change shortname.
// Check if the user can change shortname, and the new value is non-empty.
if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
require_capability('moodle/course:changeshortname', $context);
if (trim($course['shortname']) === '') {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
}
}
// Check if the user can change the idnumber.
@@ -48,8 +48,8 @@ class restore_format_topics_plugin extends restore_format_plugin {
*/
protected function need_restore_numsections() {
$backupinfo = $this->step->get_task()->get_info();
$backuprelease = $backupinfo->backup_release;
return version_compare($backuprelease, '3.3', 'lt');
$backuprelease = $backupinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
return version_compare($backuprelease, '3.3', '<');
}
/**
@@ -48,8 +48,8 @@ class restore_format_weeks_plugin extends restore_format_plugin {
*/
protected function is_pre_33_backup() {
$backupinfo = $this->step->get_task()->get_info();
$backuprelease = $backupinfo->backup_release;
return version_compare($backuprelease, '3.3', 'lt');
$backuprelease = $backupinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
return version_compare($backuprelease, '3.3', '<');
}
/**
+3 -9
View File
@@ -733,20 +733,14 @@ function get_category_or_system_context($categoryid) {
}
/**
* Returns full course categories trees to be used in html_writer::select()
* Returns the list of full course categories to be used in html_writer::select()
*
* Calls {@link core_course_category::make_categories_list()} to build the tree and
* adds whitespace to denote nesting
* Calls {@see core_course_category::make_categories_list()} to build the list.
*
* @return array array mapping course category id to the display name
*/
function make_categories_options() {
$cats = core_course_category::make_categories_list('', 0, ' / ');
foreach ($cats as $key => $value) {
// Prefix the value with the number of spaces equal to category depth (number of separators in the value).
$cats[$key] = str_repeat('&nbsp;', substr_count($value, ' / ')). $value;
}
return $cats;
return core_course_category::make_categories_list('', 0, ' / ');
}
/**
+6 -1
View File
@@ -143,7 +143,12 @@ $mform->set_data($data);
if ($mform->is_cancelled()) {
if ($return && !empty($cm->id)) {
redirect("$CFG->wwwroot/mod/$module->name/view.php?id=$cm->id");
$urlparams = [
'id' => $cm->id, // We always need the activity id.
'forceview' => 1, // Stop file downloads in resources.
];
$activityurl = new moodle_url("/mod/$module->name/view.php", $urlparams);
redirect($activityurl);
} else {
redirect(course_get_url($course, $cw->section, array('sr' => $sectionreturn)));
}
+74
View File
@@ -551,6 +551,80 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$createdsubcats = core_course_external::create_courses($courses);
}
/**
* Data provider for testing empty fields produce expected exceptions
*
* @see test_create_courses_empty_field
* @see test_update_courses_empty_field
*
* @return array
*/
public function course_empty_field_provider(): array {
return [
[[
'fullname' => '',
'shortname' => 'ws101',
], 'fullname'],
[[
'fullname' => ' ',
'shortname' => 'ws101',
], 'fullname'],
[[
'fullname' => 'Web Services',
'shortname' => '',
], 'shortname'],
[[
'fullname' => 'Web Services',
'shortname' => ' ',
], 'shortname'],
];
}
/**
* Test creating courses with empty fields throws an exception
*
* @param array $course
* @param string $expectedemptyfield
*
* @dataProvider course_empty_field_provider
*/
public function test_create_courses_empty_field(array $course, string $expectedemptyfield): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a category for the new course.
$course['categoryid'] = $this->getDataGenerator()->create_category()->id;
$this->expectException(moodle_exception::class);
$this->expectExceptionMessageRegExp("/{$expectedemptyfield}/");
core_course_external::create_courses([$course]);
}
/**
* Test updating courses with empty fields returns warnings
*
* @param array $course
* @param string $expectedemptyfield
*
* @dataProvider course_empty_field_provider
*/
public function test_update_courses_empty_field(array $course, string $expectedemptyfield): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course to update.
$course['id'] = $this->getDataGenerator()->create_course()->id;
$result = core_course_external::update_courses([$course]);
$result = core_course_external::clean_returnvalue(core_course_external::update_courses_returns(), $result);
$this->assertCount(1, $result['warnings']);
$warning = reset($result['warnings']);
$this->assertEquals('errorinvalidparam', $warning['warningcode']);
$this->assertContains($expectedemptyfield, $warning['message']);
}
/**
* Test delete_courses
*/
Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" preserveAspectRatio="xMinYMid meet">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(60%,60%,60%);fill-opacity:1;" d="M 13.144531 8.304688 L 13.144531 11.144531 C 13.144531 11.851562 12.890625 12.457031 12.386719 12.960938 C 11.886719 13.460938 11.28125 13.714844 10.570312 13.714844 L 3.144531 13.714844 C 2.433594 13.714844 1.828125 13.460938 1.324219 12.960938 C 0.824219 12.457031 0.570312 11.851562 0.570312 11.144531 L 0.570312 3.714844 C 0.570312 3.007812 0.824219 2.398438 1.324219 1.898438 C 1.828125 1.394531 2.433594 1.144531 3.144531 1.144531 L 10.570312 1.144531 C 10.945312 1.144531 11.292969 1.21875 11.617188 1.367188 C 11.707031 1.40625 11.757812 1.476562 11.777344 1.570312 C 11.792969 1.671875 11.769531 1.757812 11.695312 1.832031 L 11.257812 2.269531 C 11.199219 2.328125 11.132812 2.355469 11.054688 2.355469 C 11.035156 2.355469 11.007812 2.351562 10.972656 2.339844 C 10.835938 2.304688 10.703125 2.285156 10.570312 2.285156 L 3.144531 2.285156 C 2.75 2.285156 2.414062 2.425781 2.132812 2.707031 C 1.855469 2.984375 1.714844 3.320312 1.714844 3.714844 L 1.714844 11.144531 C 1.714844 11.535156 1.855469 11.871094 2.132812 12.152344 C 2.414062 12.429688 2.75 12.570312 3.144531 12.570312 L 10.570312 12.570312 C 10.964844 12.570312 11.300781 12.429688 11.582031 12.152344 C 11.859375 11.871094 12 11.535156 12 11.144531 L 12 8.875 C 12 8.796875 12.027344 8.730469 12.082031 8.679688 L 12.652344 8.105469 C 12.710938 8.046875 12.78125 8.019531 12.855469 8.019531 C 12.894531 8.019531 12.929688 8.027344 12.964844 8.042969 C 13.082031 8.09375 13.144531 8.179688 13.144531 8.304688 Z M 15.207031 3.9375 L 7.9375 11.207031 C 7.792969 11.347656 7.625 11.417969 7.429688 11.417969 C 7.230469 11.417969 7.0625 11.347656 6.917969 11.207031 L 3.082031 7.367188 C 2.9375 7.222656 2.867188 7.054688 2.867188 6.855469 C 2.867188 6.660156 2.9375 6.492188 3.082031 6.347656 L 4.0625 5.367188 C 4.207031 5.222656 4.375 5.152344 4.570312 5.152344 C 4.769531 5.152344 4.9375 5.222656 5.082031 5.367188 L 7.429688 7.714844 L 13.207031 1.9375 C 13.347656 1.792969 13.519531 1.722656 13.714844 1.722656 C 13.910156 1.722656 14.082031 1.792969 14.222656 1.9375 L 15.207031 2.917969 C 15.347656 3.0625 15.417969 3.230469 15.417969 3.429688 C 15.417969 3.625 15.347656 3.792969 15.207031 3.9375 Z M 15.207031 3.9375 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" preserveAspectRatio="xMinYMid meet">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(60%,60%,60%);fill-opacity:1;" d="M 11.714844 2.285156 L 4.285156 2.285156 C 3.894531 2.285156 3.554688 2.425781 3.277344 2.707031 C 2.996094 2.984375 2.855469 3.320312 2.855469 3.714844 L 2.855469 11.144531 C 2.855469 11.535156 2.996094 11.871094 3.277344 12.152344 C 3.554688 12.429688 3.894531 12.570312 4.285156 12.570312 L 11.714844 12.570312 C 12.105469 12.570312 12.445312 12.429688 12.722656 12.152344 C 13.003906 11.871094 13.144531 11.535156 13.144531 11.144531 L 13.144531 3.714844 C 13.144531 3.320312 13.003906 2.984375 12.722656 2.707031 C 12.445312 2.425781 12.105469 2.285156 11.714844 2.285156 Z M 14.285156 3.714844 L 14.285156 11.144531 C 14.285156 11.851562 14.035156 12.457031 13.53125 12.960938 C 13.027344 13.460938 12.421875 13.714844 11.714844 13.714844 L 4.285156 13.714844 C 3.578125 13.714844 2.972656 13.460938 2.46875 12.960938 C 1.964844 12.457031 1.714844 11.851562 1.714844 11.144531 L 1.714844 3.714844 C 1.714844 3.007812 1.964844 2.398438 2.46875 1.898438 C 2.972656 1.394531 3.578125 1.144531 4.285156 1.144531 L 11.714844 1.144531 C 12.421875 1.144531 13.027344 1.394531 13.53125 1.898438 C 14.035156 2.398438 14.285156 3.007812 14.285156 3.714844 Z M 14.285156 3.714844 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -96,7 +96,7 @@ class data_controller extends \core_customfield\data_controller {
$this->save();
}
if ($fromform['text']) {
if (array_key_exists('text', $fromform)) {
$textoptions = $this->value_editor_options();
$data = (object) ['field_editor' => $fromform];
$data = file_postupdate_standard_editor($data, 'field', $textoptions, $textoptions['context'],
@@ -145,6 +145,41 @@ class customfield_textarea_plugin_testcase extends advanced_testcase {
$handler->instance_form_save($data);
}
/**
* Test that instance form save empties the field content for blank values
*/
public function test_instance_form_save_clear(): void {
global $CFG;
require_once("{$CFG->dirroot}/customfield/tests/fixtures/test_instance_form.php");
$this->setAdminUser();
$handler = $this->cfcat->get_handler();
// Set our custom field to a known value.
$submitdata = (array) $this->courses[1] + [
'customfield_myfield1_editor' => ['text' => 'I can see it in your eyes', 'format' => FORMAT_HTML],
'customfield_myfield2_editor' => ['text' => 'I can see it in your smile', 'format' => FORMAT_HTML],
];
core_customfield_test_instance_form::mock_submit($submitdata, []);
$form = new core_customfield_test_instance_form('post', ['handler' => $handler, 'instance' => $this->courses[1]]);
$handler->instance_form_save($form->get_data());
$this->assertEquals($submitdata['customfield_myfield1_editor']['text'],
core_customfield\data_controller::create($this->cfdata[1]->get('id'))->export_value());
// Now empty our non-required field.
$submitdata['customfield_myfield1_editor']['text'] = '';
core_customfield_test_instance_form::mock_submit($submitdata, []);
$form = new core_customfield_test_instance_form('post', ['handler' => $handler, 'instance' => $this->courses[1]]);
$handler->instance_form_save($form->get_data());
$this->assertEmpty(core_customfield\data_controller::create($this->cfdata[1]->get('id'))->export_value());
}
/**
* Test for data_controller::get_value and export_value
*/
+77 -64
View File
@@ -31,83 +31,24 @@ defined('MOODLE_INTERNAL') || die();
* Activity name filtering
*/
class filter_activitynames extends moodle_text_filter {
// Trivial-cache - keyed on $cachedcourseid and $cacheduserid.
static $activitylist = null;
static $cachedcourseid;
static $cacheduserid;
function filter($text, array $options = array()) {
global $USER; // Since 2.7 we can finally start using globals in filters.
$coursectx = $this->context->get_course_context(false);
if (!$coursectx) {
return $text;
}
$courseid = $coursectx->instanceid;
// Initialise/invalidate our trivial cache if dealing with a different course.
if (!isset(self::$cachedcourseid) || self::$cachedcourseid !== (int)$courseid) {
self::$activitylist = null;
}
self::$cachedcourseid = (int)$courseid;
// And the same for user id.
if (!isset(self::$cacheduserid) || self::$cacheduserid !== (int)$USER->id) {
self::$activitylist = null;
}
self::$cacheduserid = (int)$USER->id;
/// It may be cached
if (is_null(self::$activitylist)) {
self::$activitylist = array();
$modinfo = get_fast_modinfo($courseid);
if (!empty($modinfo->cms)) {
self::$activitylist = array(); // We will store all the created filters here.
// Create array of visible activities sorted by the name length (we are only interested in properties name and url).
$sortedactivities = array();
foreach ($modinfo->cms as $cm) {
// Use normal access control and visibility, but exclude labels and hidden activities.
if ($cm->visible and $cm->has_view() and $cm->uservisible) {
$sortedactivities[] = (object)array(
'name' => $cm->name,
'url' => $cm->url,
'id' => $cm->id,
'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
);
}
}
// Sort activities by the length of the activity name in reverse order.
core_collator::asort_objects_by_property($sortedactivities, 'namelen', core_collator::SORT_NUMERIC);
foreach ($sortedactivities as $cm) {
$title = s(trim(strip_tags($cm->name)));
$currentname = trim($cm->name);
$entitisedname = s($currentname);
// Avoid empty or unlinkable activity names.
if (!empty($title)) {
$href_tag_begin = html_writer::start_tag('a',
array('class' => 'autolink', 'title' => $title,
'href' => $cm->url));
self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
if ($currentname != $entitisedname) {
// If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545.
self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
}
}
}
}
}
$activitylist = $this->get_cached_activity_list($courseid);
$filterslist = array();
if (self::$activitylist) {
if (!empty($activitylist)) {
$cmid = $this->context->instanceid;
if ($this->context->contextlevel == CONTEXT_MODULE && isset(self::$activitylist[$cmid])) {
if ($this->context->contextlevel == CONTEXT_MODULE && isset($activitylist[$cmid])) {
// remove filterobjects for the current module
$filterslist = array_values(array_diff_key(self::$activitylist, array($cmid => 1, $cmid.'-e' => 1)));
$filterslist = array_values(array_diff_key($activitylist, array($cmid => 1, $cmid.'-e' => 1)));
} else {
$filterslist = array_values(self::$activitylist);
$filterslist = array_values($activitylist);
}
}
@@ -117,4 +58,76 @@ class filter_activitynames extends moodle_text_filter {
return $text;
}
}
/**
* Get all the cached activity list for a course
*
* @param int $courseid id of the course
* @return filterobject[] the activities
*/
protected function get_cached_activity_list($courseid) {
global $USER;
$cached = cache::make_from_params(cache_store::MODE_REQUEST, 'filter', 'activitynames');
// Return cached activity list.
if ($cached->get('cachecourseid') == $courseid && $cached->get('cacheuserid') == $USER->id) {
return $cached->get('activitylist');
}
// Not cached yet, get activity list and set cache.
$activitylist = $this->get_activity_list($courseid);
$cached->set('cacheuserid', $USER->id);
$cached->set('cachecourseid', $courseid);
$cached->set('activitylist', $activitylist);
return $activitylist;
}
/**
* Get all the activity list for a course
*
* @param int $courseid id of the course
* @return filterobject[] the activities
*/
protected function get_activity_list($courseid) {
$activitylist = array();
$modinfo = get_fast_modinfo($courseid);
if (!empty($modinfo->cms)) {
$activitylist = array(); // We will store all the created filters here.
// Create array of visible activities sorted by the name length (we are only interested in properties name and url).
$sortedactivities = array();
foreach ($modinfo->cms as $cm) {
// Use normal access control and visibility, but exclude labels and hidden activities.
if ($cm->visible and $cm->has_view() and $cm->uservisible) {
$sortedactivities[] = (object)array(
'name' => $cm->name,
'url' => $cm->url,
'id' => $cm->id,
'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
);
}
}
// Sort activities by the length of the activity name in reverse order.
core_collator::asort_objects_by_property($sortedactivities, 'namelen', core_collator::SORT_NUMERIC);
foreach ($sortedactivities as $cm) {
$title = s(trim(strip_tags($cm->name)));
$currentname = trim($cm->name);
$entitisedname = s($currentname);
// Avoid empty or unlinkable activity names.
if (!empty($title)) {
$hreftagbegin = html_writer::start_tag('a',
array('class' => 'autolink', 'title' => $title,
'href' => $cm->url));
$activitylist[$cm->id] = new filterobject($currentname, $hreftagbegin, '</a>', false, true);
if ($currentname != $entitisedname) {
// If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545.
$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $hreftagbegin, '</a>', false, true);
}
}
}
}
return $activitylist;
}
}
@@ -102,4 +102,56 @@ class filter_activitynames_filter_testcase extends advanced_testcase {
$this->assertEquals($page->cmid, $matches[2][0]);
$this->assertEquals($page->name, $matches[3][0]);
}
public function test_cache() {
$this->resetAfterTest(true);
// Create a test courses.
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$context1 = context_course::instance($course1->id);
$context2 = context_course::instance($course2->id);
// Create page 1.
$page1 = $this->getDataGenerator()->create_module('page',
['course' => $course1->id, 'name' => 'Test 1']);
// Format text with page 1 in HTML.
$html = '<p>Please read the two pages Test 1 and Test 2.</p>';
$filtered1 = format_text($html, FORMAT_HTML, array('context' => $context1));
// Find all the activity links in the result.
$matches = [];
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
$filtered1, $matches);
// There should be 1 link.
$this->assertCount(1, $matches[1]);
$this->assertEquals($page1->name, $matches[1][0]);
// Create page 2.
$page2 = $this->getDataGenerator()->create_module('page',
['course' => $course1->id, 'name' => 'Test 2']);
// Filter the text again.
$filtered2 = format_text($html, FORMAT_HTML, array('context' => $context1));
// The filter result does not change due to caching.
$this->assertEquals($filtered1, $filtered2);
// Change context, so that cache for course 1 is cleared.
$filtered3 = format_text($html, FORMAT_HTML, array('context' => $context2));
$this->assertNotEquals($filtered1, $filtered3);
$matches = [];
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
$filtered3, $matches);
// There should be no links.
$this->assertCount(0, $matches[1]);
// Filter the text for course 1.
$filtered4 = format_text($html, FORMAT_HTML, array('context' => $context1));
// Find all the activity links in the result.
$matches = [];
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
$filtered4, $matches);
// There should be 2 links.
$this->assertCount(2, $matches[1]);
$this->assertEquals($page1->name, $matches[1][0]);
$this->assertEquals($page2->name, $matches[1][1]);
}
}
+1 -4
View File
@@ -399,10 +399,7 @@ class gradeimport_csv_load_data {
case 'useridnumber':
case 'useremail':
case 'username':
// Skip invalid row with blank user field.
if (!empty($value)) {
$this->studentid = $this->check_user_exists($value, $userfields[$mappingidentifier]);
}
$this->studentid = $this->check_user_exists($value, $userfields[$mappingidentifier]);
break;
case 'new':
$this->import_new_grade_item($header, $key, $value);
@@ -164,7 +164,7 @@ class grade extends tablelike implements selectable_items, filterable_items {
public function original_headers() {
return array(
'', // For filter icon.
get_string('firstname') . ' (' . get_string('alternatename') . ') ' . get_string('lastname'),
get_string('fullnameuser', 'core'),
get_string('range', 'grades'),
get_string('grade', 'grades'),
get_string('feedback', 'grades'),
@@ -198,8 +198,8 @@ class grade extends tablelike implements selectable_items, filterable_items {
$lockicon = $OUTPUT->pix_icon('t/locked', 'grade is locked') . ' ';
}
if (!empty($item->alternatename)) {
$fullname = $lockicon . $item->alternatename . ' (' . $item->firstname . ') ' . $item->lastname;
if (has_capability('moodle/site:viewfullnames', \context_course::instance($this->courseid))) {
$fullname = $lockicon . fullname($item, true);
} else {
$fullname = $lockicon . fullname($item);
}
@@ -161,12 +161,12 @@ class finalgrade extends grade_attribute_format implements unique_value, be_disa
}
if ($errorstr) {
$user = $DB->get_record('user', array('id' => $userid), 'id, firstname, alternatename, lastname');
$user = get_complete_user_data('id', $userid);
$gradestr = new stdClass;
if (!empty($user->alternatename)) {
$gradestr->username = $user->alternatename . ' (' . $user->firstname . ') ' . $user->lastname;
if (has_capability('moodle/site:viewfullnames', \context_course::instance($gradeitem->courseid))) {
$gradestr->username = fullname($user, true);
} else {
$gradestr->username = $user->firstname . ' ' . $user->lastname;
$gradestr->username = fullname($user);
}
$gradestr->itemname = $this->grade->grade_item->get_name();
$errorstr = get_string($errorstr, 'grades', $gradestr);
@@ -42,33 +42,33 @@ Feature: We can bulk insert grades for students in a course
And I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
And I follow "Single view for Test assignment one"
Then the field "Grade for james (Student) 1" matches value "50.00"
And the field "Override for james (Student) 1" matches value "0"
Then the field "Grade for Student 1" matches value "50.00"
And the field "Override for Student 1" matches value "0"
And I set the field "Perform bulk insert" to "1"
And I set the field "Insert value" to "1.0"
And I press "Save"
And I press "Continue"
And the field "Grade for james (Student) 1" matches value "50.00"
And the field "Override for james (Student) 1" matches value "0"
And the field "Grade for holly (Student) 2" matches value "1.00"
And the field "Override for holly (Student) 2" matches value "1"
And the field "Grade for anna (Student) 3" matches value "1.00"
And the field "Override for anna (Student) 3" matches value "1"
And the field "Grade for zac (Student) 4" matches value "1.00"
And the field "Override for zac (Student) 4" matches value "1"
And the field "Grade for Student 1" matches value "50.00"
And the field "Override for Student 1" matches value "0"
And the field "Grade for Student 2" matches value "1.00"
And the field "Override for Student 2" matches value "1"
And the field "Grade for Student 3" matches value "1.00"
And the field "Override for Student 3" matches value "1"
And the field "Grade for Student 4" matches value "1.00"
And the field "Override for Student 4" matches value "1"
And I set the field "For" to "All grades"
And I set the field "Perform bulk insert" to "1"
And I set the field "Insert value" to "2.0"
And I press "Save"
And I press "Continue"
And the field "Grade for james (Student) 1" matches value "2.00"
And the field "Override for james (Student) 1" matches value "1"
And the field "Grade for holly (Student) 2" matches value "2.00"
And the field "Override for holly (Student) 2" matches value "1"
And the field "Grade for anna (Student) 3" matches value "2.00"
And the field "Override for anna (Student) 3" matches value "1"
And the field "Grade for zac (Student) 4" matches value "2.00"
And the field "Override for zac (Student) 4" matches value "1"
And the field "Grade for Student 1" matches value "2.00"
And the field "Override for Student 1" matches value "1"
And the field "Grade for Student 2" matches value "2.00"
And the field "Override for Student 2" matches value "1"
And the field "Grade for Student 3" matches value "2.00"
And the field "Override for Student 3" matches value "1"
And the field "Grade for Student 4" matches value "2.00"
And the field "Override for Student 4" matches value "1"
Scenario: I can bulk insert grades and check their override flags for user view.
Given I log in as "teacher1"
@@ -110,8 +110,8 @@ Feature: We can bulk insert grades for students in a course
And I set the field "Perform bulk insert" to "1"
When I set the field "Insert value" to "-1"
And I press "Save"
Then I should see "The grade entered for Test assignment one for james (Student) 1 is less than the minimum allowed"
And I should see "The grade entered for Test assignment one for holly (Student) 2 is less than the minimum allowed"
And I should see "The grade entered for Test assignment one for anna (Student) 3 is less than the minimum allowed"
And I should see "The grade entered for Test assignment one for zac (Student) 4 is less than the minimum allowed"
Then I should see "The grade entered for Test assignment one for Student 1 is less than the minimum allowed"
And I should see "The grade entered for Test assignment one for Student 2 is less than the minimum allowed"
And I should see "The grade entered for Test assignment one for Student 3 is less than the minimum allowed"
And I should see "The grade entered for Test assignment one for Student 4 is less than the minimum allowed"
And I should see "Grades were set for 0 items"
@@ -9,13 +9,13 @@ Feature: We can use Single view
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber | alternatename |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | fred |
| teacher2 | No edit | 1 | teacher2@example.com | t2 | nick |
| student1 | Student | 1 | student1@example.com | s1 | james |
| student2 | Student | 2 | student1@example.com | s2 | holly |
| student3 | Student | 3 | student1@example.com | s3 | anna |
| student4 | Student | 4 | student1@example.com | s4 | zac |
| username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | | fred | | |
| teacher2 | No edit | 1 | teacher2@example.com | t2 | | nick | | |
| student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham |
| student2 | Niamh | Cholmondely | student2@example.com | s2 | Jane | Nina | Nee | Chumlee |
| student3 | Siobhan | Desforges | student3@example.com | s3 | Sarah | Sev | Shevon | De-forjay |
| student4 | Student | 4 | student4@example.com | s4 | | zac | | |
And the following "scales" exist:
| name | scale |
| Test Scale | Disappointing, Good, Very good, Excellent |
@@ -50,6 +50,9 @@ Feature: We can use Single view
| capability | permission | role | contextlevel | reference |
| moodle/grade:edit | Allow | teacher | Course | C1 |
| gradereport/singleview:view | Allow | teacher | Course | C1 |
And the following config values are set as admin:
| fullnamedisplay | firstnamephonetic,lastnamephonetic |
| alternativefullnameformat | middlename, alternatename, firstname, lastname |
And I log in as "teacher1"
And I am on "Course 1" course homepage
Given I navigate to "View > Grader report" in the course gradebook
@@ -57,7 +60,7 @@ Feature: We can use Single view
@javascript
Scenario: I can update grades, add feedback and exclude grades.
Given I navigate to "View > Single view" in the course gradebook
And I select "Student 4" from the "Select user..." singleselect
And I select "Student" from the "Select user..." singleselect
And I set the field "Override for Test assignment one" to "1"
When I set the following fields to these values:
| Grade for Test assignment one | 10.00 |
@@ -76,29 +79,29 @@ Feature: We can use Single view
And the field "Grade for Test grade item" matches value "45.00"
And the field "Grade for Course total" matches value "55.00"
And I click on "Show grades for Test assignment three" "link"
And I click on "Override for james (Student) 1" "checkbox"
And I click on "Override for Ann, Jill, Grainne, Beauchamp" "checkbox"
And I set the following fields to these values:
| Grade for james (Student) 1 | 12.05 |
| Feedback for james (Student) 1 | test data2 |
And I set the field "Exclude for holly (Student) 2" to "1"
| Grade for Ann, Jill, Grainne, Beauchamp | 12.05 |
| Feedback for Ann, Jill, Grainne, Beauchamp | test data2 |
And I set the field "Exclude for Jane, Nina, Niamh, Cholmondely" to "1"
And I press "Save"
Then I should see "Grades were set for 2 items"
And I press "Continue"
And the field "Grade for james (Student) 1" matches value "12.05"
And the field "Exclude for holly (Student) 2" matches value "1"
And the field "Grade for Ann, Jill, Grainne, Beauchamp" matches value "12.05"
And the field "Exclude for Jane, Nina, Niamh, Cholmondely" matches value "1"
And I select "new grade item 1" from the "Select grade item..." singleselect
And I set the field "Grade for james (Student) 1" to "Very good"
And I set the field "Grade for Ann, Jill, Grainne, Beauchamp" to "Very good"
And I press "Save"
Then I should see "Grades were set for 1 items"
And I press "Continue"
And the following should exist in the "generaltable" table:
| First name (Alternate name) Surname | Grade |
| james (Student) 1 | Very good |
| Ann, Jill, Grainne, Beauchamp | Very good |
And I log out
And I log in as "teacher2"
And I am on "Course 1" course homepage
Given I navigate to "View > Single view" in the course gradebook
And I select "Student 4" from the "Select user..." singleselect
And I select "Student" from the "Select user..." singleselect
And the "Exclude for Test assignment one" "checkbox" should be disabled
And the "Override for Test assignment one" "checkbox" should be enabled
@@ -106,12 +109,12 @@ Feature: We can use Single view
Given I follow "Single view for Test assignment one"
Then I should see "Test assignment one"
Then I navigate to "View > Grader report" in the course gradebook
And I follow "Single view for Student 1"
Then I should see "Student 1"
And I follow "Single view for Ann, Jill, Grainne, Beauchamp"
Then I should see "Gronya,Beecham"
Scenario: I can bulk update grades.
Given I follow "Single view for Student 1"
Then I should see "Student 1"
Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
Then I should see "Gronya,Beecham"
When I set the field "For" to "All grades"
And I set the field "Insert value" to "1.0"
And I set the field "Perform bulk insert" to "1"
@@ -119,12 +122,12 @@ Feature: We can use Single view
Then I should see "Grades were set for 6 items"
Scenario: Navigation works in the Single view.
Given I follow "Single view for Student 1"
Then I should see "Student 1"
And I follow "Student 2"
Then I should see "Student 2"
And I follow "Student 1"
Then I should see "Student 1"
Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
Then I should see "Gronya,Beecham"
And I follow "Nee,Chumlee"
Then I should see "Nee,Chumlee"
And I follow "Gronya,Beecham"
Then I should see "Gronya,Beecham"
And I click on "Show grades for Test assignment four" "link"
Then I should see "Test assignment four"
And I follow "Test assignment three"
@@ -134,7 +137,7 @@ Feature: We can use Single view
Scenario: Activities are clickable only when
it has a valid activity page.
Given I follow "Single view for Student 1"
Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
And "new grade item 1" "link" should not exist in the "//tbody//tr[position()=1]//td[position()=2]" "xpath_element"
Then "Category total" "link" should not exist in the "//tbody//tr[position()=2]//td[position()=2]" "xpath_element"
And "Course total" "link" should not exist in the "//tbody//tr[position()=last()]//td[position()=2]" "xpath_element"
+32 -28
View File
@@ -49,30 +49,37 @@ $PAGE->set_pagelayout('admin');
$returnurl = new moodle_url('/group/index.php', array('id'=>$id));
$mform_post = new groups_import_form(null, array('id'=>$id));
$importform = new groups_import_form(null, ['id' => $id]);
// If a file has been uploaded, then process it
if ($mform_post->is_cancelled()) {
if ($importform->is_cancelled()) {
redirect($returnurl);
} else if ($mform_post->get_data()) {
} else if ($formdata = $importform->get_data()) {
echo $OUTPUT->header();
$csv_encode = '/\&\#44/';
if (isset($CFG->CSV_DELIMITER)) {
$csv_delimiter = $CFG->CSV_DELIMITER;
if (isset($CFG->CSV_ENCODE)) {
$csv_encode = '/\&\#' . $CFG->CSV_ENCODE . '/';
}
} else {
$csv_delimiter = ",";
}
$text = $mform_post->get_file_content('userfile');
$text = preg_replace('!\r\n?!',"\n",$text);
$text = $importform->get_file_content('userfile');
$text = preg_replace('!\r\n?!', "\n", $text);
$rawlines = explode("\n", $text);
require_once($CFG->libdir . '/csvlib.class.php');
$importid = csv_import_reader::get_new_iid('groupimport');
$csvimport = new csv_import_reader($importid, 'groupimport');
$delimiter = $formdata->delimiter_name;
$encoding = $formdata->encoding;
$readcount = $csvimport->load_csv_content($text, $encoding, $delimiter);
if ($readcount === false) {
print_error('csvfileerror', 'error', $PAGE->url, $csvimport->get_error());
} else if ($readcount == 0) {
print_error('csvemptyfile', 'error', $PAGE->url, $csvimport->get_error());
} else if ($readcount == 1) {
print_error('csvnodata', 'error', $PAGE->url);
}
$csvimport->init();
unset($text);
// make arrays of valid fields for error checking
@@ -88,12 +95,12 @@ if ($mform_post->is_cancelled()) {
);
// --- get header (field names) ---
$header = explode($csv_delimiter, array_shift($rawlines));
$header = explode($csvimport::get_delimiter($delimiter), array_shift($rawlines));
// check for valid field names
foreach ($header as $i => $h) {
$h = trim($h); $header[$i] = $h; // remove whitespace
if (!(isset($required[$h]) or isset($optionalDefaults[$h]) or isset($optional[$h]))) {
print_error('invalidfieldname', 'error', 'import.php?id='.$id, $h);
print_error('invalidfieldname', 'error', $PAGE->url, $h);
}
if (isset($required[$h])) {
$required[$h] = 2;
@@ -102,32 +109,28 @@ if ($mform_post->is_cancelled()) {
// check for required fields
foreach ($required as $key => $value) {
if ($value < 2) {
print_error('fieldrequired', 'error', 'import.php?id='.$id, $key);
print_error('fieldrequired', 'error', $PAGE->url, $key);
}
}
$linenum = 2; // since header is line 1
foreach ($rawlines as $rawline) {
while ($line = $csvimport->next()) {
$newgroup = new stdClass();//to make Martin happy
foreach ($optionalDefaults as $key => $value) {
$newgroup->$key = current_language(); //defaults to current language
}
//Note: commas within a field should be encoded as &#44 (for comma separated csv files)
//Note: semicolon within a field should be encoded as &#59 (for semicolon separated csv files)
$line = explode($csv_delimiter, $rawline);
foreach ($line as $key => $value) {
//decode encoded commas
$record[$header[$key]] = preg_replace($csv_encode, $csv_delimiter, trim($value));
$record[$header[$key]] = trim($value);
}
if (trim($rawline) !== '') {
if (trim(implode($line)) !== '') {
// add a new group to the database
// add fields to object $user
foreach ($record as $name => $value) {
// check for required values
if (isset($required[$name]) and !$value) {
print_error('missingfield', 'error', 'import.php?id='.$id, $name);
print_error('missingfield', 'error', $PAGE->url, $name);
} else if ($name == "groupname") {
$newgroup->name = $value;
} else {
@@ -232,6 +235,7 @@ if ($mform_post->is_cancelled()) {
}
}
$csvimport->close();
echo $OUTPUT->single_button($returnurl, get_string('continue'), 'get');
echo $OUTPUT->footer();
die;
@@ -240,5 +244,5 @@ if ($mform_post->is_cancelled()) {
/// Print the form
echo $OUTPUT->header();
echo $OUTPUT->heading_with_help($strimportgroups, 'importgroups', 'core_group');
$mform_post ->display();
$importform->display();
echo $OUTPUT->footer();
+14
View File
@@ -27,6 +27,7 @@ if (!defined('MOODLE_INTERNAL')) {
}
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir . '/csvlib.class.php');
/**
* Groups import form class
@@ -56,6 +57,19 @@ class groups_import_form extends moodleform {
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$choices = csv_import_reader::get_delimiter_list();
$mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'group'), $choices);
if (array_key_exists('cfg', $choices)) {
$mform->setDefault('delimiter_name', 'cfg');
} else if (get_string('listsep', 'langconfig') == ';') {
$mform->setDefault('delimiter_name', 'semicolon');
} else {
$mform->setDefault('delimiter_name', 'comma');
}
$choices = core_text::get_encodings();
$mform->addElement('select', 'encoding', get_string('encoding', 'group'), $choices);
$mform->setDefault('encoding', 'UTF-8');
$this->add_action_buttons(true, get_string('importgroups', 'core_group'));
$this->set_data($data);
+36
View File
@@ -0,0 +1,36 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['clianswerno'] = 'n';
$string['cliansweryes'] = 'j';
$string['cliincorrectvalueretry'] = 'Falscha Wert, probiers nummal';
$string['cliyesnoprompt'] = 'druck j (bedeit ja) oda n (bedeit na)';
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'de';
$string['thislanguage'] = 'Bairisch';
+1 -1
View File
@@ -41,5 +41,5 @@ $string['cliunknowoption'] = 'Aukera ezezagunak:
Mesedez, erabili --help aukera.';
$string['cliyesnoprompt'] = 'idatzi b (bai esateko) edo e (ez esateko)';
$string['environmentrequireinstall'] = 'derrigorrezkoa da instalatuta eta gaituta izatea';
$string['environmentrequireversion'] = '{$a->needed} bertsioa beharrezkoa da eta zu {$a->current} ari zara egikaritzen';
$string['environmentrequireversion'] = '{$a->needed} bertsioa beharrezkoa da eta zu {$a->current} ari zara exekutatzen';
$string['upgradekeyset'] = 'Eguneraketa-kodea (utzi hutsik kodea erabili nahi ez baduzu)';
+1
View File
@@ -31,5 +31,6 @@
defined('MOODLE_INTERNAL') || die();
$string['language'] = 'भाषा';
$string['moodlelogo'] = 'Moodle लोगो';
$string['next'] = 'अगला';
$string['reload'] = 'सीमा से अधिक लादना';
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'hi';
$string['thislanguage'] = 'हिंदी';
+33
View File
@@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['thislanguage'] = 'Qaraqalpaq tili';
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['language'] = 'زبان';
$string['moodlelogo'] = 'لوگوی مودل (Moodle)';
$string['next'] = 'بعدی';
$string['previous'] = 'قبلی';
$string['reload'] = 'بارگیری مجدد';
+1 -1
View File
@@ -301,7 +301,7 @@ $string['error:invalidexpiredate'] = 'Expiry date has to be in the future.';
$string['error:invalidexpireperiod'] = 'Expiry period cannot be negative or equal 0.';
$string['error:invalidparambadge'] = 'Badge does not exist. ';
$string['error:noactivities'] = 'There are no activities with completion criteria enabled in this course.';
$string['error:nobadges'] = 'There are no course or site badges with access enabled to be added as criteria.';
$string['error:nobadges'] = 'There are currently no badges with access enabled to be added as criteria. A site badge can only have other site badges as criteria. A course badge can have other course badges or site badges as criteria.';
$string['error:invalidparamcohort'] = 'Cohort does not exist. ';
$string['error:noactivities'] = 'There are no activities with completion criteria enabled in this course.';
$string['error:nocohorts'] = 'No cohorts';
+3
View File
@@ -52,6 +52,9 @@ $string['courses'] = 'Courses';
$string['customexport'] = 'Custom range ({$a->timestart} - {$a->timeend})';
$string['daily'] = 'Daily';
$string['day'] = 'Day';
$string['dayeventsmany'] = '{$a->num} events, {$a->day}';
$string['dayeventsnone'] = 'No events, {$a}';
$string['dayeventsone'] = '1 event, {$a}';
$string['daynext'] = 'Next day';
$string['dayprev'] = 'Previous day';
$string['dayviewfor'] = 'Day view for:';
+4 -1
View File
@@ -194,10 +194,12 @@ $string['coursemisconf'] = 'Course is misconfigured';
$string['courserequestdisabled'] = 'Sorry, but course requests have been disabled by the administrator.';
$string['csvcolumnduplicates'] = 'Duplicate columns detected';
$string['csvemptyfile'] = 'The CSV file is empty';
$string['csvfileerror'] = 'There is something wrong with the format of the CSV file. Please check the number of headings and columns match, and that the delimiter and file encoding are correct: {$a}';
$string['csvfewcolumns'] = 'Not enough columns, please verify the delimiter setting';
$string['csvinvalidcols'] = '<b>Invalid CSV file:</b> First line must include "Header Fields" and the file must be type of <br />"Expanded Fields/Comma Separated"<br />or<br /> "Expanded Fields with CAVV Result Code/Comma Separated"';
$string['csvinvalidcolsnum'] = 'Invalid CSV file - each line must include 49 or 70 fields';
$string['csvloaderror'] = 'An error occurred while loading the CSV file: {$a}';
$string['csvnodata'] = 'Invalid CSV file - The CSV file has headers but does not contain any data.';
$string['csvweirdcolumns'] = 'Invalid CSV file format - number of columns is not constant!';
$string['dbconnectionfailed'] = '<p>Error: Database connection failed</p>
<p>It is possible that the database is overloaded or otherwise not running properly.</p>
@@ -220,6 +222,7 @@ $string['ddlxmlfileerror'] = 'XML database file errors found';
$string['destinationcmnotexit'] = 'The destination course module does not exist';
$string['detectedbrokenplugin'] = 'Plugin "{$a}" is defective or outdated, can not continue, sorry.';
$string['dmlexceptiononinstall'] = '<p>A database error has occurred [{$a->errorcode}].<br />{$a->debuginfo}</p>';
$string['dmlparseexception'] = 'Error parsing SQL query';
$string['dmlreadexception'] = 'Error reading from database';
$string['dmltransactionexception'] = 'Database transaction error';
$string['dmlwriteexception'] = 'Error writing to database';
@@ -434,7 +437,7 @@ $string['noparticipants'] = 'No participants found for this course';
$string['noparticipatorycms'] = 'Sorry, but you have no participatory course modules to report on';
$string['nopermissions'] = 'Sorry, but you do not currently have permissions to do that ({$a}).';
$string['nopermissiontocomment'] = 'You can\'t add comments';
$string['nopermissiontodelentry'] = 'You can\'t delete other people\'s entries!';
$string['nopermissiontodelentry'] = 'You can\'t delete this comment!';
$string['nopermissiontoeditcomment'] = 'You can\'t edit other people\'s comments!';
$string['nopermissiontohide'] = 'No permission to hide!';
$string['nopermissiontoimportact'] = 'You do not have the required permissions to import activities to this course';
+2
View File
@@ -43,6 +43,7 @@ $string['creategrouping'] = 'Create grouping';
$string['creategroupinselectedgrouping'] = 'Create group in grouping';
$string['createingrouping'] = 'Grouping of auto-created groups';
$string['createorphangroup'] = 'Create orphan group';
$string['csvdelimiter'] = 'CSV delimiter';
$string['databaseupgradegroups'] = 'Groups version is now {$a}';
$string['defaultgrouping'] = 'Default grouping';
$string['defaultgroupingname'] = 'Grouping';
@@ -59,6 +60,7 @@ $string['editgroupsettings'] = 'Edit group settings';
$string['editusersgroupsa'] = 'Edit groups for "{$a}"';
$string['enablemessaging'] = 'Group messaging';
$string['enablemessaging_help'] = 'If enabled, group members can send messages to the others in their group via the messaging drawer.';
$string['encoding'] = 'Encoding';
$string['enrolmentkey'] = 'Enrolment key';
$string['enrolmentkey_help'] = 'An enrolment key enables access to the course to be restricted to only those who know the key. If a group enrolment key is specified, then not only will entering that key let the user into the course, but it will also automatically make them a member of this group.
+1
View File
@@ -74,6 +74,7 @@ $string['group:html_track'] = 'HTML track files';
$string['group:html_video'] = 'Video files natively supported by browsers';
$string['group:image'] = 'Image files';
$string['group:media_source'] = 'Streaming media';
$string['group:optimised_image'] = 'Image files to be optimised, such as badges';
$string['group:presentation'] = 'Presentation files';
$string['group:sourcecode'] = 'Source code';
$string['group:spreadsheet'] = 'Spreadsheet files';
+2
View File
@@ -56,6 +56,8 @@ $string['cannotdelete'] = 'Cannot delete this file.';
$string['cannotdownload'] = 'Cannot download this file';
$string['cannotdownloaddir'] = 'Cannot download this folder';
$string['cannotinitplugin'] = 'Call plugin_init failed';
$string['cannotunzipcontentunreadable'] = 'Cannot unzip this file because the contents of the file cannot be read.';
$string['cannotunzipquotaexceeded'] = 'Cannot unzip this file because the maximum size allowed in this draft area will be exceeded.';
$string['cleancache'] = 'Clean my cache files';
$string['close'] = 'Close';
$string['commonrepositorysettings'] = 'Common repository settings';
+58
View File
@@ -521,6 +521,16 @@ function has_capability($capability, context $context, $user = null, $doanything
}
}
if (!empty($USER->loginascontext)) {
// The current user is logged in as another user and can assume their identity at or below the `loginascontext`
// defined in the USER session.
// The user may not assume their identity at any other location.
if (!$USER->loginascontext->is_parent_of($context, true)) {
// The context being checked is not the specified context, or one of its children.
return false;
}
}
// Find out if user is admin - it is not possible to override the doanything in any way
// and it is not possible to switch to admin role either.
if ($doanything) {
@@ -5552,6 +5562,30 @@ abstract class context extends stdClass implements IteratorAggregate {
return $result;
}
/**
* Determine if the current context is a parent of the possible child.
*
* @param context $possiblechild
* @param bool $includeself Whether to check the current context
* @return bool
*/
public function is_parent_of(context $possiblechild, bool $includeself): bool {
// A simple substring check is used on the context path.
// The possible child's path is used as a haystack, with the current context as the needle.
// The path is prefixed with '+' to ensure that the parent always starts at the top.
// It is suffixed with '+' to ensure that parents are not included.
// The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
// The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
// The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
$haystacksuffix = $includeself ? '/+' : '+';
$strpos = strpos(
"+{$possiblechild->path}{$haystacksuffix}",
"+{$this->path}/"
);
return $strpos === 0;
}
/**
* Returns parent contexts of this context in reversed order, i.e. parent first,
* then grand parent, etc.
@@ -5576,6 +5610,30 @@ abstract class context extends stdClass implements IteratorAggregate {
return $result;
}
/**
* Determine if the current context is a child of the possible parent.
*
* @param context $possibleparent
* @param bool $includeself Whether to check the current context
* @return bool
*/
public function is_child_of(context $possibleparent, bool $includeself): bool {
// A simple substring check is used on the context path.
// The current context is used as a haystack, with the possible parent as the needle.
// The path is prefixed with '+' to ensure that the parent always starts at the top.
// It is suffixed with '+' to ensure that children are not included.
// The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
// The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
// The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
$haystacksuffix = $includeself ? '/+' : '+';
$strpos = strpos(
"+{$this->path}{$haystacksuffix}",
"+{$possibleparent->path}/"
);
return $strpos === 0;
}
/**
* Returns parent context ids of this context in reversed order, i.e. parent first,
* then grand parent, etc.
+2 -2
View File
@@ -1200,7 +1200,7 @@ class admin_externalpage implements part_of_admin_tree {
/** @var string The external URL that we should link to when someone requests this external page. */
public $url;
/** @var string The role capability/permission a user must have to access this external page. */
/** @var array The role capability/permission a user must have to access this external page. */
public $req_capability;
/** @var object The context in which capability/permission should be checked, default is site context. */
@@ -1424,7 +1424,7 @@ class admin_settingpage implements part_of_admin_tree {
/** @var admin_settingdependency[] list of settings to hide when certain conditions are met */
protected $dependencies = [];
/** @var string The role capability/permission a user must have to access this external page. */
/** @var array The role capability/permission a user must have to access this external page. */
public $req_capability;
/** @var object The context in which capability/permission should be checked, default is site context. */
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core/checkbox-toggleall",["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){if(c){return a.find("[data-action=\"toggle\"][data-togglegroup=\""+b+"\"]")}else{return a.find("[data-action=\"toggle\"][data-togglegroup^=\""+b+"\"]")}},f=function(a,b){return e(a,b,!1).filter("[data-toggle=\"slave\"]")},g=function(a,b,c){return e(a,b,c).filter("[data-toggle=\"master\"]")},h=function(a,b){return e(a,b,!0).filter("[data-toggle=\"action\"]")},i=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i;if(g.is(":checkbox")){i=g.is(":checked")}else{i=1===g.data("checkall")}var j=f(e,h),k=j.filter(":checked");l(e,h,i,!1);j.prop("checked",i);j.trigger("change");b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:j,checkedSlaves:k,anyChecked:i})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup").split(" "),i=[],j="";h.forEach(function(a){j+=" "+a;i.push(j.trim())});i.forEach(function(a){var c=f(e,a),g=c.filter(":checked"),h=c.length===g.length;l(e,a,h,!0);k(e,a,!g.length);b.publish(d.checkboxToggled,{root:e,toggleGroupName:a,slaves:c,checkedSlaves:g,anyChecked:!!g.length})})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d);f.each(function(c,e){e=a(e);var f;if(d){f=e.data("toggle-deselectall")}else{f=e.data("toggle-selectall")}if(e.is(":checkbox")){var g=b.find("[for=\""+e.attr("id")+"\"]");if(g.length){if(g.html()!==f){g.html(f)}}}else{e.text(f);e.data("checkall",d?0:1)}})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("click","[data-action=\"toggle\"][data-toggle=\"master\"]",{root:b},i);b.on("change","[data-action=\"toggle\"][data-toggle=\"slave\"]",{root:b},j)}};return{init:function init(){m()},events:d}});
define ("core/checkbox-toggleall",["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){if(c){return a.find("[data-action=\"toggle\"][data-togglegroup=\""+b+"\"]")}else{return a.find("[data-action=\"toggle\"][data-togglegroup^=\""+b+"\"]")}},f=function(a,b){return e(a,b,!1).filter("[data-toggle=\"slave\"]")},g=function(a,b,c){return e(a,b,c).filter("[data-toggle=\"master\"]")},h=function(a,b){return e(a,b,!0).filter("[data-toggle=\"action\"]")},i=function(a,b){var c=f(a,b);if(0<c.length){var d=c.filter(":checked"),e=c.length===d.length;n(a,b,e,!0);m(a,b,!d.length)}},j=function(a){var b=a.split(" "),c=[],d="";b.forEach(function(a){d+=" "+a;c.push(d.trim())});return c},k=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),k;if(g.is(":checkbox")){k=g.is(":checked")}else{k=1===g.data("checkall")}var l=f(e,h);l.prop("checked",k).trigger("change");var o=l.filter(":checked");n(e,h,k,!1);m(e,h,!o.length);var p=j(h).filter(function(a){return a!==h});p.forEach(function(a){i(e,a)});b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:l,checkedSlaves:o,anyChecked:k})},l=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),k=f(e,h),l=k.filter(":checked"),m=j(h);m.forEach(function(a){i(e,a)});b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:k,checkedSlaves:l,anyChecked:!!l.length})},m=function(a,b,c){h(a,b).prop("disabled",c)},n=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d);f.each(function(c,e){e=a(e);var f;if(d){f=e.data("toggle-deselectall")}else{f=e.data("toggle-selectall")}if(e.is(":checkbox")){var g=b.find("[for=\""+e.attr("id")+"\"]");if(g.length){if(g.html()!==f){g.html(f)}}}else{e.text(f);e.data("checkall",d?0:1)}})},o=function(){if(!c){c=!0;var b=a(document.body);b.on("click","[data-action=\"toggle\"][data-toggle=\"master\"]",{root:b},k);b.on("click","[data-action=\"toggle\"][data-toggle=\"slave\"]",{root:b},l)}};return{init:function init(){o()},events:d}});
//# sourceMappingURL=checkbox-toggleall.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core/icon_system_fontawesome",["core/icon_system","jquery","core/ajax","core/mustache","core/localstorage","core/url"],function(a,b,c,d,e,f){var g=null,h=null,i=function(){a.apply(this,arguments)};i.prototype=Object.create(a.prototype);i.prototype.init=function(){if(g){return b.when(this)}var a=e.get("core/iconmap-fontawesome");if(a){a=JSON.parse(a)}if(a){g=a;return b.when(this)}if(null===h){h=c.call([{methodname:"core_output_load_fontawesome_icon_map",args:[]}],!0,!1,!1,0,M.cfg.themerev)[0]}return h.then(function(a){g={};b.each(a,function(a,b){g[b.component+"/"+b.pix]=b.to});e.set("core/iconmap-fontawesome",JSON.stringify(g));return this}.bind(this))};i.prototype.renderIcon=function(a,b,c,e){var h=g[b+"/"+a],i=!1;if("undefined"==typeof h){var j=f.imageUrl(a,b);i={attributes:[{name:"src",value:j},{name:"alt",value:c},{name:"title",value:c}]}}var k={key:h,title:c,alt:c,unmappedIcon:i};if("undefined"==typeof c||""===c){k["aria-hidden"]=!0}var l=d.render(e,k);return l.trim()};i.prototype.getTemplateName=function(){return"core/pix_icon_fontawesome"};return i});
define ("core/icon_system_fontawesome",["core/icon_system","jquery","core/ajax","core/mustache","core/localstorage","core/url"],function(a,b,c,d,e,f){var g=null,h=null,i=function(){a.apply(this,arguments)};i.prototype=Object.create(a.prototype);i.prototype.init=function(){if(g){return b.when(this)}var a=e.get("core/iconmap-fontawesome");if(a){a=JSON.parse(a)}if(a){g=a;return b.when(this)}if(null===h){h=c.call([{methodname:"core_output_load_fontawesome_icon_system_map",args:{themename:M.cfg.theme}}],!0,!1,!1,0,M.cfg.themerev)[0]}return h.then(function(a){g={};b.each(a,function(a,b){g[b.component+"/"+b.pix]=b.to});e.set("core/iconmap-fontawesome",JSON.stringify(g));return this}.bind(this))};i.prototype.renderIcon=function(a,b,c,e){var h=g[b+"/"+a],i=!1;if("undefined"==typeof h){var j=f.imageUrl(a,b);i={attributes:[{name:"src",value:j},{name:"alt",value:c},{name:"title",value:c}]}}var k={key:h,title:c,alt:c,unmappedIcon:i};if("undefined"==typeof c||""===c){k["aria-hidden"]=!0}var l=d.render(e,k);return l.trim()};i.prototype.getTemplateName=function(){return"core/pix_icon_fontawesome"};return i});
//# sourceMappingURL=icon_system_fontawesome.min.js.map
File diff suppressed because one or more lines are too long
+71 -29
View File
@@ -95,6 +95,45 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
return getToggleGroupElements(root, toggleGroup, true).filter('[data-toggle="action"]');
};
/**
* Toggles the master checkboxes and action elements in a given toggle group.
*
* @param {jQuery} root The root jQuery element.
* @param {String} toggleGroupName The name of the toggle group
*/
var toggleMastersAndActionElements = function(root, toggleGroupName) {
var toggleGroupSlaves = getAllSlaveCheckboxes(root, toggleGroupName);
if (toggleGroupSlaves.length > 0) {
var toggleGroupCheckedSlaves = toggleGroupSlaves.filter(':checked');
var targetState = toggleGroupSlaves.length === toggleGroupCheckedSlaves.length;
// Make sure to toggle the exact master checkbox in the given toggle group.
setMasterStates(root, toggleGroupName, targetState, true);
// Enable the action elements if there's at least one checkbox checked in the given toggle group.
// Disable otherwise.
setActionElementStates(root, toggleGroupName, !toggleGroupCheckedSlaves.length);
}
};
/**
* Returns an array containing every toggle group level of a given toggle group.
*
* @param {String} toggleGroupName The name of the toggle group
* @return {Array} toggleGroupLevels Array that contains every toggle group level of a given toggle group
*/
var getToggleGroupLevels = function(toggleGroupName) {
var toggleGroups = toggleGroupName.split(' ');
var toggleGroupLevels = [];
var toggleGroupLevel = '';
toggleGroups.forEach(function(toggleGroupName) {
toggleGroupLevel += ' ' + toggleGroupName;
toggleGroupLevels.push(toggleGroupLevel.trim());
});
return toggleGroupLevels;
};
/**
* Toggles the slave checkboxes in a given toggle group when a master element in that toggle group is toggled.
*
@@ -114,14 +153,26 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
}
var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
// Set the slave checkboxes from the masters and manually trigger the native 'change' event.
slaves.prop('checked', targetState).trigger('change');
// Get all checked slaves after the change of state.
var checkedSlaves = slaves.filter(':checked');
// Toggle the master checkbox in the given toggle group.
setMasterStates(root, toggleGroupName, targetState, false);
// Enable the action elements if there's at least one checkbox checked in the given toggle group. Disable otherwise.
setActionElementStates(root, toggleGroupName, !checkedSlaves.length);
// Set the slave checkboxes from the masters.
slaves.prop('checked', targetState);
// Trigger 'change' event to toggle other master checkboxes (e.g. parent master checkboxes) and action elements.
slaves.trigger('change');
// Get all toggle group levels and toggle accordingly all parent master checkboxes and action elements from each
// level. Exclude the given toggle group (toggleGroupName) as the master checkboxes and action elements from this
// level have been already toggled.
var toggleGroupLevels = getToggleGroupLevels(toggleGroupName)
.filter(toggleGroupLevel => toggleGroupLevel !== toggleGroupName);
toggleGroupLevels.forEach(function(toggleGroupLevel) {
// Toggle the master checkboxes action elements in the given toggle group level.
toggleMastersAndActionElements(root, toggleGroupLevel);
});
PubSub.publish(events.checkboxToggled, {
root: root,
@@ -142,33 +193,24 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
var toggleMastersFromSlaves = function(e) {
var root = e.data.root;
var target = $(e.target);
var toggleGroupName = target.data('togglegroup');
var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
var checkedSlaves = slaves.filter(':checked');
var toggleGroups = target.data('togglegroup').split(' ');
var toggleGroupLevels = [];
var toggleGroupLevel = '';
toggleGroups.forEach(function(toggleGroupName) {
toggleGroupLevel += ' ' + toggleGroupName;
toggleGroupLevels.push(toggleGroupLevel.trim());
// Get all toggle group levels for the given toggle group and toggle accordingly all master checkboxes
// and action elements from each level.
var toggleGroupLevels = getToggleGroupLevels(toggleGroupName);
toggleGroupLevels.forEach(function(toggleGroupLevel) {
// Toggle the master checkboxes action elements in the given toggle group level.
toggleMastersAndActionElements(root, toggleGroupLevel);
});
toggleGroupLevels.forEach(function(toggleGroupName) {
var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
var checkedSlaves = slaves.filter(':checked');
var targetState = (slaves.length === checkedSlaves.length);
// Make sure to toggle the exact master checkbox.
setMasterStates(root, toggleGroupName, targetState, true);
// Enable action elements when there's at least one checkbox checked. Disable otherwise.
setActionElementStates(root, toggleGroupName, !checkedSlaves.length);
PubSub.publish(events.checkboxToggled, {
root: root,
toggleGroupName: toggleGroupName,
slaves: slaves,
checkedSlaves: checkedSlaves,
anyChecked: !!checkedSlaves.length,
});
PubSub.publish(events.checkboxToggled, {
root: root,
toggleGroupName: toggleGroupName,
slaves: slaves,
checkedSlaves: checkedSlaves,
anyChecked: !!checkedSlaves.length,
});
};
@@ -233,7 +275,7 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
var root = $(document.body);
root.on('click', '[data-action="toggle"][data-toggle="master"]', {root: root}, toggleSlavesFromMasters);
root.on('change', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves);
root.on('click', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves);
}
};
+18 -1
View File
@@ -36,7 +36,9 @@ function($, log, str, templates, notification, LoadingIcon) {
SPACE: 32,
ESCAPE: 27,
COMMA: 44,
UP: 38
UP: 38,
LEFT: 37,
RIGHT: 39
};
var uniqueId = Date.now();
@@ -800,10 +802,24 @@ function($, log, str, templates, notification, LoadingIcon) {
// Remove it from the selection.
pendingPromise.resolve(deselectItem(options, state, $(e.currentTarget), originalSelect));
});
// Remove the highlight of items when user tabs out the tag list.
selectionElement.on('blur', function(e) {
e.preventDefault();
$(this).children().attr('data-active-selection', false).attr('id', '');
});
// When tag list is focused, highlight the first item.
selectionElement.on('focus', function(e) {
e.preventDefault();
var element = $(this).children('[data-active-selection=true]');
if (element && element.length === 0) {
activateNextSelection(state);
}
});
// Keyboard navigation for the selection list.
selectionElement.on('keydown', function(e) {
var pendingPromise = addPendingJSPromise('form-autocomplete-keydown-' + e.keyCode);
switch (e.keyCode) {
case KEYS.RIGHT:
case KEYS.DOWN:
// We handled this event, so prevent it.
e.preventDefault();
@@ -811,6 +827,7 @@ function($, log, str, templates, notification, LoadingIcon) {
// Choose the next selection item.
pendingPromise.resolve(activateNextSelection(state));
return false;
case KEYS.LEFT:
case KEYS.UP:
// We handled this event, so prevent it.
e.preventDefault();

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