Compare commits

..

313 Commits

Author SHA1 Message Date
Eloy Lafuente (stronk7) 25c9c6336b Moodle release 3.7.3 2019-11-09 11:57:47 +01:00
Sara Arjona a88baa553a weekly release 3.7.2+ 2019-11-08 15:39:46 +01:00
Sara Arjona 2b0e7a4ea9 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-11-08 15:39:43 +01:00
Adrian Greeve 46cb3afcfe Merge branch 'MDL-67169_37' of https://github.com/stronk7/moodle into MOODLE_37_STABLE 2019-11-08 10:44:19 +08:00
Jake Dallimore b7bb837d2b Merge branch 'MDL-67016-37-user-profile-see-own-courses' of https://github.com/danowar2k/moodle into MOODLE_37_STABLE 2019-11-08 10:22:26 +08:00
Andrew Nicols bf1e1b917f Merge branch 'MDL-67149-m37' of https://github.com/sammarshallou/moodle into MOODLE_37_STABLE 2019-11-08 10:10:36 +08:00
Sara Arjona 35c54f8ea8 Merge branch 'MDL-66683-37' of git://github.com/cescobedo/moodle into MOODLE_37_STABLE 2019-11-07 17:41:30 +01:00
cescobedo ffc5dc3245 MDL-66683 core: Revert MDL-66181 because created regressions
This reverts commit ed080a580f.
This issue reverts the behaviour modified in MDL-66181, as that change
negatively impacted course-creation workflows. As this appears to be
the intended functionality, the Security team has agreed to revert
this change.
2019-11-07 17:30:19 +01:00
Jake Dallimore 64f1c9d901 MDL-66257 tool_cohortroles: unit test for users in multiple cohorts 2019-11-07 23:47:33 +08:00
cescobedo b7ee5cb2ea MDL-66257 tool_cohortroles: Unassign the role on removal cohortroles. 2019-11-07 23:47:33 +08:00
Eloy Lafuente (stronk7) 0581a99241 MDL-67169 phpunit: fix clear flaw in unit test provider
Basically the provider is ignoring the CRLF to LF normalization
results and loading the original file again.

This doesn't have any impact normally, because all moodle
files are LF ones and people using other systems have their
git configurations set to work that way (not modify or force LF).

But there may be checkouts out there (for example travis) where
the git configuration by defult is to convert to the OS, causing
windows runs to fail badly there. See the issue for more info
and links.
2019-11-07 13:43:46 +01:00
Jun Pataleta d084e4194c Merge branch 'MDL-67084-37' of git://github.com/junpataleta/moodle into MOODLE_37_STABLE 2019-11-07 19:37:48 +08:00
Marina Glancy 56f3b3f412 MDL-67084 core_form: duration element should export arrays 2019-11-07 19:22:05 +08:00
Jun Pataleta 0ecc1a8eab MDL-66598 auth_oauth2: OAuth2 upgrade step
This basically does two things:
1. Updates all non-Facebook/Google/Microsoft issuers and sets their
'requireconfirmation' field to 1 so that future OAuth2 logins on these
issuers will always have an email confirmation sent to them.
2. Deletes the linked logins of users with non-Facebook/Google/Microsoft
OAuth2 issuers.
2019-11-07 18:43:30 +08:00
Jun Pataleta 6cb4bd65d3 MDL-66598 auth_oauth2: Add warning messages in confirm emails 2019-11-07 18:43:30 +08:00
Jun Pataleta 034729a5ce MDL-66598 tool_oauth2: Make account confirmation required by default
* Only Facebook, Google, and Microsoft issuers can optionally offer to
require account confirmation via email. We will require email
confirmation for the rest of the issuers.
2019-11-07 18:43:30 +08:00
Mihail Geshoski 4676d5ad35 MDL-66762 tool_dataprivacy: Sanitize the extra fields in the webservice 2019-11-07 09:02:35 +08:00
Paul Holden a722b44a09 MDL-66762 user: escape extra/email user fields. 2019-11-07 09:02:35 +08:00
Paul Holden 8d111d5136 MDL-66762 user: stricter email validation. 2019-11-07 09:02:34 +08:00
Eloy Lafuente (stronk7) a423038589 Merge branch 'MDL-42638-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-11-06 22:47:29 +01:00
sam marshall cff7de72ed MDL-67149 Accessibility: Help popups fail WCAG 1.4.13 (Dismissable)
It must be possible to dismiss the help popups (popovers) with the
Escape key, without changing focus.
2019-11-06 16:27:17 +00:00
Sara Arjona 18b60b3d2b Merge branch 'MDL-66984-change_password_url-with-user-MOODLE_37_STABLE' of https://github.com/brendanheywood/moodle into MOODLE_37_STABLE 2019-11-06 08:30:55 +01:00
AMOS bot fb9553cac0 Automatically generated installer lang files 2019-11-06 00:16:32 +00:00
Luca Bösch 2b746fc34a MDL-42638 filters: Have emoticons respect <pre>. 2019-11-05 16:09:20 +01:00
Michael Hawkins 87c1819e2f MDL-66161 output: Escaping fatal error message and URL strings 2019-11-05 13:47:43 +01:00
Juan Leyva 2e4684f51c MDL-66377 user: Only allow active users to retrieve files via tokenpluginfile.php 2019-11-05 13:47:43 +01:00
Paul Holden f68e327afa MDL-66228 mod_lesson: ensure return URL parameters are always local. 2019-11-05 13:47:43 +01:00
Eloy Lafuente (stronk7) 7471e92601 weekly release 3.7.2+ 2019-11-05 13:34:30 +01:00
Eloy Lafuente (stronk7) 5dbf43b0de Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-11-05 13:34:27 +01:00
Jake Dallimore 98c155a433 Merge branch 'MDL-66455-37' of git://github.com/lameze/moodle into MOODLE_37_STABLE 2019-11-05 11:43:06 +08:00
AMOS bot 7c2d448bf8 Automatically generated installer lang files 2019-11-05 00:10:27 +00:00
Brendan Heywood fb5ed6a5ca MDL-66984 auth: Allow $USER to be used in change_password_url 2019-11-05 10:24:48 +11:00
Jun Pataleta beed18a8e9 Merge branch 'MDL-66582-37' of git://github.com/jleyva/moodle into MOODLE_37_STABLE 2019-11-04 19:35:33 +08:00
Jake Dallimore 79d23bb46b Merge branch 'MDL-67093-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-11-04 15:40:30 +08:00
Adrian Greeve 145c57f2f9 Merge branch 'MDL-43189-37' of git://github.com/snake/moodle into MOODLE_37_STABLE 2019-11-04 09:24:13 +08:00
Eloy Lafuente (stronk7) 385e7e0c26 Merge branch 'MDL-66990-37-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_37_STABLE 2019-11-03 23:15:18 +01:00
AMOS bot e848d21f9e Automatically generated installer lang files 2019-11-02 00:09:30 +00:00
Helen Foster e8014f4833 MDL-66990 lang: Import fixed English strings (en_fix)
Significant string changes:

* hidegrader_help,mod_assign - note about setting having no effect on
  comments box
* configenablecompletion,core_completion - including reason for setting
  activity completion conditions
* setuplink_desc,tool_mobile - including where the app download page
  link is displayed and leaving the setting empty
2019-11-01 11:22:19 +01:00
Shamim Rezaie 66b2dcd7be MDL-43189 core: improve validating db settings during cli install 2019-11-01 15:41:36 +08:00
Shamim Rezaie f41a30229e MDL-43189 core: Validate db prefix during installation 2019-11-01 15:41:36 +08:00
Daniel Poggenpohl d8a7f2aeb7 MDL-67016 profile: current user should see his own courses 2019-10-31 14:36:43 +01:00
Juan Leyva e7cdd0e77c MDL-66582 tool_mobile: Clean URLs from auth plugins
In order to avoid the WebService to break we must double check that URLs coming from auth plugins are valid.
2019-10-31 14:22:51 +01:00
Johan Dobbelstein cf889e4fbd MDL-66582 auth: Fix empty auth_logo in CAS/Shibboleth auth plugins
* set $idp['iconurl'] = null if no auth_logo
* lib/templates/loginform.mustache will skip NULL iconurls
* lib/ajax/service?info=tool_mobile_get_public_config will validate the NULL values
2019-10-31 14:22:51 +01:00
Eloy Lafuente (stronk7) bb519b05b1 weekly release 3.7.2+ 2019-10-31 13:33:21 +01:00
Simey Lameze ad796334cb MDL-66455 calendar: add coverage for event types in subscription page 2019-10-31 18:48:05 +08:00
Simey Lameze 0e903c3f7f MDL-66455 calendar: show site level subscriptions 2019-10-31 18:48:04 +08:00
Adrian Greeve 63c0acc5e9 Merge branch 'MDL-66247-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-10-31 14:38:29 +08:00
Andrew Nicols 307600962c MDL-67093 grading: Fetch cm with get_fast_modinfo 2019-10-31 13:28:56 +08:00
Andrew Nicols d0cf886bf8 Merge branch 'MDL-67088-37' of git://github.com/marinaglancy/moodle into MOODLE_37_STABLE 2019-10-31 11:08:20 +08:00
Marina Glancy 0c8ad6a113 MDL-67088 mod_assign: compare time without case for MacOS compat 2019-10-30 19:20:02 +01:00
Andrew Nicols bff4d30a79 MDL-66247 accessibility: Correct notification alert role support 2019-10-30 09:01:18 +08:00
Eloy Lafuente (stronk7) 3d9b0a9b1b weekly release 3.7.2+ 2019-10-29 21:01:50 +01:00
Eloy Lafuente (stronk7) 5570436ef3 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-10-29 21:01:44 +01:00
Jake Dallimore 96d6f625f3 Merge branch 'MDL-66945_37' of git://github.com/stronk7/moodle into MOODLE_37_STABLE 2019-10-29 14:56:13 +08:00
Andrew Nicols 9c8a6c45f9 Merge branch 'MDL-67027-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-10-29 09:35:42 +08:00
Andrew Nicols be328441a2 MDL-66945 composer: Update behat extension
(using php71 for this branch)

This uses a new version of the moodlehq-behat-extension which ensures
that a node is visible and in the view port before it is clicked on.

This resolves an issue with Firefox on Ubuntu 18.04 whereby the node is
only brought into the viewport instead of clicked on.
2019-10-28 13:18:30 +01:00
Jun Pataleta e282b1f1fc Merge branch 'm37_MDL-65811_Fix_Incorrect_ORDER_BY_Search' of https://github.com/scara/moodle into MOODLE_37_STABLE 2019-10-28 11:30:40 +08:00
AMOS bot 28ccb81e87 Automatically generated installer lang files 2019-10-28 00:09:18 +00:00
AMOS bot bf84c3f075 Automatically generated installer lang files 2019-10-27 00:09:15 +00:00
Matteo Scaramuccia 429de834fa MDL-65811 database: Fix subsetting in MSSQL 2019-10-25 23:04:02 +02:00
Paul Holden 77c0308e74 MDL-67027 calendar: fix event update form validation. 2019-10-25 17:36:41 +01:00
Eloy Lafuente (stronk7) 991ac49b61 weekly release 3.7.2+ 2019-10-25 15:29:45 +02:00
Eloy Lafuente (stronk7) 56a0e6ed87 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-10-25 15:29:42 +02:00
Jake Dallimore 710bfa00e7 Merge branch 'MOODLE_37_MDL-66973' of https://github.com/golenkovm/moodle into MOODLE_37_STABLE 2019-10-25 11:10:18 +08:00
Jun Pataleta 5d70a8d5ad Merge branch 'MDL-66543-37' of git://github.com/tobiasreischmann/moodle into MOODLE_37_STABLE 2019-10-24 23:08:36 +08:00
Tobias Reischmann 39a390f1f5 MDL-66543 forms: Make (hard) frozen textareas readonly (or disabled) 2019-10-24 13:44:22 +02:00
Eloy Lafuente (stronk7) 978ed33fb1 Merge branch 'MDL-66935-lock-clashes-MOODLE_37_STABLE' of https://github.com/brendanheywood/moodle into MOODLE_37_STABLE 2019-10-24 10:03:46 +02:00
Adrian Greeve 666d420194 Merge branch 'MDL-66876-37' of git://github.com/jleyva/moodle into MOODLE_37_STABLE 2019-10-24 15:16:21 +08:00
Jun Pataleta bdf64b7eec Merge branch 'MDL-66927-run-new-adhoc-37' of https://github.com/brendanheywood/moodle into MOODLE_37_STABLE 2019-10-24 13:46:12 +08:00
Brendan Heywood 413b321344 MDL-66935 core_lock: Fix resource key clashes in db and postgres locks 2019-10-24 15:09:16 +11:00
AMOS bot 4497ed3938 Automatically generated installer lang files 2019-10-24 00:09:26 +00:00
Eloy Lafuente (stronk7) fc992c94af Merge branch 'MDL-64298-37' of git://github.com/jleyva/moodle into MOODLE_37_STABLE 2019-10-23 22:21:49 +02:00
Eloy Lafuente (stronk7) 7b04b19e06 Merge branch 'MDL-67001-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-10-23 22:17:34 +02:00
Sara Arjona 7dfa4d625b Merge branch 'MOODLE_37_STABLE_MDL-66225_async_backup_state' of https://github.com/mattporritt/moodle into MOODLE_37_STABLE 2019-10-23 17:39:22 +02:00
Juan Leyva 648ec6857b MDL-64298 gradereport_user: Fix WS returned parameter description
feedbackformat must be optional always
2019-10-23 17:16:24 +02:00
Juan Leyva f6f931728a MDL-66876 core_filter: Fix context in Web Service warning 2019-10-23 17:07:41 +02:00
Luca Bösch de254adfd3 MDL-67001 question: alignment and form spaces. 2019-10-23 11:00:11 +02:00
Matt Porritt bf0a0ed189 MDL-66225 Backup: fix irrecoverable state
Fix condition where if an asynchronous backup
completes via the UI and the user refreshes the
page a new backup was created with a mismatched
backup id.
This caused a permanently failing ad-hoc task
and blocked further backups for that course.
2019-10-23 04:18:42 +00:00
Adrian Greeve 76ebea9add Merge branch 'MDL-63125-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-10-23 11:07:10 +08:00
Sara Arjona 4fef7d1c87 weekly release 3.7.2+ 2019-10-22 14:48:08 +02:00
Sara Arjona 582a3baf70 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-10-22 14:48:05 +02:00
Paul Holden 9961411a12 MDL-63125 forms: Behat test for form autocomplete element. 2019-10-21 23:25:28 +01:00
Eloy Lafuente (stronk7) ace0fcf5b4 Merge branch 'MDL-66980_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-21 20:26:49 +02:00
Sara Arjona d3750e40ae Merge branch 'MDL-61121-37-5' of git://github.com/mihailges/moodle into MOODLE_37_STABLE 2019-10-21 14:46:29 +02:00
Tim Hunt 8e506aa53c MDL-66980 question bank: fix load_many_for_cache missing record check 2019-10-21 11:18:46 +01:00
Jun Pataleta 687e3b53c0 Merge branch 'MDL-66009-37' of https://github.com/grabs/moodle into MOODLE_37_STABLE 2019-10-21 16:15:13 +08:00
Jake Dallimore 285bc3e242 Merge branch 'MDL-66915-37' of https://github.com/MartinGauk/moodle into MOODLE_37_STABLE 2019-10-21 15:56:06 +08:00
Mikhail Golenkov 8dc826354d MDL-66973 search: Reset Content writer after test. 2019-10-21 17:48:45 +11:00
Andrew Nicols d5409002be Merge branch 'MDL-66962-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-10-21 14:23:27 +08:00
Luca Bösch 3e1e159efa MDL-66962 questions: Question bank table T title non breaking. 2019-10-21 08:17:15 +02:00
Andrew Nicols a4ed555b2f Merge branch 'MDL-66944_37' of https://github.com/t-schroeder/moodle into MOODLE_37_STABLE 2019-10-21 13:48:41 +08:00
Andrew Nicols 07b3e80ed1 Merge branch 'MDL-66946_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-21 12:56:54 +08:00
Jake Dallimore 023ebdb1af Merge branch 'MDL-66930-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-10-21 11:55:39 +08:00
Jake Dallimore b09d19d5bd Merge branch 'MDL-66960-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-10-21 10:41:44 +08:00
AMOS bot 3acc7fba61 Automatically generated installer lang files 2019-10-19 00:09:18 +00:00
Tim Schroeder 66039dbc91 MDL-66944 course: fixed copy-paste-error in comment 2019-10-18 22:47:11 +02:00
Tim Hunt 90766fb12b MDL-66946 quiz reports: incorrectly show teachers sometimes 2019-10-18 17:40:37 +01:00
Luca Bösch 3984d03fda MDL-66960 questions: Top align all question bank titles exc "select all" 2019-10-18 16:35:38 +02:00
Luca Bösch 0a9eae8b7a MDL-66930 quiz: Question type icon size/alignment in Boost and Classic. 2019-10-18 13:53:02 +02:00
Eloy Lafuente (stronk7) aac11a3c4b weekly release 3.7.2+ 2019-10-18 10:31:33 +02:00
Mihail Geshoski 84801cd656 MDL-61121 assign: Declare variables in the local scope 2019-10-18 13:59:58 +08:00
Mihail Geshoski 542e7b52f2 MDL-61121 assign: Improve accessibility in quick grading page tables 2019-10-18 13:59:58 +08:00
Mihail Geshoski cbc680eb33 MDL-61121 assign: Improve accessibility - view full/view summary control 2019-10-18 13:59:58 +08:00
Jun Pataleta 9a9542a238 Merge branch 'MDL-66806_37' of git://github.com/dmonllao/moodle into MOODLE_37_STABLE 2019-10-18 09:36:43 +08:00
Andrew Nicols 5b3308939d Merge branch 'MDL-66919_37' of https://github.com/stronk7/moodle into MOODLE_37_STABLE 2019-10-18 08:27:52 +08:00
Eloy Lafuente (stronk7) da348e315a Merge branch 'MDL-64064-m37' of https://github.com/NeillM/moodle into MOODLE_37_STABLE 2019-10-17 23:09:10 +02:00
Andreas Grabs ba8bc23906 MDL-66009 mod_feedback: Nested dependency fails again 2019-10-17 09:19:49 +02:00
Brendan Heywood 9c1f81775e MDL-66927 cron: Process new adhoc tasks immediately 2019-10-16 20:29:45 +11:00
Eloy Lafuente (stronk7) c6e6c935af MDL-66919 behat: fix typo in error message
Also fix a couple of phpdocs that were incorrect.
2019-10-16 00:48:55 +02:00
Eloy Lafuente (stronk7) 474159da70 weekly release 3.7.2+ 2019-10-15 23:00:02 +02:00
Martin Gauk 9ccc9ec175 MDL-66915 assign: Notify only if grade was updated in past 24 hours
Send notifications only if a grade was updated in the past 24 hours,
whether marking workflow is enabled or not.
2019-10-15 17:03:33 +02:00
Tim Hunt 2903b1ae22 MDL-66796 question bank: fix more bugs with category editing 2019-10-15 10:55:19 +08:00
Jun Pataleta 5316b73d41 Merge branch 'MDL-66821_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-15 07:36:58 +08:00
Andrew Nicols 2669dd7963 Merge branch 'MDL-66335_37' of https://github.com/stronk7/moodle into MOODLE_37_STABLE 2019-10-15 07:31:17 +08:00
Eloy Lafuente (stronk7) 4705bff0e1 MDL-66335 behat: Avoid double processing the page type
resolve_page_instance_helper() already processes the type, returning
the correct context that should be processing the navigation URL.

With that extra call to parse_page_name() the 2nd call always returns
"core", ultimately leading to tons of behat failures because "core" is
not aware of those (plugin, quiz for now) pages typology.
2019-10-15 00:02:41 +02:00
Eloy Lafuente (stronk7) 19bd02eabd Merge branch 'MDL-66335_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-14 18:55:37 +02:00
Paul Holden 0701f2f08c MDL-63125 forms: allow item removal from single-select autocomplete. 2019-10-14 17:07:54 +01:00
Eloy Lafuente (stronk7) d18f809ec4 Merge branch 'MDL-66559-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-10-14 16:37:30 +02:00
Tim Hunt 87252eae2c MDL-66335 quiz behat: using the new generic navigation steps
This converts all the navigation to quiz pages when that is not
the thing under test. Also, I decided that the Quiz settings form
belonged to the course, so did not do that here.
2019-10-14 13:13:03 +01:00
Tim Hunt df143c2923 MDL-66335 behat: new step to log in and go straight to a particular page
I did not think thought through if this new URL parameter would be a
potential security hole. The simple cop-out option is to make it only
work on Behat sites for now.
2019-10-14 13:13:01 +01:00
Tim Hunt 621e8888e5 MDL-66335 behat: generic step for navigating direct to specific pages 2019-10-14 13:12:59 +01:00
Tim Hunt ff76a9d590 MDL-66821 question behat: new step for acting on questions in the bank
There is a proposed change MDL-66816 which will change the question
bank UI. This will break any Behat tests which uses low-level steps
like

    When I click on "Duplicate" "link" in the "Test question" "table_row"

to perform an action on a question in the question bank. This commit
introduces a new step:

    When I choose "Duplicate" action for "Test question" in the question bank

This commit also converts all core Behat tests to use the new step.
2019-10-14 11:55:56 +01:00
Sara Arjona a9a9fd455a Merge branch 'MDL-66836-m37' of https://github.com/sammarshallou/moodle into MOODLE_37_STABLE 2019-10-14 12:27:10 +02:00
Jake Dallimore 81bbf95756 Merge branch 'MDL-66659-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-10-14 15:33:38 +08:00
Andrew Nicols 30702cc0bd MDL-66559 behat: Register component selectors earlier 2019-10-14 14:24:05 +08:00
Andrew Nicols 5eb80c4af6 MDL-66559 behat: Update running suite 2019-10-14 14:17:47 +08:00
Jun Pataleta 1f19dc8237 Merge branch 'MDL-66796-37-fix' of https://github.com/snake/moodle into MOODLE_37_STABLE 2019-10-14 13:48:24 +08:00
Jake Dallimore ca42dbc0dd MDL-66796 mod_quiz: only update the id number if changed
If the idnumber is present in the DB, it's set to null. In this case,
don't update the value.
2019-10-14 13:40:05 +08:00
Jake Dallimore 04ed69e387 Merge branch 'MDL-66559-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-10-14 11:10:19 +08:00
Jake Dallimore b30c16ec86 Merge branch 'MDL-66796_37' of git://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-14 07:41:48 +08:00
Eloy Lafuente (stronk7) 532b7c9462 Merge branch 'MDL-66792-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-10-13 17:10:27 +02:00
Eloy Lafuente (stronk7) d09616d194 Merge branch 'MDL-66851-37' of git://github.com/mihailges/moodle into MOODLE_37_STABLE 2019-10-13 16:49:48 +02:00
Eloy Lafuente (stronk7) 6c06678c34 Merge branch 'MDL-65243-37' of git://github.com/mihailges/moodle into MOODLE_37_STABLE 2019-10-13 16:31:27 +02:00
Eloy Lafuente (stronk7) b089becae9 Merge branch 'MDL-65515-37' of git://github.com/mihailges/moodle into MOODLE_37_STABLE 2019-10-13 16:13:07 +02:00
Eloy Lafuente (stronk7) 703d8c2784 Merge branch 'MDL-66779-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-10-13 02:12:50 +02:00
Eloy Lafuente (stronk7) 2a7ae663c5 Merge branch 'MOODLE_37_MDL-66864' of https://github.com/golenkovm/moodle into MOODLE_37_STABLE 2019-10-13 02:08:44 +02:00
Eloy Lafuente (stronk7) b303604bf1 Merge branch 'MDL-65584-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-10-13 01:49:47 +02:00
Eloy Lafuente (stronk7) 9f0a5cf2f5 weekly release 3.7.2+ 2019-10-11 22:43:54 +02:00
Eloy Lafuente (stronk7) 75232c7d34 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-10-11 22:43:42 +02:00
Neill Magill 0e57a3dede MDL-64064 block_timeline: Confusing when event is not in this year
If an event is not in the current year it can be confusing as the
months and days may appear to be in the wrong order.

This change adds the year to the displayed date to make the reason
for the order clear.
2019-10-11 08:38:29 +01:00
Tim Hunt d9dc216897 MDL-66559 behat: Documentation for component selectors 2019-10-11 12:58:16 +08:00
Andrew Nicols 814f15a05d MDL-66559 message: Move behat selectors to message subsystem 2019-10-11 12:58:16 +08:00
Andrew Nicols 50cd119caa MDL-66559 behat: Allow per-component selectors and replacements 2019-10-11 12:58:16 +08:00
Jake Dallimore caaf2427a7 Merge branch 'MDL-66804-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-10-11 09:58:16 +08:00
AMOS bot 88c69c2dcf Automatically generated installer lang files 2019-10-11 00:11:35 +00:00
Eloy Lafuente (stronk7) 4398cdace0 Merge branch 'MDL-66815_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-11 00:50:42 +02:00
David Monllaó 519142291c MDL-66806 analytics: No predictions for hidden courses 2019-10-10 10:09:28 +08:00
AMOS bot 20b96798d7 Automatically generated installer lang files 2019-10-10 00:09:26 +00:00
sam marshall 3816bd8776 MDL-66836 Behat: Reset unknown config variables between scenarios
In the Behat CLI run (not in the individual web requests), if you
modified an existing $CFG variable then this would be reset at the
start of the next scenario. However, if you added a completely new
one, it would not be reset. This change removes those variables.
2019-10-09 17:49:24 +01:00
Jun Pataleta 760ffcd6bf Merge branch 'MDL-66835_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-09 20:44:36 +08:00
Jun Pataleta 76bd81c69c Merge branch 'MDL-66450-37' of git://github.com/bmbrands/moodle into MOODLE_37_STABLE 2019-10-09 20:39:59 +08:00
Bas Brands 4a8e6db894 MDL-66450 themes: form autocomplete truncate listitems 2019-10-09 14:27:08 +02:00
Tim Hunt f5fbfb4d6b MDL-66835 behat: steps for setting/checking fields in containers 2019-10-09 13:24:56 +01:00
Sara Arjona 621f04de6a Merge branch 'MDL-66852-37' of github.com:lucaboesch/moodle into MOODLE_37_STABLE 2019-10-09 12:58:07 +02:00
Damyon Wiese 93d309cce2 MDL-65243 badges: Use real time api
Never add or subtract timestamps - use the time api to do it correctly.
2019-10-09 11:55:22 +08:00
Mihail Geshoski 53cc513636 MDL-65515 badges: Split install/upgrade
Use badges/upgradelib.php for functions that only affect install/upgrade.

All credit goes to Damyon Wiese.
2019-10-09 11:43:43 +08:00
Adrian Greeve bf4f5df33e Merge branch 'MDL-66011-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-10-09 10:51:29 +08:00
Andrew Nicols d784d3751c MDL-66779 phpunit: Correct component name for core_grades 2019-10-09 09:35:27 +08:00
Andrew Nicols 7ef7de84a2 Merge branch 'MDL-66118-37-byebyemoodlenet' of git://github.com/mudrd8mz/moodle into MOODLE_37_STABLE 2019-10-09 08:51:21 +08:00
Luca Bösch 259bbd1960 MDL-66852 quiz: Style "Try another question like this one" button. 2019-10-08 11:27:22 +02:00
Eloy Lafuente (stronk7) 765579d021 weekly release 3.7.2+ 2019-10-08 10:43:35 +02:00
Eloy Lafuente (stronk7) a7e0f274f5 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-10-08 10:43:28 +02:00
Mihail Geshoski bdef5416c3 MDL-66851 block_starredcourses: Hide 'hidden' courses from students
Thanks to Mark Sharp for the bug report and fix suggestion.
2019-10-08 09:54:15 +08:00
AMOS bot ad35c1ca2f Automatically generated installer lang files 2019-10-08 00:10:05 +00:00
Paul Holden 958b331ada MDL-65584 blog: safer deletion of associations in privacy provider.
It's possible that the given context list contains no posts, which caused
an exception to be thrown when passing an empty array to get_in_or_equal.
2019-10-08 00:23:40 +01:00
Eloy Lafuente (stronk7) 470ba08790 Merge branch 'MDL-66801_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-08 01:16:36 +02:00
Eloy Lafuente (stronk7) 7ac967501b Merge branch 'MDL-66734-37' of https://github.com/snake/moodle into MOODLE_37_STABLE 2019-10-07 23:04:10 +02:00
Eloy Lafuente (stronk7) 4bb12ff74d Merge branch 'MDL-66764_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-07 22:51:11 +02:00
Eloy Lafuente (stronk7) 0ffb67352b Merge branch 'MDL-66750_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-10-07 22:00:13 +02:00
Adrian Greeve 713a5cdb97 Merge branch 'MOODLE_37-MDL-66794' of https://github.com/golenkovm/moodle into MOODLE_37_STABLE 2019-10-07 14:30:53 +08:00
Mikhail Golenkov bf61d3f2b7 MDL-66864 upgrade: Delete non-mutual contacts in chunks. 2019-10-07 16:28:16 +11:00
Marina Glancy f7ea6feec7 MDL-65187 core_hub: truncate registration request, use post 2019-10-07 11:46:31 +08:00
Marina Glancy b46eb4266d MDL-65187 core_hub: send more important info first to moodle.net
Rumor has it that some browsers may truncate the GET request to 2000 characters,
make sure that all important info is sent first
Everything else will be updated on the first registration update request.
2019-10-07 11:46:20 +08:00
David Mudrák e8bc91383d MDL-66118 hub: Remove the mentions of moodle.net
This is an update of the site registration admin page and some relevant
backend code to remove the mentions of moodle.net.

AMOS BEGIN
 CPY [registerwithmoodleorg,core_admin],[registerwithmoodleorg,core_hub]
AMOS END
2019-10-04 21:38:07 +02:00
David Mudrák 75bb4372ca MDL-66118 hub: Stop using the hub URL as the setting name suffix
Site indicators shared with the registration hub used to have the hub
URL in the relevant setting names - such as site_name_httpsmoodlenet.
This upgrade step converts all such settings and drops the URL suffix
because we do not support alternative hubs any more.

Additionally the upgrade step renames the official hub and updates its
URL so that existing registrations with https://moodle.net are still
valid.
2019-10-04 21:38:07 +02:00
David Mudrák 1d0dc3102a MDL-66118 hub: Define the new sites directory URL
The Moodle sites now register with stats.moodle.org so use that as a new
official hub's URL. Beside that, allow for the alternative value be
provided in config.php for development and testing purposes.
2019-10-04 21:34:53 +02:00
Eloy Lafuente (stronk7) 8b89ab5798 weekly release 3.7.2+ 2019-10-04 16:01:45 +02:00
Eloy Lafuente (stronk7) 67ccb85794 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-10-04 16:01:39 +02:00
Jun Pataleta 60e2e906f1 Merge branch 'MDL-65315-37' of git://github.com/peterRd/moodle into MOODLE_37_STABLE 2019-10-04 10:25:07 +08:00
Andrew Nicols aeccf1b188 MDL-66011 test: remove magic @covers lines 2019-10-04 09:40:50 +08:00
Andrew Nicols 512320cd6c MDL-66011 tests: Stop forcing coverage annoation 2019-10-04 09:40:50 +08:00
AMOS bot aefc7f7f70 Automatically generated installer lang files 2019-10-04 00:11:51 +00:00
Eloy Lafuente (stronk7) 2ab788bcf8 Merge branch 'MDL-56649-37' of git://github.com/bmbrands/moodle into MOODLE_37_STABLE 2019-10-03 19:37:24 +02:00
Jun Pataleta 2ecdf811f3 Merge branch 'MDL-61450-37' of git://github.com/lameze/moodle into MOODLE_37_STABLE 2019-10-03 15:42:08 +08:00
Jun Pataleta 8a6d5c8c88 MDL-61450 calendar: Unit test for calendar_view_event_allowed()
* Unit test for calendar_view_event_allowed() when dealing with course
events.
2019-10-03 15:39:17 +08:00
Simey Lameze 765ef31f00 MDL-61450 calendar: fix course event type permissions 2019-10-03 15:39:17 +08:00
Eloy Lafuente (stronk7) e011fc3dfb Merge branch 'MDL-66774-37-macedonia' of git://github.com/mudrd8mz/moodle into MOODLE_37_STABLE 2019-10-03 00:58:25 +02:00
Eloy Lafuente (stronk7) b08e192757 Merge branch 'MDL-66197-37' of git://github.com/marinaglancy/moodle into MOODLE_37_STABLE 2019-10-03 00:45:42 +02:00
Eloy Lafuente (stronk7) a4ec901a4d Merge branch 'MDL-65957-37' of git://github.com/mihailges/moodle into MOODLE_37_STABLE 2019-10-02 16:49:39 +02:00
Mihail Geshoski a99105cc33 MDL-65957 repository_upload: Throw exception if @_file_upload is missing 2019-10-02 09:27:32 +08:00
Eloy Lafuente (stronk7) 8278f79219 Merge branch 'MDL-65335_37_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_37_STABLE 2019-10-01 23:22:32 +02:00
Tim Hunt 5fa3dccf4b MDL-66815 behat: question category generator must find right parent id
This is implemented in a bit of a hacky way, because the only other
alternative would be a large rewrite of the whole class, which is
not feasible for me right now. Note that many other types of thing
that can be generated suffer from the same issue. (E.g. if you ever
wanted to generate two groups with the same name in two different
courses and then add group memebers.)

It is worth doing a special-case fix for question categories, because
way Moodle works, it is inevitable that we end up with many categories
called 'top' in the database.
2019-10-01 16:48:15 +01:00
Eloy Lafuente (stronk7) b306a159de Merge branch 'MDL-65522-m37' of https://github.com/NeillM/moodle into MOODLE_37_STABLE 2019-10-01 16:10:34 +02:00
Eloy Lafuente (stronk7) 14d1be23c1 Merge branch 'MDL-66542_37' of https://github.com/t-schroeder/moodle into MOODLE_37_STABLE 2019-10-01 15:58:20 +02:00
Paul Holden 3f4935dfda MDL-66659 core: observe viewfullnames capability in flexible_table. 2019-10-01 10:15:15 +01:00
Jake Dallimore 3958f4a302 MDL-66734 core_comment: fix get_comment ordering when timestamps match
Include id in the sorting, to be sure that we always get the correct
record in cases where the comment timestamps are the same.
2019-10-01 09:18:56 +08:00
Paul Holden 3c7f5e8472 MDL-66792 tool_dataprivacy: correct fa classes in privacy registry. 2019-09-30 23:54:52 +01:00
Paul Holden 7db4bf3b34 MDL-66804 user: consistent user access times in privacy export. 2019-09-30 23:14:55 +01:00
Eloy Lafuente (stronk7) baa5130e0a Merge branch 'MDL-65876-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-10-01 00:07:41 +02:00
Tim Hunt 01d1ae9a5b MDL-66801 questions: XML import/export should include category idnumbers 2019-09-30 19:00:20 +01:00
Tim Hunt 1fa5fe2104 MDL-66796 question bank: 0 is a valid question category idnumber 2019-09-30 14:19:37 +01:00
Tim Schroeder c91312545d MDL-66542 lib: Fix quote escaping in JS quote helper 2019-09-30 10:17:59 +02:00
Neill Magill 68e8070295 MDL-65522 block_social_activities: Improve behat steps documentation
The documentation for these steps was not helpful in the Acceptance
testing admin tool page.
2019-09-30 08:58:51 +01:00
Mikhail Golenkov c2ba01fe9e MDL-66794 core_customfield: Reset Course handler after unit tests run. 2019-09-30 11:12:33 +10:00
Luca Bösch 6714c137c8 MDL-65876 forum: same arrows forum/activity navigation 2019-09-27 14:20:09 +02:00
Jun Pataleta 31f8f75672 weekly release 3.7.2+ 2019-09-27 11:12:23 +08:00
Jun Pataleta 8d268a8d04 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-09-27 11:12:22 +08:00
David Mudrák ac210ad583 MDL-66774 lang: Update the list of countries as per ISO 3166-1
Source: https://www.iso.org/obp/ui/

Two significant changes are:

* North Macedonia
* Eswatini

All other changes are formal, such as changing the letter case of the
"And" (the current ISO uses the lower case "and").  Our own existing
modifications of the list (such as having just "United States" and
"United Kingdom" instead of the full long name) were kept.
2019-09-26 10:44:56 +02:00
Jun Pataleta 29d555ab48 Merge branch 'MDL-66262_37' of git://github.com/stronk7/moodle into MOODLE_37_STABLE 2019-09-26 11:34:39 +08:00
Eloy Lafuente (stronk7) 86fa1ca83d Merge branch 'MDL-66106-37' of https://github.com/snake/moodle into MOODLE_37_STABLE 2019-09-26 00:54:25 +02:00
Jake Dallimore 8a5783c2be MDL-66169 admin: properly set debugsmtp, which is a config.php only var
This may not be set, so care must be taken to conditionally reinstate
the value, once the test email has been sent.
2019-09-26 00:48:30 +02:00
Eloy Lafuente (stronk7) fc71bf1e4a Merge branch 'MDL-51327-37' of git://github.com/mihailges/moodle into MOODLE_37_STABLE 2019-09-26 00:04:53 +02:00
Eloy Lafuente (stronk7) 0845113531 Merge branch 'MDL-66685_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-09-25 23:57:55 +02:00
Eloy Lafuente (stronk7) c131927ae5 Merge branch 'MDL-66675-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-09-25 20:06:11 +02:00
Marina Glancy f1b71b0199 MDL-66197 core: correct get parent language 2019-09-25 17:41:53 +02:00
Bas Brands 952b0caf57 MDL-56649 themes: filepicker file exists buttons spacing 2019-09-25 12:20:39 +02:00
Tim Hunt 2366f12757 MDL-66764 quiz: redo question should not repeat random questions
Before this fix, it was only considering questions seen in previous attempts.

It should avoid the questions in the current attmept too.
2019-09-25 11:05:13 +01:00
Jun Pataleta 3135b6f694 Merge branch 'MDL-66354-37' of http://github.com/dravek/moodle into MOODLE_37_STABLE 2019-09-25 16:42:59 +08:00
David Matamoros 4be324b42a MDL-66354 core_calendar: Notices in error.log on calendar events 2019-09-25 09:11:32 +02:00
Jun Pataleta 2d887636e6 Merge branch 'MDL-66644-37-versionrequires' of git://github.com/mudrd8mz/moodle into MOODLE_37_STABLE 2019-09-25 14:53:44 +08:00
Adrian Greeve 4e0b890955 Merge branch 'wip_MDL-66656_m37' of git://github.com/gjb2048/moodle into MOODLE_37_STABLE 2019-09-25 11:41:18 +08:00
Eloy Lafuente (stronk7) ee1fac1fd8 Merge branch 'MDL-65621-dimmed-courses-MOODLE_3.7' of https://github.com/stopfstedt/moodle into MOODLE_37_STABLE 2019-09-24 19:08:37 +02:00
David Monllaó 0a397e3fd2 MDL-66394 analytics: Extra GC during analysis
(backport of MDL-66234)
2019-09-24 14:08:24 +02:00
Mihail Geshoski f4397e3098 MDL-51327 core_course: Allow subdirs in edit section form 2019-09-24 09:08:54 +08:00
AMOS bot cc2932aefa Automatically generated installer lang files 2019-09-24 00:09:34 +00:00
Peter ce20bd7a73 MDL-65315 badges: Update the class and issuer OBv2.0 url 2019-09-24 07:59:18 +08:00
Tim Hunt f5f5bfbdf7 MDL-66750 formslib selects: handle size attribute properly 2019-09-23 12:15:08 +01:00
AMOS bot e3e8ee312b Automatically generated installer lang files 2019-09-22 00:09:19 +00:00
Gareth Barnard 5f2dd1bd50 MDL-66656 format_social: Edit Summary Icon leads to 'File not found'. 2019-09-20 21:46:43 +01:00
Sara Arjona 26c79027ff Merge branch 'MDL-46978-MOODLE_37_STABLE' of github.com:tuanngocnguyen/moodle into MOODLE_37_STABLE 2019-09-20 13:07:39 +02:00
Sara Arjona cfbcdc97e5 Merge branch 'MDL-66090_37' of git://github.com/dmonllao/moodle into MOODLE_37_STABLE 2019-09-20 12:46:24 +02:00
Mark Johnson 6bdfddc463 MDL-65335 auth: Make oauth2 enabled check more efficient 2019-09-20 08:55:32 +01:00
Jake Dallimore 481d6f6387 Merge branch 'MDL-65970-37' of git://github.com/jleyva/moodle into MOODLE_37_STABLE 2019-09-20 14:28:36 +08:00
Jake Dallimore a174ed3736 MDL-66106 core_form: fix client side validation bug caused by MDL-65217
Forms with randomised ids need to support client side validation.
2019-09-20 09:04:37 +08:00
Sara Arjona eb77088991 weekly release 3.7.2+ 2019-09-19 21:19:19 +02:00
Stefan Topfstedt a9c3e2f95e MDL-66433 block_myoverview: tag courses as "hidden from students"
This is intended to visually separate hidden course from open courses in the course overview block
by applying a "Hidden from students" tag to the various views in this block.
To achive this, the course summary exporter's output must be augmented with a "visiblity" attribute,
carrying the given course's corresponding visibility setting.
Since the exporter now expects this data point as part of its input, the output of the course_get_recent_courses()
function had to also be augmented with the course visibility setting to provide this value.
2019-09-19 10:11:03 -07:00
Nathan Nguyen 9cab0bb465 MDL-46978 core_grades: preserve category total visibility 2019-09-19 14:11:16 +10:00
Andrew Nicols 53c04bafc2 MDL-66675 behat: Fix typo in step function name 2019-09-19 07:31:07 +08:00
Andrew Nicols 409b3bbd81 MDL-66675 behat: Add option to pause on behat failures 2019-09-19 07:31:06 +08:00
Andrew Nicols fcad36339a MDL-66675 CLI: Add ANSI formatting options 2019-09-19 07:31:06 +08:00
Eloy Lafuente (stronk7) 10892d50d2 Merge branch 'MDL-66548-37' of git://github.com/bmbrands/moodle into MOODLE_37_STABLE 2019-09-19 01:01:46 +02:00
Eloy Lafuente (stronk7) 69aca65995 Merge branch 'MDL-66641_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-09-18 20:30:04 +02:00
Eloy Lafuente (stronk7) ec5aca167c Merge branch 'MDL-65878-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-09-18 20:07:59 +02:00
Luca Bösch 59cf70e977 MDL-65878 core_output: Move button not sticky to select field. 2019-09-18 19:11:37 +02:00
Eloy Lafuente (stronk7) 1c8d4b0bce Merge branch 'MDL-63895-37' of git://github.com/vmdef/moodle into MOODLE_37_STABLE 2019-09-18 17:33:26 +02:00
Sara Arjona ed401f9445 Merge branch 'MDL-66614_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-09-18 17:11:07 +02:00
Eloy Lafuente (stronk7) a80c888892 Merge branch 'MDL-66091_37' of git://github.com/dmonllao/moodle into MOODLE_37_STABLE 2019-09-18 16:47:09 +02:00
Sara Arjona 6354c24aed Merge branch 'MDL-63293-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-09-18 13:40:01 +02:00
Sara Arjona 5d21bf1b16 Merge branch 'MDL-66156-37' of https://github.com/snake/moodle into MOODLE_37_STABLE 2019-09-18 12:25:48 +02:00
David Monllaó 4a3f3ee3e4 MDL-66091 report_insights: Unify contextwithinsights cache sets 2019-09-18 13:00:26 +08:00
David Monllaó b2d9aaa126 MDL-66091 analytics: Targets choose if there should be a report or not 2019-09-18 13:00:25 +08:00
David Monllaó 5e17f2faeb MDL-66091 report_insights: Usability improvements
- More feedback provided for the user once they click on useful/notuseful from the email.
- Replace the indicator calculations table headers for the table caption and
  replace the text by just "Indicators"
- Replace "System" for the site name for insights generated at system
  level
- Replace "$modelname prediction" for "$modelname" in report/insights/prediction.php
  heading

MDL-66091
2019-09-18 13:00:25 +08:00
Eloy Lafuente (stronk7) 92e745c293 Merge branch 'MDL-65026-37' of git://github.com/jleyva/moodle into MOODLE_37_STABLE 2019-09-18 01:01:42 +02:00
Juan Leyva f4670d9e85 MDL-65970 airnotifier: Set lower timeout for Push curl requests 2019-09-17 16:51:06 +02:00
Eloy Lafuente (stronk7) cc1968a042 Merge branch 'MDL-62915-37' of https://github.com/paulholden/moodle into MOODLE_37_STABLE 2019-09-17 16:21:57 +02:00
Sara Arjona 7e107b3a98 Merge branch 'MDL-66113-37' of git://github.com/jleyva/moodle into MOODLE_37_STABLE 2019-09-17 12:03:53 +02:00
David Monllaó 6c15dc2f3c MDL-66090 analytics: Support user for notifications 2019-09-17 07:54:22 +08:00
Eloy Lafuente (stronk7) 072c1ad68e Merge branch 'MDL-65025-37' of git://github.com/jleyva/moodle into MOODLE_37_STABLE 2019-09-17 00:06:03 +02:00
Paul Holden 0590e71eeb MDL-62915 tool_dataprivacy: don't let primary admin delete themselves. 2019-09-16 22:31:40 +01:00
Eloy Lafuente (stronk7) 3536e97dc8 Merge branch 'MDL-66293-37' of git://github.com/rezaies/moodle into MOODLE_37_STABLE 2019-09-16 20:37:08 +02:00
Bas Brands f5cb0ec042 MDL-66548 theme_classic: secure layout block position fixes
- the secure layout for theme classic used an icorrect layout for blocks
and content. A fix has been submitted by G J Barnard.
2019-09-16 16:03:50 +02:00
Tim Hunt 40ed8aa97a MDL-66685 questions: should able to save an empty question usage 2019-09-16 14:36:47 +01:00
Juan Leyva 309550be69 MDL-65026 course: Fix modules intro formatting in WS
The Web Service seems to be doing excessive text cleaning.
2019-09-16 13:04:06 +02:00
Juan Leyva 535caf12c6 MDL-66113 assign: Apply user overrides in get_submission_status WS 2019-09-16 12:06:18 +02:00
Paul Holden e4bb1b0d75 MDL-63293 enrol_manual: observe user paging config in enrol dialog. 2019-09-15 21:48:13 +01:00
Eloy Lafuente (stronk7) bd3ce90c13 MDL-66262 env: Moodle 3.6.x and 3.7.x do not support PHP 7.4 2019-09-13 17:43:52 +02:00
Jake Dallimore 1542213b00 weekly release 3.7.2+ 2019-09-13 11:30:20 +08:00
Jake Dallimore 6a838cd4a0 Merge branch 'install_37_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_37_STABLE 2019-09-13 11:30:19 +08:00
Eloy Lafuente (stronk7) c0858f7068 Merge branch 'MDL-66550-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-09-12 15:31:44 +02:00
Andrew Nicols 1128545ebf MDL-66550 admin: The execute wrapper MUST be used in behat 2019-09-12 20:58:10 +08:00
Andrew Nicols 484606eb08 MDL-66550 theme: Wrap JS calls in pendingJS 2019-09-12 20:58:10 +08:00
Víctor Déniz Falcón 2d28f15a3a MDL-63895 user: Hide suspended enrolments to user without capability
Users without course:viewsuspendedusers capability should not view
suspended users on the participant list
2019-09-12 11:46:41 +01:00
Shamim Rezaie e6f2757f3c MDL-66293 javascript: set maximum paging bar size to 10 2019-09-12 14:22:13 +10:00
Shamim Rezaie f525ed2bdc MDL-66293 javascript: Added support to set bar size for paging bars 2019-09-12 14:22:13 +10:00
Jake Dallimore df991a4d53 MDL-66156 block_myoverview: remove orphaned course favourite records 2019-09-12 12:09:49 +08:00
Jake Dallimore d72a6fc04c MDL-66156 block_myoverview: purge course favourites on course deletion. 2019-09-12 11:47:50 +08:00
Andrew Nicols 83ecf18e34 Merge branch 'MDL-66498_37' of git://github.com/dmonllao/moodle into MOODLE_37_STABLE 2019-09-12 11:16:44 +08:00
Jake Dallimore 49b92a4a70 Merge branch 'MDL-64917-MOODLE_37_STABLE' of https://github.com/tuanngocnguyen/moodle into MOODLE_37_STABLE 2019-09-12 09:28:13 +08:00
Nathan Nguyen d540a0ae0b MDL-64917 filepicker: change event dispatch 2019-09-12 11:01:19 +10:00
AMOS bot a2f1de61a0 Automatically generated installer lang files 2019-09-12 00:09:22 +00:00
Andrew Nicols ae89df663a Merge branch 'MDL-66489-37' of git://github.com/rezaies/moodle into MOODLE_37_STABLE 2019-09-12 07:06:43 +08:00
Shamim Rezaie ee8d64f6ef MDL-66489 enrol: Send all form data to WS and do all busines logic there
It is discouraged to use JS Date functions. We should pass all raw data to PHP.
2019-09-11 20:31:56 +10:00
Andrew Nicols 11ac4acdb1 Merge branch 'MDL-65429-37' of git://github.com/peterRd/moodle into MOODLE_37_STABLE 2019-09-11 14:22:46 +08:00
Jake Dallimore c3fbb0278b Merge branch 'MDL-65493-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-09-11 13:45:40 +08:00
David Monllaó 375c1b8e20 MDL-66498 analytics: get_samples() with no params limit 2019-09-11 12:44:38 +08:00
David Monllaó 868cd1eacb MDL-66498 analytics: Avoid get_in_or_equal for big arrays
Also, join directly with context table.
2019-09-11 12:44:38 +08:00
David Monllao 4996652352 MDL-66498 analytics: SQL IN in chunks 2019-09-11 12:44:38 +08:00
Andrew Nicols 3857644a5c Merge branch 'MDL-66328_37' of https://github.com/timhunt/moodle into MOODLE_37_STABLE 2019-09-11 12:21:33 +08:00
Jake Dallimore eb8ecb04b6 Merge branch 'MDL-66550-37' of git://github.com/andrewnicols/moodle into MOODLE_37_STABLE 2019-09-11 10:39:57 +08:00
Eloy Lafuente (stronk7) 8e6fdc1cdb Merge branch 'MDL-66436-37' of https://github.com/lucaboesch/moodle into MOODLE_37_STABLE 2019-09-11 01:21:49 +02:00
Eloy Lafuente (stronk7) 733a5b75e9 Merge branch 'MDL-65310-37' of https://github.com/tsmilan/moodle into MOODLE_37_STABLE 2019-09-11 00:44:39 +02:00
David Mudrák af58905893 MDL-66644 admin: Display required moodle version only when declared
The minimum required Moodle version specified in the plugin's
version.php file is not a mandatory field. If it is not declared, do not
display "Moodle {$a}".
2019-09-10 17:40:07 +02:00
Andrew Nicols af0fd8128f MDL-66550 forum: Target fields in a fieldset for behat test 2019-09-10 21:29:03 +08:00
Andrew Nicols b559117706 MDL-66550 behat: Transform fields to search in a fieldset 2019-09-10 21:29:03 +08:00
Andrew Nicols 4e6831d51c MDL-66550 behat: Standardise finding in core functionality 2019-09-10 21:29:03 +08:00
Andrew Nicols e60183e163 MDL-66550 behat: Add ability to transform a specific selector 2019-09-10 21:29:00 +08:00
Andrew Nicols 59b30e3dae MDL-66550 behat: Normalise selectors better 2019-09-10 21:28:47 +08:00
Tim Hunt 87cc8008f4 MDL-66641 quiz stats: discriminativeefficiency is already out of 100 2019-09-10 12:12:00 +01:00
Jake Dallimore 35a8f83e2c Merge branch 'MDL-66409-edit-numerical/moodle37' of https://github.com/RaAstons/moodle into MOODLE_37_STABLE 2019-09-10 13:35:00 +08:00
Andrew Nicols a31372df3f Merge branch 'MDL-63250-37' of https://github.com/Kathrin84/moodle into MOODLE_37_STABLE 2019-09-10 12:40:41 +08:00
Jake Dallimore 252cc45466 Merge branch 'MDL-66599_37' of git://github.com/dmonllao/moodle into MOODLE_37_STABLE 2019-09-10 10:47:00 +08:00
Tim Hunt b652d2afc7 MDL-66328 groups: overview page can require lots of memory 2019-09-09 11:20:00 +01:00
Raphaël Santos 070453243b MDL-66409 mod_lesson: Fix editing of numerical pages
This avoids using the "magic" `isset` function implemented
within the lesson_base class (which returns the `empty` function).
In the case of the answer beeing `0`, the test fails and the answer
is considered to be deleted.
2019-09-09 09:43:32 +02:00
David Monllaó 991a4c0fd1 MDL-66599 analytics: New index for analytics_used_analysables 2019-09-09 08:32:51 +08:00
Trisha Milan 8faaa5da65 MDL-65310 block_timeline: Hide activities without due date 2019-09-06 13:48:24 +10:00
Andrew Nicols f6dd23d9a2 MDL-65493 core: Add pendingJS around pubsub.publish 2019-09-06 08:40:56 +08:00
Andrew Nicols b82faf7eda MDL-65493 message: The execute wrapper MUST be used 2019-09-06 08:40:56 +08:00
Andrew Nicols 191c5c293a MDL-65493 message: JS fixes for random behat fails 2019-09-06 08:40:56 +08:00
Luca Bösch 8d2bae85ed MDL-66436 glossary: Edit and delete categories form styles. 2019-09-05 18:02:28 +02:00
Andrew Nicols 4f32de51a9 MDL-66614 pgsql DML: Update get_records_sql to consume less memory
This is a backport of MDL-66327.

It's more memory efficient to use `pg_fetch_assoc` for each row than to
call `pg_fetch_all` and release memory immediately. This is because we
can treat the assoc fetch like an iterator and it only fetches the
current record into memory one at a time, whilst the all fetch fetches
all records and never unsets them. Attempting to unset them is extremely
time consuming.
2019-09-05 16:01:44 +01:00
Peter 341c68c779 MDL-65429 mod_forum: Indicate subs fetch completion with no subs
Unit tests updated to make sure only a single query is executed after
multiple reads
2019-08-30 09:08:29 +08:00
Juan Leyva d2154bafdb MDL-65025 forum: Fix text formatting (check messagetrust) in WS 2019-08-16 12:59:05 +01:00
Kathrin Osswald bed95cdef1 MDL-63250 mediaplugin: Fixed video player size in specific tables 2019-07-18 13:53:22 +02:00
420 changed files with 5861 additions and 2476 deletions
+97 -88
View File
@@ -514,100 +514,105 @@ if ($interactive) {
$database = $databases[$CFG->dbtype];
// ask for db host
if ($interactive) {
cli_separator();
cli_heading(get_string('databasehost', 'install'));
if ($options['dbhost'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbhost']);
// We cannot do any validation until all DB connection data is provided.
$hintdatabase = '';
do {
echo $hintdatabase;
// Ask for db host.
if ($interactive) {
cli_separator();
cli_heading(get_string('databasehost', 'install'));
if ($options['dbhost'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbhost']);
} else {
$prompt = get_string('clitypevalue', 'admin');
}
$CFG->dbhost = cli_input($prompt, $options['dbhost']);
} else {
$prompt = get_string('clitypevalue', 'admin');
$CFG->dbhost = $options['dbhost'];
}
$CFG->dbhost = cli_input($prompt, $options['dbhost']);
} else {
$CFG->dbhost = $options['dbhost'];
}
// Ask for db name.
if ($interactive) {
cli_separator();
cli_heading(get_string('databasename', 'install'));
if ($options['dbname'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbname']);
} else {
$prompt = get_string('clitypevalue', 'admin');
}
$CFG->dbname = cli_input($prompt, $options['dbname']);
// ask for db name
if ($interactive) {
cli_separator();
cli_heading(get_string('databasename', 'install'));
if ($options['dbname'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbname']);
} else {
$prompt = get_string('clitypevalue', 'admin');
$CFG->dbname = $options['dbname'];
}
$CFG->dbname = cli_input($prompt, $options['dbname']);
} else {
$CFG->dbname = $options['dbname'];
}
// Ask for db prefix.
if ($interactive) {
cli_separator();
cli_heading(get_string('dbprefix', 'install'));
//TODO: solve somehow the prefix trouble for oci.
if ($options['prefix'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['prefix']);
} else {
$prompt = get_string('clitypevalue', 'admin');
}
$CFG->prefix = cli_input($prompt, $options['prefix']);
// ask for db prefix
if ($interactive) {
cli_separator();
cli_heading(get_string('dbprefix', 'install'));
//TODO: solve somehow the prefix trouble for oci
if ($options['prefix'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['prefix']);
} else {
$prompt = get_string('clitypevalue', 'admin');
$CFG->prefix = $options['prefix'];
}
$CFG->prefix = cli_input($prompt, $options['prefix']);
} else {
$CFG->prefix = $options['prefix'];
}
// Ask for db port.
if ($interactive) {
cli_separator();
cli_heading(get_string('databaseport', 'install'));
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbport']);
$CFG->dboptions['dbport'] = (int) cli_input($prompt, $options['dbport']);
// ask for db port
if ($interactive) {
cli_separator();
cli_heading(get_string('databaseport', 'install'));
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbport']);
$CFG->dboptions['dbport'] = (int)cli_input($prompt, $options['dbport']);
} else {
$CFG->dboptions['dbport'] = (int)$options['dbport'];
}
if ($CFG->dboptions['dbport'] <= 0) {
$CFG->dboptions['dbport'] = '';
}
// ask for db socket
if ($CFG->ostype === 'WINDOWS') {
$CFG->dboptions['dbsocket'] = '';
} else if ($interactive and empty($CFG->dboptions['dbport'])) {
cli_separator();
cli_heading(get_string('databasesocket', 'install'));
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbsocket']);
$CFG->dboptions['dbsocket'] = cli_input($prompt, $options['dbsocket']);
} else {
$CFG->dboptions['dbsocket'] = $options['dbsocket'];
}
// ask for db user
if ($interactive) {
cli_separator();
cli_heading(get_string('databaseuser', 'install'));
if ($options['dbuser'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbuser']);
} else {
$prompt = get_string('clitypevalue', 'admin');
$CFG->dboptions['dbport'] = (int) $options['dbport'];
}
if ($CFG->dboptions['dbport'] <= 0) {
$CFG->dboptions['dbport'] = '';
}
$CFG->dbuser = cli_input($prompt, $options['dbuser']);
} else {
$CFG->dbuser = $options['dbuser'];
}
// Ask for db socket.
if ($CFG->ostype === 'WINDOWS') {
$CFG->dboptions['dbsocket'] = '';
} else if ($interactive and empty($CFG->dboptions['dbport'])) {
cli_separator();
cli_heading(get_string('databasesocket', 'install'));
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbsocket']);
$CFG->dboptions['dbsocket'] = cli_input($prompt, $options['dbsocket']);
} else {
$CFG->dboptions['dbsocket'] = $options['dbsocket'];
}
// Ask for db user.
if ($interactive) {
cli_separator();
cli_heading(get_string('databaseuser', 'install'));
if ($options['dbuser'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbuser']);
} else {
$prompt = get_string('clitypevalue', 'admin');
}
$CFG->dbuser = cli_input($prompt, $options['dbuser']);
} else {
$CFG->dbuser = $options['dbuser'];
}
// Ask for db password.
if ($interactive) {
cli_separator();
cli_heading(get_string('databasepass', 'install'));
// ask for db password
if ($interactive) {
cli_separator();
cli_heading(get_string('databasepass', 'install'));
do {
if ($options['dbpass'] !== '') {
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbpass']);
} else {
@@ -615,19 +620,23 @@ if ($interactive) {
}
$CFG->dbpass = cli_input($prompt, $options['dbpass']);
if (function_exists('distro_pre_create_db')) { // Hook for distros needing to do something before DB creation
$distro = distro_pre_create_db($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbport'=>$CFG->dboptions['dbport'], 'dbsocket'=>$CFG->dboptions['dbsocket']), $distro);
if (function_exists('distro_pre_create_db')) { // Hook for distros needing to do something before DB creation.
$distro = distro_pre_create_db($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix,
array('dbpersist' => 0, 'dbport' => $CFG->dboptions['dbport'], 'dbsocket' => $CFG->dboptions['dbsocket']),
$distro);
}
$hint_database = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbport'=>$CFG->dboptions['dbport'], 'dbsocket'=>$CFG->dboptions['dbsocket']));
} while ($hint_database !== '');
$hintdatabase = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix,
array('dbpersist' => 0, 'dbport' => $CFG->dboptions['dbport'], 'dbsocket' => $CFG->dboptions['dbsocket']));
} else {
$CFG->dbpass = $options['dbpass'];
$hint_database = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbport'=>$CFG->dboptions['dbport'], 'dbsocket'=>$CFG->dboptions['dbsocket']));
if ($hint_database !== '') {
cli_error(get_string('dbconnectionerror', 'install'));
} else {
$CFG->dbpass = $options['dbpass'];
$hintdatabase = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix,
array('dbpersist' => 0, 'dbport' => $CFG->dboptions['dbport'], 'dbsocket' => $CFG->dboptions['dbsocket']));
if ($hintdatabase !== '') {
cli_error(get_string('dbconnectionerror', 'install'));
}
}
}
} while ($hintdatabase !== '');
// ask for fullname
if ($interactive) {
+2
View File
@@ -2451,6 +2451,7 @@
<VENDOR name="oracle" version="11.2" />
</DATABASE>
<PHP version="7.0.0" level="required">
<RESTRICT function="restrict_php_version_74" message="unsupportedphpversion74" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
@@ -2632,6 +2633,7 @@
<VENDOR name="oracle" version="11.2" />
</DATABASE>
<PHP version="7.1.0" level="required">
<RESTRICT function="restrict_php_version_74" message="unsupportedphpversion74" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
+1 -1
View File
@@ -46,7 +46,7 @@ $error = optional_param('error', '', PARAM_ALPHANUM);
admin_externalpage_setup('registrationmoodleorg');
if ($url !== HUB_MOODLEORGHUBURL) {
// Allow other plugins to confirm registration on hubs other than moodle.net . Plugins implementing this
// Allow other plugins to confirm registration on custom hubs. Plugins implementing this
// callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
$callbacks = get_plugins_with_function('hub_registration');
foreach ($callbacks as $plugintype => $plugins) {
+1 -2
View File
@@ -32,5 +32,4 @@
defined('MOODLE_INTERNAL') || die();
debugging('Support for alternative hubs has been removed from Moodle in 3.4. For communication with moodle.net ' .
'see lib/classes/hub/ .', DEBUG_DEVELOPER);
debugging('Support for alternative hubs has been removed from Moodle in 3.4.', DEBUG_DEVELOPER);
+6 -6
View File
@@ -22,7 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
*
* This page displays the site registration form for Moodle.net.
* This page displays the site registration form.
* It handles redirection to the hub to continue the registration workflow process.
* It also handles update operation by web service.
*/
@@ -48,7 +48,7 @@ if ($unregistration && \core\hub\registration::is_registered()) {
}
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('unregisterfrom', 'hub', 'Moodle.net'), 3, 'main');
echo $OUTPUT->heading(get_string('registerwithmoodleorgremove', 'core_hub'), 3, 'main');
$siteunregistrationform->display();
echo $OUTPUT->footer();
exit;
@@ -82,7 +82,7 @@ if ($fromform = $siteregistrationform->get_data()) {
echo $OUTPUT->header();
// Current status of registration on Moodle.net.
// Current status of registration.
$notificationtype = \core\output\notification::NOTIFY_ERROR;
if (\core\hub\registration::is_registered()) {
@@ -104,11 +104,11 @@ if (\core\hub\registration::is_registered()) {
// Heading.
if (\core\hub\registration::is_registered()) {
echo $OUTPUT->heading(get_string('updatesite', 'hub', 'Moodle.net'));
echo $OUTPUT->heading(get_string('registerwithmoodleorgupdate', 'core_hub'));
} else if ($isinitialregistration) {
echo $OUTPUT->heading(get_string('completeregistration', 'hub'));
echo $OUTPUT->heading(get_string('registerwithmoodleorgcomplete', 'core_hub'));
} else {
echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'admin'));
echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'core_hub'));
}
$renderer = $PAGE->get_renderer('core', 'admin');
+2 -2
View File
@@ -40,7 +40,7 @@ $token = optional_param('token', '', PARAM_TEXT);
admin_externalpage_setup('registrationmoodleorg');
if ($url !== HUB_MOODLEORGHUBURL) {
// Allow other plugins to renew registration on hubs other than moodle.net . Plugins implementing this
// Allow other plugins to renew registration on custom hubs. Plugins implementing this
// callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
$callbacks = get_plugins_with_function('hub_registration');
foreach ($callbacks as $plugintype => $plugins) {
@@ -56,7 +56,7 @@ if ($url !== HUB_MOODLEORGHUBURL) {
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('renewregistration', 'hub'), 3, 'main');
$hublink = html_writer::tag('a', 'Moodle.net', array('href' => HUB_MOODLEORGHUBURL));
$hublink = html_writer::tag('a', HUB_MOODLEORGHUBURL, array('href' => HUB_MOODLEORGHUBURL));
$deletedregmsg = get_string('previousregistrationdeleted', 'hub', $hublink);
+27 -4
View File
@@ -1469,9 +1469,11 @@ class core_admin_renderer extends plugin_renderer_base {
$class = 'requires-failed';
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-danger');
}
$requires[] = html_writer::tag('li',
html_writer::span(get_string('moodleversion', 'core_plugin', $plugin->versionrequires), 'dep dep-core').
' '.$label, array('class' => $class));
if ($reqinfo->reqver != ANY_VERSION) {
$requires[] = html_writer::tag('li',
html_writer::span(get_string('moodleversion', 'core_plugin', $plugin->versionrequires), 'dep dep-core').
' '.$label, array('class' => $class));
}
} else {
$actions = array();
@@ -2110,6 +2112,27 @@ class core_admin_renderer extends plugin_renderer_base {
* @return string
*/
public function moodleorg_registration_message() {
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_HTML, ['noclean' => true]);
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub'), FORMAT_MARKDOWN);
$out .= html_writer::link(
new moodle_url('/admin/settings.php', ['section' => 'moodleservices']),
$this->output->pix_icon('i/info', '').' '.get_string('registerwithmoodleorginfoapp', 'core_hub'),
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
);
$out .= html_writer::link(
HUB_MOODLEORGHUBURL,
$this->output->pix_icon('i/stats', '').' '.get_string('registerwithmoodleorginfostats', 'core_hub'),
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
);
$out .= html_writer::link(
HUB_MOODLEORGHUBURL.'/sites',
$this->output->pix_icon('i/location', '').' '.get_string('registerwithmoodleorginfosites', 'core_hub'),
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
);
return $this->output->box($out);
}
}
+2 -5
View File
@@ -36,15 +36,12 @@ if (!is_siteadmin()) {
}
$admisselector = new core_role_admins_existing_selector();
$admisselector->set_extra_fields(array('username', 'email'));
$potentialadmisselector = new core_role_admins_potential_selector();
$potentialadmisselector->set_extra_fields(array('username', 'email'));
if (optional_param('add', false, PARAM_BOOL) and confirm_sesskey()) {
if ($userstoadd = $potentialadmisselector->get_selected_users()) {
$user = reset($userstoadd);
$username = fullname($user) . " ($user->username, $user->email)";
$username = $potentialadmisselector->output_user($user);
echo $OUTPUT->header();
$yesurl = new moodle_url('/admin/roles/admins.php', array('confirmadd'=>$user->id, 'sesskey'=>sesskey()));
echo $OUTPUT->confirm(get_string('confirmaddadmin', 'core_role', $username), $yesurl, $PAGE->url);
@@ -58,7 +55,7 @@ if (optional_param('add', false, PARAM_BOOL) and confirm_sesskey()) {
if ($USER->id == $user->id) {
// Can not remove self.
} else {
$username = fullname($user) . " ($user->username, $user->email)";
$username = $admisselector->output_user($user);
echo $OUTPUT->header();
$yesurl = new moodle_url('/admin/roles/admins.php', array('confirmdel'=>$user->id, 'sesskey'=>sesskey()));
echo $OUTPUT->confirm(get_string('confirmdeladmin', 'core_role', $username), $yesurl, $PAGE->url);
+7 -2
View File
@@ -52,7 +52,7 @@ if ($data) {
// Manage Moodle debugging options.
$debuglevel = $CFG->debug;
$debugdisplay = $CFG->debugdisplay;
$debugsmtp = $CFG->debugsmtp;
$debugsmtp = $CFG->debugsmtp ?? null; // This might not be set as it's optional.
$CFG->debugdisplay = true;
$CFG->debugsmtp = true;
$CFG->debug = 15;
@@ -66,7 +66,12 @@ if ($data) {
// Restore Moodle debugging options.
$CFG->debug = $debuglevel;
$CFG->debugdisplay = $debugdisplay;
$CFG->debugsmtp = $debugsmtp;
// Restore the debugsmtp config, if it was set originally.
unset($CFG->debugsmtp);
if (!is_null($debugsmtp)) {
$CFG->debugsmtp = $debugsmtp;
}
if ($success) {
$msgparams = new stdClass();
+2 -25
View File
@@ -48,13 +48,11 @@ class behat_admin extends behat_base {
* @param TableNode $table
*/
public function i_set_the_following_administration_settings_values(TableNode $table) {
if (!$data = $table->getRowsHash()) {
return;
}
foreach ($data as $label => $value) {
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]);
// Search by label.
@@ -76,38 +74,17 @@ class behat_admin extends behat_base {
"@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@for]";
$fieldnode = $this->find('xpath', $fieldxpath, $exception);
$formfieldtypenode = $this->find('xpath', $fieldxpath .
"/ancestor::div[contains(concat(' ', @class, ' '), ' form-setting ')]" .
"/child::div[contains(concat(' ', @class, ' '), ' form-')]/child::*/parent::div");
} catch (ElementNotFoundException $e) {
// Multi element settings, interacting only the first one.
$fieldxpath = "//*[label[contains(., $label)]|span[contains(., $label)]]" .
"/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
"/descendant::div[contains(concat(' ', @class, ' '), ' form-group ')]" .
"/descendant::*[self::input | self::textarea | self::select]" .
"[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]";
$fieldnode = $this->find('xpath', $fieldxpath);
// It is the same one that contains the type.
$formfieldtypenode = $fieldnode;
}
// Getting the class which contains the field type.
$classes = explode(' ', $formfieldtypenode->getAttribute('class'));
$type = false;
foreach ($classes as $class) {
if (substr($class, 0, 5) == 'form-') {
$type = substr($class, 5);
}
}
// Instantiating the appropiate field type.
$field = behat_field_manager::get_field_instance($type, $fieldnode, $this->getSession());
$field->set_value($value);
$this->find_button(get_string('savechanges'))->press();
$this->execute('behat_forms::i_set_the_field_with_xpath_to', [$fieldxpath, $value]);
$this->execute("behat_general::i_click_on", [get_string('savechanges'), 'button']);
}
}
@@ -59,6 +59,9 @@ class predict_models extends \core\task\scheduled_task {
}
foreach ($models as $model) {
$renderer = $PAGE->get_renderer('tool_analytics');
$result = $model->predict();
// Reset the page as some indicators may call external functions that overwrite the page context.
@@ -66,7 +69,6 @@ class predict_models extends \core\task\scheduled_task {
if ($result) {
echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
$renderer = $PAGE->get_renderer('tool_analytics');
echo $renderer->render_get_predictions_results(false, array(), $result, $model->get_analyser()->get_logs());
}
}
@@ -70,6 +70,8 @@ class train_models extends \core\task\scheduled_task {
continue;
}
$renderer = $PAGE->get_renderer('tool_analytics');
$result = $model->train();
// Reset the page as some indicators may call external functions that overwrite the page context.
@@ -77,8 +79,6 @@ class train_models extends \core\task\scheduled_task {
if ($result) {
echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
$renderer = $PAGE->get_renderer('tool_analytics');
echo $renderer->render_get_predictions_results($result, $model->get_analyser()->get_logs());
}
}
+2 -1
View File
@@ -102,6 +102,8 @@ if ($options['reuse-prev-analysed']) {
mtrace(get_string('evaluationinbatches', 'tool_analytics'));
}
$renderer = $PAGE->get_renderer('tool_analytics');
$analyseroptions = array(
'filter' => $options['filter'],
'timesplitting' => $options['timesplitting'],
@@ -114,7 +116,6 @@ $results = $model->evaluate($analyseroptions);
// Reset the page as some indicators may call external functions that overwrite the page context.
\tool_analytics\output\helper::reset_page();
$renderer = $PAGE->get_renderer('tool_analytics');
echo $renderer->render_evaluate_results($results, $model->get_analyser()->get_logs());
// Check that we have, at leasa,t 1 valid dataset (not necessarily good) to use.
@@ -0,0 +1,27 @@
@tool_behat
Feature: Behat steps for interacting with form work
In order to test my Moodle code
As a developer
I need the Behat steps for form elements to work reliably
@javascript
Scenario: Test fields in containers
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
When I log in as "admin"
And I am on "Course 1" course homepage
# Just get to any form.
And I navigate to "Edit settings" in current page administration
And I set the field "Course full name" in the "General" "fieldset" to "Frog"
And I set the following fields in the "Appearance" "fieldset" to these values:
| Show activity reports | Yes |
| Number of announcements | 1 |
Then the field "Show activity reports" in the "Appearance" "fieldset" matches value "Yes"
And the field "Show activity reports" in the "Appearance" "fieldset" does not match value "No"
And the following fields in the "region-main" "region" match these values:
| Course full name | Frog |
| Number of announcements | 1 |
And the following fields in the "region-main" "region" do not match these values:
| Course full name | Course 1 |
| Number of announcements | 5 |
+42
View File
@@ -222,6 +222,48 @@ class api {
}
}
// Clean the legacy role assignments which are stale.
$paramsclean['usercontext'] = CONTEXT_USER;
$paramsclean['component'] = 'tool_cohortroles';
$sql = 'SELECT DISTINCT(ra.id), ra.roleid, ra.userid, ra.contextid, ctx.instanceid
FROM {role_assignments} ra
JOIN {context} ctx ON ctx.id = ra.contextid AND ctx.contextlevel = :usercontext
JOIN {cohort_members} cm ON cm.userid = ctx.instanceid
LEFT JOIN {tool_cohortroles} tc ON tc.cohortid = cm.cohortid
AND tc.userid = ra.userid
AND tc.roleid = ra.roleid
WHERE ra.component = :component
AND tc.id is null';
if ($candidatelegacyassignments = $DB->get_records_sql($sql, $paramsclean)) {
$sql = 'SELECT DISTINCT(ra.id)
FROM {role_assignments} ra
JOIN {context} ctx ON ctx.id = ra.contextid AND ctx.contextlevel = :usercontext
JOIN {cohort_members} cm ON cm.userid = ctx.instanceid
JOIN {tool_cohortroles} tc ON tc.cohortid = cm.cohortid AND tc.userid = ra.userid
WHERE ra.component = :component';
if ($currentvalidroleassignments = $DB->get_records_sql($sql, $paramsclean)) {
foreach ($candidatelegacyassignments as $candidate) {
if (!array_key_exists($candidate->id, $currentvalidroleassignments)) {
role_unassign($candidate->roleid, $candidate->userid, $candidate->contextid, 'tool_cohortroles');
$rolesremoved[] = array(
'useridassignedto' => $candidate->userid,
'useridassignedover' => $candidate->instanceid,
'roleid' => $candidate->roleid
);
}
}
} else {
foreach ($candidatelegacyassignments as $candidate) {
role_unassign($candidate->roleid, $candidate->userid, $candidate->contextid, 'tool_cohortroles');
$rolesremoved[] = array(
'useridassignedto' => $candidate->userid,
'useridassignedover' => $candidate->instanceid,
'roleid' => $candidate->roleid
);
}
}
}
return array('rolesadded' => $rolesadded, 'rolesremoved' => $rolesremoved);
}
@@ -103,5 +103,4 @@ class cohort_role_assignment extends persistent {
return true;
}
}
+82 -7
View File
@@ -50,8 +50,6 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
* Setup function- we will create a course and add an assign instance to it.
*/
protected function setUp() {
global $DB;
$this->resetAfterTest(true);
// Create some users.
@@ -133,14 +131,91 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
public function test_delete_cohort_role_assignment() {
$this->setAdminUser();
$params = (object) array(
// Create a cohort role assigment.
$params = (object) [
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
$worked = api::delete_cohort_role_assignment($result->get('id'));
$this->assertTrue($worked);
];
$cohortroleassignment = api::create_cohort_role_assignment($params);
$sync = api::sync_all_cohort_roles();
$rolesadded = [
[
'useridassignedto' => $this->userassignto->id,
'useridassignedover' => $this->userassignover->id,
'roleid' => $this->roleid
]
];
$expected = [
'rolesadded' => $rolesadded,
'rolesremoved' => []
];
$this->assertEquals($sync, $expected);
// Delete the cohort role assigment and confirm the roles are removed.
$result = api::delete_cohort_role_assignment($cohortroleassignment->get('id'));
$this->assertTrue($result);
$sync = api::sync_all_cohort_roles();
$expected = [
'rolesadded' => [],
'rolesremoved' => $rolesadded
];
$this->assertEquals($expected, $sync);
}
/**
* Test case verifying that syncing won't remove role assignments if they are valid for another cohort role assignment.
*/
public function test_delete_cohort_role_assignment_cohorts_having_same_members() {
$this->setAdminUser();
// Create 2 cohorts, with a 1 user (user1) present in both,
// and user2 and user3 members of 1 cohort each.
$cohort1 = $this->getDataGenerator()->create_cohort();
$cohort2 = $this->getDataGenerator()->create_cohort();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
cohort_add_member($cohort1->id, $user1->id);
cohort_add_member($cohort1->id, $user2->id);
cohort_add_member($cohort2->id, $user1->id);
cohort_add_member($cohort2->id, $user3->id);
// And a role and a user to assign that role to.
$user4 = $this->getDataGenerator()->create_user(); // A cohort manager, for example.
$roleid = create_role('Role 1', 'myrole', 'test');
// Assign the role for user4 in both cohorts.
$params = (object) [
'userid' => $user4->id,
'roleid' => $roleid,
'cohortid' => $cohort1->id
];
$cohort1roleassignment = api::create_cohort_role_assignment($params);
$params->cohortid = $cohort2->id;
$cohort2roleassignment = api::create_cohort_role_assignment($params);
$sync = api::sync_all_cohort_roles();
// There is no guarantee about the order of roles assigned.
// so confirm we have 3 role assignments, and they are for the users 1, 2 and 3.
$this->assertCount(3, $sync['rolesadded']);
$addedusers = array_column($sync['rolesadded'], 'useridassignedover');
$this->assertContains($user1->id, $addedusers);
$this->assertContains($user2->id, $addedusers);
$this->assertContains($user3->id, $addedusers);
// Remove the role assignment for user4/cohort1.
// Verify only 1 role is unassigned as the others are still valid for the other cohort role assignment.
$result = api::delete_cohort_role_assignment($cohort1roleassignment->get('id'));
$this->assertTrue($result);
$sync = api::sync_all_cohort_roles();
$this->assertCount(0, $sync['rolesadded']);
$this->assertCount(1, $sync['rolesremoved']);
$removedusers = array_column($sync['rolesremoved'], 'useridassignedover');
$this->assertContains($user2->id, $removedusers);
}
public function test_list_cohort_role_assignments() {
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core/url","core/str"],function(a,b,c){var d=a('<img alt="" src="'+b.imageUrl("t/expanded")+'"/>'),e=a('<img alt="" src="'+b.imageUrl("t/collapsed")+'"/>');return{expandCollapse:function(a,b){a.hasClass("hide")?(a.removeClass("hide"),a.addClass("visible"),a.attr("aria-expanded",!0),b.find(":header i.fa").removeClass("fa-plus-square"),b.find(":header i.fa").addClass("fa-minus-square"),b.find(":header img.icon").attr("src",d.attr("src"))):(a.removeClass("visible"),a.addClass("hide"),a.attr("aria-expanded",!1),b.find(":header i.fa").removeClass("fa-minus-square"),b.find(":header i.fa").addClass("fa-plus-square"),b.find(":header img.icon").attr("src",e.attr("src")))},expandCollapseAll:function(b){var f="visible"==b?"hide":"visible",g="visible"==b,h="visible"==b?"fa-plus-square":"fa-minus-square",i="visible"==b?"fa-minus-square":"fa-plus-square",j="visible"==b?d.attr("src"):e.attr("src");a("."+f).each(function(){a(this).removeClass(f),a(this).addClass(b),a(this).attr("aria-expanded",g)}),a(".tool_dataprivacy-expand-all").data("visibilityState",f),c.get_string(f,"tool_dataprivacy").then(function(b){a(".tool_dataprivacy-expand-all").html(b)})["catch"](Notification.exception),a(":header i.fa").each(function(){a(this).removeClass(h),a(this).addClass(i)}),a(":header img.icon").each(function(){a(this).attr("src",j)})}}});
define(["jquery","core/url","core/str"],function(a,b,c){var d=a('<img alt="" src="'+b.imageUrl("t/expanded")+'"/>'),e=a('<img alt="" src="'+b.imageUrl("t/collapsed")+'"/>'),f={EXPAND:"fa-caret-right",COLLAPSE:"fa-caret-down"};return{expandCollapse:function(a,b){a.hasClass("hide")?(a.removeClass("hide"),a.addClass("visible"),a.attr("aria-expanded",!0),b.find(":header i.fa").removeClass(f.EXPAND),b.find(":header i.fa").addClass(f.COLLAPSE),b.find(":header img.icon").attr("src",d.attr("src"))):(a.removeClass("visible"),a.addClass("hide"),a.attr("aria-expanded",!1),b.find(":header i.fa").removeClass(f.COLLAPSE),b.find(":header i.fa").addClass(f.EXPAND),b.find(":header img.icon").attr("src",e.attr("src")))},expandCollapseAll:function(b){var g="visible"==b?"hide":"visible",h="visible"==b,i="visible"==b?f.EXPAND:f.COLLAPSE,j="visible"==b?f.COLLAPSE:f.EXPAND,k="visible"==b?d.attr("src"):e.attr("src");a("."+g).each(function(){a(this).removeClass(g),a(this).addClass(b),a(this).attr("aria-expanded",h)}),a(".tool_dataprivacy-expand-all").data("visibilityState",g),c.get_string(g,"tool_dataprivacy").then(function(b){a(".tool_dataprivacy-expand-all").html(b)})["catch"](Notification.exception),a(":header i.fa").each(function(){a(this).removeClass(i),a(this).addClass(j)}),a(":header img.icon").each(function(){a(this).attr("src",k)})}}});
@@ -28,6 +28,14 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
/*
* Class names to apply when expanding/collapsing nodes.
*/
var CLASSES = {
EXPAND: 'fa-caret-right',
COLLAPSE: 'fa-caret-down'
};
return /** @alias module:tool_dataprivacy/expand-collapse */ {
/**
* Expand or collapse a selected node.
@@ -40,15 +48,15 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
targetnode.removeClass('hide');
targetnode.addClass('visible');
targetnode.attr('aria-expanded', true);
thisnode.find(':header i.fa').removeClass('fa-plus-square');
thisnode.find(':header i.fa').addClass('fa-minus-square');
thisnode.find(':header i.fa').removeClass(CLASSES.EXPAND);
thisnode.find(':header i.fa').addClass(CLASSES.COLLAPSE);
thisnode.find(':header img.icon').attr('src', expandedImage.attr('src'));
} else {
targetnode.removeClass('visible');
targetnode.addClass('hide');
targetnode.attr('aria-expanded', false);
thisnode.find(':header i.fa').removeClass('fa-minus-square');
thisnode.find(':header i.fa').addClass('fa-plus-square');
thisnode.find(':header i.fa').removeClass(CLASSES.COLLAPSE);
thisnode.find(':header i.fa').addClass(CLASSES.EXPAND);
thisnode.find(':header img.icon').attr('src', collapsedImage.attr('src'));
}
},
@@ -61,8 +69,8 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
expandCollapseAll: function(nextstate) {
var currentstate = (nextstate == 'visible') ? 'hide' : 'visible';
var ariaexpandedstate = (nextstate == 'visible') ? true : false;
var iconclassnow = (nextstate == 'visible') ? 'fa-plus-square' : 'fa-minus-square';
var iconclassnext = (nextstate == 'visible') ? 'fa-minus-square' : 'fa-plus-square';
var iconclassnow = (nextstate == 'visible') ? CLASSES.EXPAND : CLASSES.COLLAPSE;
var iconclassnext = (nextstate == 'visible') ? CLASSES.COLLAPSE : CLASSES.EXPAND;
var imagenow = (nextstate == 'visible') ? expandedImage.attr('src') : collapsedImage.attr('src');
$('.' + currentstate).each(function() {
$(this).removeClass(currentstate);
+3 -2
View File
@@ -774,7 +774,8 @@ class api {
public static function can_create_data_deletion_request_for_self(int $userid = null): bool {
global $USER;
$userid = $userid ?: $USER->id;
return has_capability('tool/dataprivacy:requestdelete', \context_user::instance($userid), $userid);
return has_capability('tool/dataprivacy:requestdelete', \context_user::instance($userid), $userid)
&& !is_primary_admin($userid);
}
/**
@@ -803,7 +804,7 @@ class api {
global $USER;
$requesterid = $requesterid ?: $USER->id;
return has_capability('tool/dataprivacy:makedatadeletionrequestsforchildren', \context_user::instance($userid),
$requesterid);
$requesterid) && !is_primary_admin($userid);
}
/**
+2 -2
View File
@@ -713,7 +713,6 @@ class external extends external_api {
list($sql, $params) = users_search_sql($query, '', false, $extrafields, $excludedusers);
$users = $DB->get_records_select('user', $sql, $params, $sort, $fields, 0, 30);
$useroptions = [];
foreach ($users as $user) {
$useroption = (object)[
@@ -722,9 +721,10 @@ class external extends external_api {
];
$useroption->extrafields = [];
foreach ($extrafields as $extrafield) {
// Sanitize the extra fields to prevent potential XSS exploit.
$useroption->extrafields[] = (object)[
'name' => $extrafield,
'value' => $user->$extrafield
'value' => s($user->$extrafield)
];
}
$useroptions[$user->id] = $useroption;
@@ -127,13 +127,17 @@ class process_data_request_task extends adhoc_task {
$thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
$completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY;
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
// Delete the data.
$manager = new \core_privacy\manager();
$manager->set_observer(new \tool_dataprivacy\manager_observer());
// Delete the data for users other than the primary admin, which is rejected.
if (is_primary_admin($foruser->id)) {
$completestatus = api::DATAREQUEST_STATUS_REJECTED;
} else {
$manager = new \core_privacy\manager();
$manager->set_observer(new \tool_dataprivacy\manager_observer());
$manager->delete_data_for_user($approvedclcollection);
$completestatus = api::DATAREQUEST_STATUS_DELETED;
$deleteuser = !$foruser->deleted;
$manager->delete_data_for_user($approvedclcollection);
$completestatus = api::DATAREQUEST_STATUS_DELETED;
$deleteuser = !$foruser->deleted;
}
}
// When the preparation of the metadata finishes, update the request status to awaiting approval.
@@ -47,6 +47,6 @@
<span>
<span>{{fullname}}</span>
{{#extrafields}}
<span><small>{{value}}</small></span>
<span><small>{{{value}}}</small></span>
{{/extrafields}}
</span>
+56 -1
View File
@@ -303,6 +303,29 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$requestid = $datarequest->get('id');
}
/**
* Test that deletion requests for the primary admin are rejected
*/
public function test_reject_data_deletion_request_primary_admin() {
$this->resetAfterTest();
$this->setAdminUser();
$datarequest = api::create_data_request(get_admin()->id, api::DATAREQUEST_TYPE_DELETE);
// Approve the request and execute the ad-hoc process task.
ob_start();
api::approve_data_request($datarequest->get('id'));
$this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
ob_end_clean();
$request = api::get_request($datarequest->get('id'));
$this->assertEquals(api::DATAREQUEST_STATUS_REJECTED, $request->get('status'));
// Confirm they weren't deleted.
$user = core_user::get_user($request->get('userid'));
core_user::require_active_user($user);
}
/**
* Test for api::can_contact_dpo()
*/
@@ -2126,6 +2149,33 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->assertFalse(api::can_create_data_deletion_request_for_self());
}
/**
* Test primary admin cannot create data deletion request for themselves
*/
public function test_can_create_data_deletion_request_for_self_primary_admin() {
$this->resetAfterTest();
$this->setAdminUser();
$this->assertFalse(api::can_create_data_deletion_request_for_self());
}
/**
* Test secondary admin can create data deletion request for themselves
*/
public function test_can_create_data_deletion_request_for_self_secondary_admin() {
$this->resetAfterTest();
$admin1 = $this->getDataGenerator()->create_user();
$admin2 = $this->getDataGenerator()->create_user();
// The primary admin is the one listed first in the 'siteadmins' config.
set_config('siteadmins', implode(',', [$admin1->id, $admin2->id]));
// Set the current user as the second admin (non-primary).
$this->setUser($admin2);
$this->assertTrue(api::can_create_data_deletion_request_for_self());
}
/**
* Test user can create data deletion request for themselves if they have
* "tool/dataprivacy:requestdelete" capability.
@@ -2171,7 +2221,8 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
}
/**
* Check parents can create data deletion request for their children but not others.
* Check parents can create data deletion request for their children (unless the child is the primary admin),
* but not other users.
*
* @throws coding_exception
* @throws dml_exception
@@ -2194,5 +2245,9 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->setUser($parent);
$this->assertTrue(api::can_create_data_deletion_request_for_children($child->id));
$this->assertFalse(api::can_create_data_deletion_request_for_children($otheruser->id));
// Now make child the primary admin, confirm parent can't make deletion request.
set_config('siteadmins', $child->id);
$this->assertFalse(api::can_create_data_deletion_request_for_children($child->id));
}
}
@@ -189,6 +189,12 @@ Feature: Data delete from the privacy API
And I follow "Profile" in the user menu
Then I should not see "Delete my account"
@javascript
Scenario: As a primary admin, the link to create a data deletion request should not be shown.
Given I log in as "admin"
When I follow "Profile" in the user menu
Then I should not see "Delete my account"
@javascript
Scenario: As a Privacy Officer, I cannot Approve to Deny deletion data request without permission.
Given the following "permission overrides" exist:
@@ -75,7 +75,7 @@ class manage_competency_frameworks_page implements renderable, templatable {
);
$this->navigation[] = $addpage;
$competenciesrepository = new single_button(
new moodle_url('https://moodle.net/competencies'),
new moodle_url('https://archive.moodle.net/competencies'),
get_string('competencyframeworksrepository', 'tool_lp'),
'get'
);
+6
View File
@@ -206,6 +206,12 @@ class api {
$identityprovidersdata = \auth_plugin_base::prepare_identity_providers_for_output($identityproviders, $OUTPUT);
if (!empty($identityprovidersdata)) {
$settings['identityproviders'] = $identityprovidersdata;
// Clean URLs to avoid breaking Web Services.
// We can't do it in prepare_identity_providers_for_output() because it may break the web output.
foreach ($settings['identityproviders'] as &$ip) {
$ip['url'] = (!empty($ip['url'])) ? clean_param($ip['url'], PARAM_URL) : '';
$ip['iconurl'] = (!empty($ip['iconurl'])) ? clean_param($ip['iconurl'], PARAM_URL) : '';
}
}
// If age is verified, return also the admin contact details.
+1 -1
View File
@@ -91,7 +91,7 @@ $string['readingthisemailgettheapp'] = 'Reading this in an email? <a href="{$a}"
$string['remoteaddons'] = 'Remote add-ons';
$string['selfsignedoruntrustedcertificatewarning'] = 'It seems that the HTTPS certificate is self-signed or not trusted. The mobile app will only work with trusted sites.';
$string['setuplink'] = 'App download page';
$string['setuplink_desc'] = 'URL of page with links to download the mobile app from the App Store and Google Play.';
$string['setuplink_desc'] = 'URL of page with options to download the mobile app from the App Store and Google Play. The app download page link is displayed in the page footer and in a user\'s profile. Leave blank to not display a link.';
$string['smartappbanners'] = 'App Banners';
$string['typeoflogin'] = 'Type of login';
$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
@@ -99,6 +99,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
);
$this->assertEquals($expected, $result);
$this->setAdminUser();
// Change some values.
set_config('registerauth', 'email');
$authinstructions = 'Something with <b>html tags</b>';
@@ -112,6 +113,18 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
set_config('lang', 'a_b'); // Set invalid lang.
set_config('disabledfeatures', 'myoverview', 'tool_mobile');
// Enable couple of issuers.
$issuer = \core\oauth2\api::create_standard_issuer('google');
$irecord = $issuer->to_record();
$irecord->clientid = 'mock';
$irecord->clientsecret = 'mock';
core\oauth2\api::update_issuer($irecord);
set_config('hostname', 'localhost', 'auth_cas');
set_config('auth_logo', 'http://invalidurl.com//invalid/', 'auth_cas');
set_config('auth', 'oauth2,cas');
list($authinstructions, $notusedformat) = external_format_text($authinstructions, FORMAT_MOODLE, $context->id);
$expected['registerauth'] = 'email';
$expected['authinstructions'] = $authinstructions;
@@ -133,7 +146,26 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
$result = external::get_public_config();
$result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
// First check providers.
$identityproviders = $result['identityproviders'];
unset($result['identityproviders']);
$this->assertEquals('Google', $identityproviders[0]['name']);
$this->assertEquals($irecord->image, $identityproviders[0]['iconurl']);
$this->assertContains($CFG->wwwroot, $identityproviders[0]['url']);
$this->assertEquals('CAS', $identityproviders[1]['name']);
$this->assertEmpty($identityproviders[1]['iconurl']);
$this->assertContains($CFG->wwwroot, $identityproviders[1]['url']);
$this->assertEquals($expected, $result);
// Change providers img.
$newurl = 'validimage.png';
set_config('auth_logo', $newurl, 'auth_cas');
$result = external::get_public_config();
$result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
$this->assertContains($newurl, $result['identityproviders'][1]['iconurl']);
}
/**
+2 -1
View File
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
use \tool_monitor\privacy\provider;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\approved_userlist;
use \core_privacy\tests\provider_testcase;
/**
* Privacy test for the event monitor
@@ -37,7 +38,7 @@ use \core_privacy\local\request\approved_userlist;
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_monitor_privacy_testcase extends advanced_testcase {
class tool_monitor_privacy_testcase extends provider_testcase {
/**
* Set up method.
+9 -3
View File
@@ -46,6 +46,9 @@ class issuer extends persistent {
/** @var string $type */
protected $type;
/** @var boolean $showrequireconfirm Whether to show the require confirmation email checkbox or not. */
protected $showrequireconfirm;
/**
* Constructor.
*
@@ -71,6 +74,7 @@ class issuer extends persistent {
if (array_key_exists('type', $customdata)) {
$this->type = $customdata['type'];
}
$this->showrequireconfirm = !empty($customdata['showrequireconfirm']);
parent::__construct($action, $customdata, $method, $target, $attributes, $editable, $ajaxformdata);
}
@@ -157,9 +161,11 @@ class issuer extends persistent {
$mform->addElement('checkbox', 'showonloginpage', get_string('issuershowonloginpage', 'tool_oauth2'));
$mform->addHelpButton('showonloginpage', 'issuershowonloginpage', 'tool_oauth2');
// Require confirmation email for new accounts.
$mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
$mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
if ($this->showrequireconfirm) {
// Require confirmation email for new accounts.
$mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
$mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
}
$mform->addElement('hidden', 'sortorder');
$mform->setType('sortorder', PARAM_INT);
+37 -7
View File
@@ -58,7 +58,24 @@ if ($action == 'edit') {
$PAGE->navbar->add(get_string('createnewissuer', 'tool_oauth2'));
}
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer]);
$showrequireconfirm = false;
if (!empty($issuerid)) {
// Show the "Require confirmation email" checkbox for trusted issuers like Google, Facebook and Microsoft.
$likefacebook = $DB->sql_like('url', ':facebook');
$likegoogle = $DB->sql_like('url', ':google');
$likemicrosoft = $DB->sql_like('url', ':microsoft');
$params = [
'issuerid' => $issuerid,
'facebook' => '%facebook%',
'google' => '%google%',
'microsoft' => '%microsoft%',
];
$select = "issuerid = :issuerid AND ($likefacebook OR $likegoogle OR $likemicrosoft)";
// We're querying from the oauth2_endpoint table because the base URLs of FB and Microsoft can be empty in the issuer table.
$showrequireconfirm = $DB->record_exists_select('oauth2_endpoint', $select, $params);
}
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'showrequireconfirm' => $showrequireconfirm]);
}
if ($mform && $mform->is_cancelled()) {
@@ -108,9 +125,11 @@ if ($mform && $mform->is_cancelled()) {
$type = required_param('type', PARAM_ALPHA);
$docs = required_param('docslink', PARAM_ALPHAEXT);
$showrequireconfirm = optional_param('showrequireconfirm', false, PARAM_BOOL);
require_sesskey();
$issuer = core\oauth2\api::init_standard_issuer($type);
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'type' => $type]);
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'type' => $type,
'showrequireconfirm' => $showrequireconfirm]);
echo $OUTPUT->header();
$mform->display();
@@ -178,26 +197,37 @@ if ($mform && $mform->is_cancelled()) {
$issuers = core\oauth2\api::get_all_issuers();
echo $renderer->issuers_table($issuers);
// Google template.
$docs = 'admin/tool/oauth2/issuers/google';
$params = ['action' => 'edittemplate', 'type' => 'google', 'sesskey' => sesskey(), 'docslink' => $docs];
$params = ['action' => 'edittemplate', 'type' => 'google', 'sesskey' => sesskey(), 'docslink' => $docs,
'showrequireconfirm' => true];
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
echo $renderer->single_button($addurl, get_string('createnewgoogleissuer', 'tool_oauth2'));
// Microsoft template.
$docs = 'admin/tool/oauth2/issuers/microsoft';
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs];
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs,
'showrequireconfirm' => true];
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
echo $renderer->single_button($addurl, get_string('createnewmicrosoftissuer', 'tool_oauth2'));
// Facebook template.
$docs = 'admin/tool/oauth2/issuers/facebook';
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs];
$params = ['action' => 'edittemplate', 'type' => 'facebook', 'sesskey' => sesskey(), 'docslink' => $docs];
$params = ['action' => 'edittemplate', 'type' => 'facebook', 'sesskey' => sesskey(), 'docslink' => $docs,
'showrequireconfirm' => true];
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
echo $renderer->single_button($addurl, get_string('createnewfacebookissuer', 'tool_oauth2'));
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['action' => 'edit']);
// Nextcloud template.
$docs = 'admin/tool/oauth2/issuers/nextcloud';
$params = ['action' => 'edittemplate', 'type' => 'nextcloud', 'sesskey' => sesskey(), 'docslink' => $docs];
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
echo $renderer->single_button($addurl, get_string('createnewnextcloudissuer', 'tool_oauth2'));
// Generic issuer.
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['action' => 'edit']);
echo $renderer->single_button($addurl, get_string('createnewissuer', 'tool_oauth2'));
echo $OUTPUT->footer();
}
+1 -1
View File
@@ -257,7 +257,7 @@ class manager {
'title' => get_string('importtour', 'tool_usertours'),
],
(object) [
'link' => new \moodle_url('https://moodle.net/tours'),
'link' => new \moodle_url('https://archive.moodle.net/tours'),
'linkproperties' => [
'target' => '_blank',
],
+1 -1
View File
@@ -401,7 +401,7 @@
$row = array ();
$row[] = "<a href=\"../user/view.php?id=$user->id&amp;course=$site->id\">$fullname</a>";
foreach ($extracolumns as $field) {
$row[] = $user->{$field};
$row[] = s($user->{$field});
}
$row[] = $user->city;
$row[] = $user->country;
+1 -1
View File
@@ -138,7 +138,7 @@ foreach ($users as $user) {
'<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $user->id . '&amp;course=' . SITEID . '">' .
$user->fullname .
'</a>',
$user->email,
s($user->email),
$user->city,
$user->country,
$user->lastaccess ? format_time(time() - $user->lastaccess) : $strnever
+1 -1
View File
@@ -72,7 +72,7 @@ foreach($users as $user) {
$table->data[] = array (
'<a href="'.$CFG->wwwroot.'/user/view.php?id='.$user->id.'&amp;course='.SITEID.'">'.$user->fullname.'</a>',
// $user->username,
$user->email,
s($user->email),
$user->city,
$user->country,
$user->lastaccess ? format_time(time() - $user->lastaccess) : $strnever
+9 -1
View File
@@ -138,6 +138,9 @@ class analysis {
}
}
}
// Force GC to clean up the indicator instances used during the last iteration.
$this->analyser->instantiate_indicators();
}
/**
@@ -474,6 +477,11 @@ class analysis {
list($samplesfeatures, $newindicatorcalculations, $indicatornotnulls) = $rangeindicator->calculate($sampleids,
$this->analyser->get_samples_origin(), $range['start'], $range['end'], $prevcalculations);
// Free memory ASAP.
unset($rangeindicator);
gc_collect_cycles();
gc_mem_caches();
// Copy the features data to the dataset.
foreach ($samplesfeatures as $analysersampleid => $features) {
@@ -503,7 +511,7 @@ class analysis {
$indcalc->endtime = $range['end'];
$indcalc->sampleid = $sampleid;
$indcalc->sampleorigin = $this->analyser->get_samples_origin();
$indcalc->indicator = $rangeindicator->get_id();
$indcalc->indicator = $indicator->get_id();
$indcalc->value = $calculatedvalue;
$indcalc->timecreated = $timecreated;
$newcalculations[] = $indcalc;
+8 -3
View File
@@ -135,7 +135,7 @@ class insights_generator {
$message->component = 'moodle';
$message->name = 'insights';
$message->userfrom = \core_user::get_noreply_user();
$message->userfrom = \core_user::get_support_user();
$message->userto = $user;
$message->subject = $this->target->get_insight_subject($this->modelid, $context);
@@ -193,18 +193,23 @@ class insights_generator {
$insighturl = null;
foreach ($predictionactions as $action) {
$actionurl = $action->get_url();
$opentoblank = false;
if (!$actionurl->get_param('forwardurl')) {
$actiondoneurl = new \moodle_url('/report/insights/done.php');
$params = ['actionvisiblename' => $action->get_text(), 'target' => '_blank'];
$actiondoneurl = new \moodle_url('/report/insights/done.php', $params);
// Set the forward url to the 'done' script.
$actionurl->param('forwardurl', $actiondoneurl->out(false));
$opentoblank = true;
}
if (empty($insighturl)) {
// We use the primary action url as insight url so we log that the user followed the provided link.
$insighturl = $action->get_url();
}
$actiondata = (object)['url' => $action->get_url()->out(false), 'text' => $action->get_text()];
$actiondata = (object)['url' => $action->get_url()->out(false), 'text' => $action->get_text(),
'opentoblank' => $opentoblank];
$fullmessageplaintext .= get_string('insightinfomessageaction', 'analytics', $actiondata) . PHP_EOL;
$messageactions[] = $actiondata;
}
@@ -246,6 +246,11 @@ abstract class base {
foreach ($this->indicators as $key => $indicator) {
$this->indicators[$key] = call_user_func(array($indicator, 'instance'));
}
// Free memory ASAP.
gc_collect_cycles();
gc_mem_caches();
return $this->indicators;
}
+10 -1
View File
@@ -95,6 +95,15 @@ abstract class base extends \core_analytics\calculable {
return true;
}
/**
* Should the insights of this model be linked from reports?
*
* @return bool
*/
public function link_insights_report(): bool {
return true;
}
/**
* Based on facts (processed by machine learning backends) by default.
*
@@ -137,7 +146,7 @@ abstract class base extends \core_analytics\calculable {
$actions = array();
if ($includedetailsaction) {
if ($this->link_insights_report() && $includedetailsaction) {
$predictionurl = new \moodle_url('/report/insights/prediction.php', array('id' => $predictionid));
$detailstext = $this->get_view_details_text();
+83 -28
View File
@@ -480,6 +480,9 @@ class manager {
/**
* Returns the models with insights at the provided context.
*
* Note that this method is used for display purposes. It filters out models whose insights
* are not linked from the reports page.
*
* @param \context $context
* @return \core_analytics\model[]
*/
@@ -490,13 +493,52 @@ class manager {
$models = self::get_all_models(true, true, $context);
foreach ($models as $key => $model) {
// Check that it not only have predictions but also generates insights from them.
if (!$model->uses_insights()) {
if (!$model->uses_insights() || !$model->get_target()->link_insights_report()) {
unset($models[$key]);
}
}
return $models;
}
/**
* Returns the models that generated insights in the provided context. It can also be used to add new models to the context.
*
* Note that if you use this function with $newmodelid is the caller responsibility to ensure that the
* provided model id generated insights for the provided context.
*
* @throws \coding_exception
* @param \context $context
* @param int|null $newmodelid A new model to add to the list of models with insights in the provided context.
* @return int[]
*/
public static function cached_models_with_insights(\context $context, int $newmodelid = null) {
$cache = \cache::make('core', 'contextwithinsights');
$modelids = $cache->get($context->id);
if ($modelids === false) {
// The cache is empty, but we don't know if it is empty because there are no insights
// in this context or because cache/s have been purged, we need to be conservative and
// "pay" 1 db read to fill up the cache.
$models = \core_analytics\manager::get_models_with_insights($context);
if ($newmodelid && empty($models[$newmodelid])) {
throw new \coding_exception('The provided modelid ' . $newmodelid . ' did not generate any insights');
}
$modelids = array_keys($models);
$cache->set($context->id, $modelids);
} else if ($newmodelid && !in_array($newmodelid, $modelids)) {
// We add the context we got as an argument to the cache.
array_push($modelids, $newmodelid);
$cache->set($context->id, $modelids);
}
return $modelids;
}
/**
* Returns a prediction
*
@@ -549,30 +591,32 @@ class manager {
public static function cleanup() {
global $DB;
// Clean up stuff that depends on contexts that do not exist anymore.
$sql = "SELECT DISTINCT ap.contextid FROM {analytics_predictions} ap
LEFT JOIN {context} ctx ON ap.contextid = ctx.id
WHERE ctx.id IS NULL";
$apcontexts = $DB->get_records_sql($sql);
$DB->execute("DELETE FROM {analytics_prediction_actions} WHERE predictionid IN
(SELECT ap.id FROM {analytics_predictions} ap
LEFT JOIN {context} ctx ON ap.contextid = ctx.id
WHERE ctx.id IS NULL)");
$sql = "SELECT DISTINCT aic.contextid FROM {analytics_indicator_calc} aic
LEFT JOIN {context} ctx ON aic.contextid = ctx.id
WHERE ctx.id IS NULL";
$indcalccontexts = $DB->get_records_sql($sql);
$contexts = $apcontexts + $indcalccontexts;
if ($contexts) {
list($sql, $params) = $DB->get_in_or_equal(array_keys($contexts));
$DB->execute("DELETE FROM {analytics_prediction_actions} WHERE predictionid IN
(SELECT ap.id FROM {analytics_predictions} ap WHERE ap.contextid $sql)", $params);
$DB->delete_records_select('analytics_predictions', "contextid $sql", $params);
$DB->delete_records_select('analytics_indicator_calc', "contextid $sql", $params);
}
$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)");
// Clean up stuff that depends on analysable ids that do not exist anymore.
$models = self::get_all_models();
foreach ($models as $model) {
// We first dump into memory the list of analysables we have in the database (we could probably do this with 1 single
// query for the 3 tables, but it may be safer to do it separately).
$predictsamplesanalysableids = $DB->get_fieldset_select('analytics_predict_samples', 'DISTINCT analysableid',
'modelid = :modelid', ['modelid' => $model->get_id()]);
$predictsamplesanalysableids = array_flip($predictsamplesanalysableids);
$trainsamplesanalysableids = $DB->get_fieldset_select('analytics_train_samples', 'DISTINCT analysableid',
'modelid = :modelid', ['modelid' => $model->get_id()]);
$trainsamplesanalysableids = array_flip($trainsamplesanalysableids);
$usedanalysablesanalysableids = $DB->get_fieldset_select('analytics_used_analysables', 'DISTINCT analysableid',
'modelid = :modelid', ['modelid' => $model->get_id()]);
$usedanalysablesanalysableids = array_flip($usedanalysablesanalysableids);
$analyser = $model->get_analyser(array('notimesplitting' => true));
$analysables = $analyser->get_analysables_iterator();
@@ -581,17 +625,28 @@ class manager {
if (!$analysable) {
continue;
}
$analysableids[] = $analysable->get_id();
}
if (empty($analysableids)) {
continue;
unset($predictsamplesanalysableids[$analysable->get_id()]);
unset($trainsamplesanalysableids[$analysable->get_id()]);
unset($usedanalysablesanalysableids[$analysable->get_id()]);
}
list($notinsql, $params) = $DB->get_in_or_equal($analysableids, SQL_PARAMS_NAMED, 'param', false);
$params['modelid'] = $model->get_id();
$param = ['modelid' => $model->get_id()];
$DB->delete_records_select('analytics_predict_samples', "modelid = :modelid AND analysableid $notinsql", $params);
$DB->delete_records_select('analytics_train_samples', "modelid = :modelid AND analysableid $notinsql", $params);
if ($predictsamplesanalysableids) {
list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($predictsamplesanalysableids), SQL_PARAMS_NAMED);
$DB->delete_records_select('analytics_predict_samples', "modelid = :modelid AND analysableid $idssql",
$param + $idsparams);
}
if ($trainsamplesanalysableids) {
list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($trainsamplesanalysableids), SQL_PARAMS_NAMED);
$DB->delete_records_select('analytics_train_samples', "modelid = :modelid AND analysableid $idssql",
$param + $idsparams);
}
if ($usedanalysablesanalysableids) {
list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($usedanalysablesanalysableids), SQL_PARAMS_NAMED);
$DB->delete_records_select('analytics_used_analysables', "modelid = :modelid AND analysableid $idssql",
$param + $idsparams);
}
}
}
+91 -39
View File
@@ -933,19 +933,11 @@ class model {
$this->get_target()->generate_insight_notifications($this->model->id, $samplecontexts, $predictions);
// Update cache.
$cache = \cache::make('core', 'contextwithinsights');
foreach ($samplecontexts as $context) {
$modelids = $cache->get($context->id);
if (!$modelids) {
// The cache is empty, but we don't know if it is empty because there are no insights
// in this context or because cache/s have been purged, we need to be conservative and
// "pay" 1 db read to fill up the cache.
$models = \core_analytics\manager::get_models_with_insights($context);
$cache->set($context->id, array_keys($models));
} else if (!in_array($this->get_id(), $modelids)) {
array_push($modelids, $this->get_id());
$cache->set($context->id, $modelids);
if ($this->get_target()->link_insights_report()) {
// Update cache.
foreach ($samplecontexts as $context) {
\core_analytics\manager::cached_models_with_insights($context, $this->get_id());
}
}
}
@@ -1000,7 +992,7 @@ class model {
}
// Get all samples data.
list($sampleids, $samplesdata) = $this->get_analyser()->get_samples($sampleids);
list($sampleids, $samplesdata) = $this->get_samples($sampleids);
// Calculate the targets.
$predictions = array();
@@ -1314,7 +1306,7 @@ class model {
return $prediction->sampleid;
}, $predictions);
list($unused, $samplesdata) = $this->get_analyser()->get_samples($sampleids);
list($unused, $samplesdata) = $this->get_samples($sampleids);
$current = 0;
@@ -1356,7 +1348,7 @@ class model {
*/
public function prediction_sample_data($predictionobj) {
list($unused, $samplesdata) = $this->get_analyser()->get_samples(array($predictionobj->sampleid));
list($unused, $samplesdata) = $this->get_samples(array($predictionobj->sampleid));
if (empty($samplesdata[$predictionobj->sampleid])) {
throw new \moodle_exception('errorsamplenotavailable', 'analytics');
@@ -1668,12 +1660,8 @@ class model {
$predictor->clear_model($this->get_unique_id(), $this->get_output_dir());
}
$predictionids = $DB->get_fieldset_select('analytics_predictions', 'id', 'modelid = :modelid',
array('modelid' => $this->get_id()));
if ($predictionids) {
list($sql, $params) = $DB->get_in_or_equal($predictionids);
$DB->delete_records_select('analytics_prediction_actions', "predictionid $sql", $params);
}
$DB->delete_records_select('analytics_prediction_actions', "predictionid IN
(SELECT id FROM {analytics_predictions} WHERE modelid = :modelid)", ['modelid' => $this->get_id()]);
$DB->delete_records('analytics_predictions', array('modelid' => $this->model->id));
$DB->delete_records('analytics_predict_samples', array('modelid' => $this->model->id));
@@ -1759,30 +1747,94 @@ class model {
$contextids = array_map(function($predictionobj) {
return $predictionobj->contextid;
}, $predictionrecords);
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
// We select the fields that will allow us to map ids to $predictionrecords. Given that we already filter by modelid
// we have enough with sampleid and rangeindex. The reason is that the sampleid relation to a site is N - 1.
$fields = 'id, sampleid, rangeindex';
// Limited to 30000 records as a middle point between the ~65000 params limit in pgsql and the size limit for mysql which
// can be increased if required up to a reasonable point.
$chunks = array_chunk($contextids, 30000);
foreach ($chunks as $contextidschunk) {
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextidschunk, SQL_PARAMS_NAMED);
// We include the contextid and the timecreated filter to reduce the number of records in $dbpredictions. We can not
// add as many OR conditions as records in $predictionrecords.
$sql = "SELECT $fields
FROM {analytics_predictions}
WHERE modelid = :modelid
AND contextid $contextsql
AND timecreated >= :firsttimecreated";
$params = $contextparams + ['modelid' => $this->model->id, 'firsttimecreated' => $firstprediction->timecreated];
$dbpredictions = $DB->get_recordset_sql($sql, $params);
foreach ($dbpredictions as $id => $dbprediction) {
// The append_rangeindex implementation is the same regardless of the time splitting method in use.
$uniqueid = $this->get_time_splitting()->append_rangeindex($dbprediction->sampleid, $dbprediction->rangeindex);
$predictionrecords[$uniqueid]->id = $dbprediction->id;
// We select the fields that will allow us to map ids to $predictionrecords. Given that we already filter by modelid
// we have enough with sampleid and rangeindex. The reason is that the sampleid relation to a site is N - 1.
$fields = 'id, sampleid, rangeindex';
// We include the contextid and the timecreated filter to reduce the number of records in $dbpredictions. We can not
// add as many OR conditions as records in $predictionrecords.
$sql = "SELECT $fields
FROM {analytics_predictions}
WHERE modelid = :modelid
AND contextid $contextsql
AND timecreated >= :firsttimecreated";
$params = $contextparams + ['modelid' => $this->model->id, 'firsttimecreated' => $firstprediction->timecreated];
$dbpredictions = $DB->get_recordset_sql($sql, $params);
foreach ($dbpredictions as $id => $dbprediction) {
// The append_rangeindex implementation is the same regardless of the time splitting method in use.
$uniqueid = $this->get_time_splitting()->append_rangeindex($dbprediction->sampleid, $dbprediction->rangeindex);
$predictionrecords[$uniqueid]->id = $dbprediction->id;
}
}
return $predictionrecords;
}
/**
* Wrapper around analyser's get_samples to skip DB's max-number-of-params exception.
*
* @param array $sampleids
* @return array
*/
public function get_samples(array $sampleids): array {
if (empty($sampleids)) {
throw new \coding_exception('No sample ids provided');
}
$chunksize = count($sampleids);
// We start with just 1 chunk, if it is too large for the db we split the list of sampleids in 2 and we
// try again. We repeat this process until the chunk is small enough for the db engine to process. The
// >= has been added in case there are other \dml_read_exceptions unrelated to the max number of params.
while (empty($done) && $chunksize >= 1) {
$chunks = array_chunk($sampleids, $chunksize);
$allsampleids = [];
$allsamplesdata = [];
foreach ($chunks as $index => $chunk) {
try {
list($chunksampleids, $chunksamplesdata) = $this->get_analyser()->get_samples($chunk);
} catch (\dml_read_exception $e) {
// Reduce the chunksize, we use floor() so the $chunksize is always less than the previous $chunksize value.
$chunksize = floor($chunksize / 2);
break;
}
// We can sum as these two arrays are indexed by sampleid and there are no collisions.
$allsampleids = $allsampleids + $chunksampleids;
$allsamplesdata = $allsamplesdata + $chunksamplesdata;
if ($index === count($chunks) - 1) {
// We successfully processed all the samples in all chunks, we are done.
$done = true;
}
}
}
if (empty($done)) {
if (!empty($e)) {
// Throw the last exception we caught, the \dml_read_exception we have been catching is unrelated to the max number
// of param's exception.
throw new \dml_read_exception($e);
} else {
throw new \coding_exception('We should never reach this point, there is a bug in ' .
'core_analytics\\model::get_samples\'s code');
}
}
return [$allsampleids, $allsamplesdata];
}
/**
* Purges the insights cache.
*/
+2 -6
View File
@@ -306,13 +306,9 @@ class provider implements
$idssql = "SELECT ap.id FROM {analytics_predictions} ap
WHERE ap.contextid = :contextid AND ap.modelid = :modelid";
$idsparams = ['contextid' => $context->id, 'modelid' => $modelid];
$predictionids = $DB->get_fieldset_sql($idssql, $idsparams);
if ($predictionids) {
list($predictionidssql, $params) = $DB->get_in_or_equal($predictionids, SQL_PARAMS_NAMED);
$DB->delete_records_select('analytics_prediction_actions', "predictionid IN ($idssql)", $idsparams);
$DB->delete_records_select('analytics_predictions', "id $predictionidssql", $params);
}
$DB->delete_records_select('analytics_prediction_actions', "predictionid IN ($idssql)", $idsparams);
$DB->delete_records_select('analytics_predictions', "contextid = :contextid AND modelid = :modelid", $idsparams);
}
// We delete them all this table is just a cache and we don't know which model filled it.
@@ -33,7 +33,8 @@
"text": "Moodle"
}, {
"url": "https://en.wikipedia.org/wiki/Noodle",
"text": "Noodle"
"text": "Noodle",
"opentoblank": 1
}
]
}
@@ -65,5 +66,5 @@ body:not(.dir-ltr):not(.dir-rtl) .btn-insight {
<br/>
{{#actions}}
<a class="btn btn-default m-r-1 m-b-1 btn-insight" href="{{url}}">{{text}}</a>
<a class="btn btn-default m-r-1 m-b-1 btn-insight" {{#opentoblank}}target="_blank" {{/opentoblank}}href="{{url}}">{{text}}</a>
{{/actions}}
+4 -2
View File
@@ -134,8 +134,9 @@ class analytics_manager_testcase extends advanced_testcase {
$model->train();
$model->predict();
$npredictsamples = $DB->count_records('analytics_predict_samples');
$ntrainsamples = $DB->count_records('analytics_train_samples');
$this->assertNotEmpty($DB->count_records('analytics_predict_samples'));
$this->assertNotEmpty($DB->count_records('analytics_train_samples'));
$this->assertNotEmpty($DB->count_records('analytics_used_analysables'));
// Now we delete an analysable, stored predict and training samples should be deleted.
$deletedcontext = \context_course::instance($coursepredict1->id);
@@ -145,6 +146,7 @@ class analytics_manager_testcase extends advanced_testcase {
$this->assertEmpty($DB->count_records('analytics_predict_samples', array('analysableid' => $coursepredict1->id)));
$this->assertEmpty($DB->count_records('analytics_train_samples', array('analysableid' => $coursepredict1->id)));
$this->assertEmpty($DB->count_records('analytics_used_analysables', array('analysableid' => $coursepredict1->id)));
set_config('enabled_stores', '', 'tool_log');
get_log_manager(true);
+55
View File
@@ -494,6 +494,61 @@ class analytics_model_testcase extends advanced_testcase {
$this->assertEquals($data->name['value'], '');
}
/**
* Tests model::get_samples()
*
* @return null
*/
public function test_get_samples() {
$this->resetAfterTest();
if (!PHPUNIT_LONGTEST) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}
// 10000 should be enough to make oracle and mssql fail, if we want pgsql to fail we need around 70000
// users, that is a few minutes just to create the users.
$nusers = 10000;
$userids = [];
for ($i = 0; $i < $nusers; $i++) {
$user = $this->getDataGenerator()->create_user();
$userids[] = $user->id;
}
$upcomingactivities = null;
foreach (\core_analytics\manager::get_all_models() as $model) {
if (get_class($model->get_target()) === 'core_user\\analytics\\target\\upcoming_activities_due') {
$upcomingactivities = $model;
}
}
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($userids);
$this->assertCount($nusers, $sampleids);
$this->assertCount($nusers, $samplesdata);
$subset = array_slice($userids, 0, 100);
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
$this->assertCount(100, $sampleids);
$this->assertCount(100, $samplesdata);
$subset = array_slice($userids, 0, 2);
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
$this->assertCount(2, $sampleids);
$this->assertCount(2, $samplesdata);
$subset = array_slice($userids, 0, 1);
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
$this->assertCount(1, $sampleids);
$this->assertCount(1, $samplesdata);
// Unexisting, so nothing returned, but still 2 arrays.
list($sampleids, $samplesdata) = $upcomingactivities->get_samples([1231231231231231]);
$this->assertEmpty($sampleids);
$this->assertEmpty($samplesdata);
}
/**
* Generates a model log record.
*/
+11 -7
View File
@@ -365,13 +365,17 @@ class auth_plugin_cas extends auth_plugin_ldap {
return [];
}
$iconurl = moodle_url::make_pluginfile_url(
context_system::instance()->id,
'auth_cas',
'logo',
null,
'/',
$this->config->auth_logo);
if ($this->config->auth_logo) {
$iconurl = moodle_url::make_pluginfile_url(
context_system::instance()->id,
'auth_cas',
'logo',
null,
null,
$this->config->auth_logo);
} else {
$iconurl = null;
}
return [
[
+1 -2
View File
@@ -402,7 +402,6 @@ class api {
* @return bool
*/
public static function is_enabled() {
$plugininfo = \core_plugin_manager::instance()->get_plugin_info('auth_oauth2');
return $plugininfo->is_enabled();
return is_enabled_auth('oauth2');
}
}
+34
View File
@@ -50,5 +50,39 @@ function xmldb_auth_oauth2_upgrade($oldversion) {
// Automatically generated Moodle v3.7.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2019052001) {
// Fetch Facebook, Google, and Microsoft issuers. We use the URL field to determine the issuer type as it's the only
// field that contains the keyword that can somewhat let us reliably determine the issuer type.
$likefacebook = $DB->sql_like('oe.url', ':facebook');
$likegoogle = $DB->sql_like('oe.url', ':google');
$likemicrosoft = $DB->sql_like('oe.url', ':microsoft');
$params = [
'facebook' => '%facebook%',
'google' => '%google%',
'microsoft' => '%microsoft%',
];
// We're querying from the oauth2_endpoint table because the base URLs of FB and Microsoft can be empty in the issuer table.
$subsql = "
SELECT DISTINCT oe.issuerid
FROM {oauth2_endpoint} oe
WHERE $likefacebook
OR $likegoogle
OR $likemicrosoft";
// Update non-Facebook/Google/Microsoft issuers and set requireconfirmation to 1.
$updatesql = "
UPDATE {oauth2_issuer}
SET requireconfirmation = 1
WHERE id NOT IN ({$subsql})";
$DB->execute($updatesql, $params);
// Delete linked logins for non-Facebook/Google/Microsoft issuers. They can easily re-link their logins anyway.
$DB->delete_records_select('auth_oauth2_linked_login', "issuerid NOT IN ($subsql)", $params);
upgrade_plugin_savepoint(true, 2019052001, 'auth', 'oauth2');
}
return true;
}
+8 -2
View File
@@ -40,7 +40,10 @@ then cut and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
{$a->admin}';
{$a->admin}
If you did not do this, someone else could be trying to compromise your account.
Please contact the site administrator immediately.';
$string['confirmaccountemailsubject'] = '{$a}: account confirmation';
$string['confirmationinvalid'] = 'The confirmation link is either invalid, or has expired. Please start the login process again to generate a new confirmation email.';
$string['confirmationpending'] = 'This account is pending email confirmation.';
@@ -60,7 +63,10 @@ then cut and paste the address into the address
line at the top of your web browser window.
If you need help, please contact the site administrator,
{$a->admin}';
{$a->admin}
If you did not do this, someone else could be trying to compromise your account.
Please contact the site administrator immediately.';
$string['confirmlinkedloginemailsubject'] = '{$a}: linked login confirmation';
$string['createaccountswarning'] = 'This authentication plugin allows users to create accounts on your site. You may want to enable the setting "authpreventaccountcreation" if you use this plugin.';
$string['createnewlinkedlogin'] = 'Link a new account ({$a})';
+19
View File
@@ -140,4 +140,23 @@ class auth_oauth2_external_testcase extends advanced_testcase {
$this->assertEquals($newuser->id, $match->get('userid'));
}
/**
* Test that is_enabled correctly identifies when the plugin is enabled.
*/
public function test_is_enabled() {
$this->resetAfterTest();
set_config('auth', 'manual,oauth2');
$this->assertTrue(\auth_oauth2\api::is_enabled());
}
/**
* Test that is_enabled correctly identifies when the plugin is disabled.
*/
public function test_is_enabled_disabled() {
$this->resetAfterTest();
set_config('auth', 'manual');
$this->assertFalse(\auth_oauth2\api::is_enabled());
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2019052000; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2019052001; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2019051100; // Requires this Moodle version.
$plugin->component = 'auth_oauth2'; // Full name of the plugin (used for diagnostics).
+13 -6
View File
@@ -294,12 +294,19 @@ class auth_plugin_shibboleth extends auth_plugin_base {
}
$url = new moodle_url('/auth/shibboleth/index.php');
$iconurl = moodle_url::make_pluginfile_url(context_system::instance()->id,
'auth_shibboleth',
'logo',
null,
'/',
$config->auth_logo);
if ($config->auth_logo) {
$iconurl = moodle_url::make_pluginfile_url(
context_system::instance()->id,
'auth_shibboleth',
'logo',
null,
null,
$config->auth_logo);
} else {
$iconurl = null;
}
$result[] = ['url' => $url, 'iconurl' => $iconurl, 'name' => $config->login_name];
return $result;
}
+10 -3
View File
@@ -42,16 +42,23 @@ class behat_auth extends behat_base {
* Logs in the user. There should exist a user with the same value as username and password.
*
* @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
* @param string $username the user to log in as.
* @param moodle_url|null $wantsurl optional, URL to go to after logging in.
*/
public function i_log_in_as($username) {
// In the mobile app the required tasks are different.
public function i_log_in_as(string $username, moodle_url $wantsurl = null) {
// In the mobile app the required tasks are different (does not support $wantsurl).
if ($this->is_in_app()) {
$this->execute('behat_app::login', [$username]);
return;
}
$loginurl = new moodle_url('/login/index.php');
if ($wantsurl !== null) {
$loginurl->param('wantsurl', $wantsurl->out_as_local_url());
}
// Visit login page.
$this->getSession()->visit($this->locate_path('login/index.php'));
$this->getSession()->visit($this->locate_path($loginurl->out_as_local_url()));
// Enter username and password.
$this->execute('behat_forms::i_set_the_field_to', array('Username', $this->escape($username)));
+3
View File
@@ -123,6 +123,9 @@ if (!async_helper::is_async_pending($id, 'course', 'backup')) {
if (!($bc = backup_ui::load_controller($backupid))) {
$bc = new backup_controller($type, $id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, $backupmode, $USER->id);
// The backup id did not relate to a valid controller so we made a new controller.
// Now we need to reset the backup id to match the new controller.
$backupid = $bc->get_backupid();
}
// Prepare a progress bar which can display optionally during long-running
+14 -5
View File
@@ -127,7 +127,12 @@ class core_badges_assertion {
$hash = $this->_data->uniquehash;
$email = empty($this->_data->backpackemail) ? $this->_data->email : $this->_data->backpackemail;
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
$classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1));
if ($this->_obversion == OPEN_BADGES_V2) {
$classurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id()));
} else {
$classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1));
}
// Required.
$assertion['uid'] = $hash;
@@ -193,7 +198,11 @@ class core_badges_assertion {
$class['image'] = 'data:image/png;base64,' . $imagedata;
$class['criteria'] = $this->_url->out(false); // Currently issued badge URL.
if ($issued) {
$issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0));
if ($this->_obversion == OPEN_BADGES_V2) {
$issuerurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id(), 'action' => 0));
} else {
$issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0));
}
$class['issuer'] = $issuerurl->out(false);
}
$this->embed_data_badge_version2($class, OPEN_BADGES_V2_TYPE_BADGE);
@@ -329,10 +338,10 @@ class core_badges_assertion {
$hash = $this->_data->uniquehash;
$assertionsurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
$classurl = new moodle_url(
'/badges/assertion.php',
array('b' => $hash, 'action' => 1, 'obversion' => $this->_obversion)
'/badges/badge_json.php',
array('id' => $this->get_badge_id())
);
$issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0,
$issuerurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id(), 'action' => 0,
'obversion' => $this->_obversion));
// For assertion.
if ($type == OPEN_BADGES_V2_TYPE_ASSERTION) {
+65
View File
@@ -0,0 +1,65 @@
<?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/>.
/**
* Contains upgrade and install functions for badges.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Called on install or upgrade to create default list of backpacks a user can connect to.
* Don't use the global defines from badgeslib because this is for install/upgrade.
*
* @return void
*/
function badges_install_default_backpacks() {
global $DB;
$record = new stdClass();
$record->backpackweburl = 'https://backpack.openbadges.org';
$record->backpackapiurl = 'https://backpack.openbadges.org';
$record->apiversion = 1;
$record->sortorder = 0;
$record->password = '';
if (!($bp = $DB->get_record('badge_external_backpack', array('backpackapiurl' => $record->backpackapiurl)))) {
$bpid = $DB->insert_record('badge_external_backpack', $record);
} else {
$bpid = $bp->id;
}
set_config('badges_site_backpack', $bpid);
// All existing backpacks default to V1.
$DB->set_field('badge_backpack', 'externalbackpackid', $bpid);
$record = new stdClass();
$record->backpackapiurl = 'https://api.badgr.io/v2';
$record->backpackweburl = 'https://badgr.io';
$record->apiversion = 2;
$record->sortorder = 1;
$record->password = '';
if (!$DB->record_exists('badge_external_backpack', array('backpackapiurl' => $record->backpackapiurl))) {
$DB->insert_record('badge_external_backpack', $record);
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file keeps track of upgrades to the myoverview block
*
* @since 3.7.3
* @package block_myoverview
* @copyright 2019 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Upgrade code for the MyOverview block.
*
* @param int $oldversion
*/
function xmldb_block_myoverview_upgrade($oldversion) {
global $DB;
if ($oldversion < 2019052001) {
// Remove orphaned course favourites, which weren't being deleted when the course was deleted.
$sql = 'SELECT f.id
FROM {favourite} f
LEFT JOIN {course} c
ON (c.id = f.itemid)
WHERE f.component = :component
AND f.itemtype = :itemtype
AND c.id IS NULL';
$params = ['component' => 'core_course', 'itemtype' => 'courses'];
if ($records = $DB->get_fieldset_sql($sql, $params)) {
$chunks = array_chunk($records, 1000);
foreach ($chunks as $chunk) {
list($insql, $inparams) = $DB->get_in_or_equal($chunk);
$DB->delete_records_select('favourite', "id $insql", $inparams);
}
}
upgrade_block_savepoint(true, 2019052001, 'myoverview', false);
}
return true;
}
@@ -37,7 +37,7 @@ $string['aria:displaydropdown'] = 'Display drop-down menu';
$string['aria:favourites'] = 'Show starred courses';
$string['aria:future'] = 'Show future courses';
$string['aria:groupingdropdown'] = 'Grouping drop-down menu';
$string['aria:inprogress'] = 'Show in courses in progress';
$string['aria:inprogress'] = 'Show courses in progress';
$string['aria:lastaccessed'] = 'Sort courses by last accessed date';
$string['aria:list'] = 'Switch to list view';
$string['aria:title'] = 'Sort courses by course name';
+11
View File
@@ -121,3 +121,14 @@ function block_myoverview_user_preferences() {
return $preferences;
}
/**
* Pre-delete course hook to cleanup any records with references to the deleted course.
*
* @param stdClass $course The deleted course
*/
function block_myoverview_pre_course_delete(\stdClass $course) {
// Removing any starred courses which have been created for users, for this course.
$service = \core_favourites\service_factory::get_service_for_component('core_course');
$service->delete_favourites_by_type_and_item('courses', $course->id);
}
@@ -29,7 +29,8 @@
"fullname": "course 3",
"hasprogress": true,
"progress": 10,
"coursecategory": "Miscellaneous"
"coursecategory": "Miscellaneous",
"visible": true
}
]
}
@@ -29,7 +29,8 @@
"fullname": "course 3",
"hasprogress": true,
"progress": 10,
"coursecategory": "Miscellaneous"
"coursecategory": "Miscellaneous",
"visible": true
}
]
}
@@ -65,6 +66,11 @@
</span>
{{{fullname}}}
</a>
{{^visible}}
<div class="d-flex flex-wrap">
<span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
</div>
{{/visible}}
</div>
</div>
{{#hasprogress}}
@@ -30,7 +30,8 @@
"summary": "This course is about assignments",
"hasprogress": true,
"progress": 10,
"coursecategory": "Miscellaneous"
"coursecategory": "Miscellaneous",
"visible": true
}
]
}
@@ -75,6 +76,11 @@
</a>
{{> block_myoverview/course-action-menu }}
</div>
{{^visible}}
<div class="d-flex flex-wrap">
<span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
</div>
{{/visible}}
<div class="summary">
<span class="sr-only">{{#str}}aria:coursesummary, block_myoverview{{/str}}</span>
{{{summary}}}
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2019052000; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2019052001; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2019051100; // Requires this Moodle version.
$plugin->component = 'block_myoverview'; // Full name of the plugin (used for diagnostics).
@@ -114,12 +114,8 @@ class behat_block_site_main_menu extends behat_base {
protected function get_site_menu_activity_element($element, $selectortype, $activityname) {
$activitynode = $this->get_site_menu_activity_node($activityname);
// Transforming to Behat selector/locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
$selectortype . '" in "' . $activityname . '" ');
return $this->find($selector, $locator, $exception, $activitynode);
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
return $this->find($selectortype, $element, $exception, $activitynode);
}
/**
@@ -56,7 +56,11 @@ class behat_block_social_activities extends behat_base {
}
/**
* Checks that the specified activity's action menu contains an item.
* Checks that the specified activity in the social activities block should have the specified editing icon.
*
* This includes items in the action menu for the item (does not require it to be open)
*
* You should be in the course page with editing mode turned on.
*
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
* @param string $activityname
@@ -71,7 +75,11 @@ class behat_block_social_activities extends behat_base {
}
/**
* Checks that the specified activity's action menu contains an item.
* Checks that the specified activity in the social activities block should not have the specified editing icon.
*
* This includes items in the action menu for the item (does not require it to be open)
*
* You should be in the course page with editing mode turned on.
*
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should not have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
* @param string $activityname
@@ -103,7 +111,7 @@ class behat_block_social_activities extends behat_base {
}
/**
* Clicks on the specified element inside the activity container.
* Finds the element containing a specific activity in the social activity block.
*
* @throws ElementNotFoundException
* @param string $element
@@ -114,16 +122,12 @@ class behat_block_social_activities extends behat_base {
protected function get_social_block_activity_element($element, $selectortype, $activityname) {
$activitynode = $this->get_social_block_activity_node($activityname);
// Transforming to Behat selector/locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
$selectortype . '" in "' . $activityname . '" ');
return $this->find($selector, $locator, $exception, $activitynode);
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
return $this->find($selectortype, $element, $exception, $activitynode);
}
/**
* Checks that the specified activity is hidden.
* Checks that the specified activity is hidden in the social activities block.
*
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should be hidden$/
* @param string $activityname
@@ -133,7 +137,7 @@ class behat_block_social_activities extends behat_base {
}
/**
* Checks that the specified activity is hidden.
* Checks that the specified activity is hidden in the social activities block.
*
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should be available but hidden from course page$/
* @param string $activityname
@@ -143,7 +147,7 @@ class behat_block_social_activities extends behat_base {
}
/**
* Opens an activity actions menu if it is not already opened.
* Opens an activity actions menu in the social activities block if it is not already opened.
*
* @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu in social activities block$/
* @throws DriverException The step is not available when Javascript is disabled
+9 -4
View File
@@ -88,13 +88,18 @@ class block_starredcourses_external extends core_course_external {
return ($a->timemodified > $b->timemodified) ? -1 : 1;
});
$formattedcourses = array_map(function($favourite) use ($renderer) {
$formattedcourses = array();
foreach ($favourites as $favourite) {
$course = get_course($favourite->itemid);
$context = \context_course::instance($favourite->itemid);
$canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context);
$exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => true]);
return $exporter->export($renderer);
}, $favourites);
if ($course->visible || $canviewhiddencourses) {
$exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => true]);
$formattedcourse = $exporter->export($renderer);
$formattedcourses[] = $formattedcourse;
}
}
return $formattedcourses;
}
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core/notification","core/templates","core/paged_content_factory","core/str","core/user_date","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g){var h=86400,i={EMPTY_MESSAGE:'[data-region="empty-message"]',ROOT:'[data-region="event-list-container"]',EVENT_LIST_CONTENT:'[data-region="event-list-content"]',EVENT_LIST_LOADING_PLACEHOLDER:'[data-region="event-list-loading-placeholder"]'},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content"},k={ignoreControlWhileLoading:!0,controlPlacementBottom:!0,ariaLabels:{itemsperpagecomponents:"ariaeventlistpagelimit, block_timeline"}},l=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden"),a.find(i.EMPTY_MESSAGE).removeClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden"),a.find(i.EMPTY_MESSAGE).addClass("hidden")},n=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},o=function(a,b){var c={},d={eventsbyday:[]};return a.forEach(function(a){var d=f.getUserMidnightForTimestamp(a.timesort,b);c[d]?c[d].push(a):c[d]=[a]}),Object.keys(c).forEach(function(a){var e=c[a];d.eventsbyday.push({past:a<b,dayTimestamp:a,events:e})}),d},p=function(a,b){var d=o(a,b),e=j.EVENT_LIST_CONTENT;return c.render(e,d)},q=function(a,b,c,d,e,f){var i=a+c*h,j=void 0!=d&&a+d*h,k={starttime:i,limit:b};return e&&(k.aftereventid=e),j&&(k.endtime=j),f?(k.courseid=f,g.queryByCourse(k)):g.queryByTime(k)},r=function(a,b,c,d,e,f,g,h){for(var i=a.pageNumber,j=a.limit,k=i;!d.hasOwnProperty(k);)k--;var l=d[k],m=null;return m=e&&e.hasOwnProperty(i)?e[i]:q(c,j+1,g,h,l,f),m.then(function(a){if(!a.events.length)return b.allItemsLoaded(i),[];var c=a.events,d=c.length<=j;return d?b.allItemsLoaded(i):c.pop(),c})},s=function(c,f,g,h,i,j,l,m,n){var o={1:0},q=!1,s=a.extend({},k,n);return e.get_string("ariaeventlistpagelimit","block_timeline",a.isArray(c)?c[0].value:c).then(function(a){return s.ariaLabels.itemsperpage=a,s.ariaLabels.paginationnav=m,a}).then(function(){return d.createWithLimit(c,function(c,d){var e=[];return c.forEach(function(a){var c=a.pageNumber,h=r(a,d,g,o,f,i,j,l).then(function(a){if(a.length){q=!0;var b=a[a.length-1].id;return o[c+1]=b,p(a,g)}return a})["catch"](b.exception);e.push(h)}),a.when.apply(a,e).then(function(){h.resolve(q)})["catch"](function(){h.resolve(q)}),e},s)})},t=function(d,e,f,g,h){d=a(d);var j=a.Deferred(),k=d.find(i.EVENT_LIST_CONTENT),o=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),p=d.attr("data-course-id"),q=parseInt(d.attr("data-days-offset"),10),r=d.attr("data-days-limit"),t=parseInt(d.attr("data-midnight"),10);return n(d),m(d),o.removeClass("hidden"),void 0!=r&&(r=parseInt(r,10)),s(e,f,t,j,p,q,r,g,h).then(function(b,e){return b=a(b),b.addClass("hidden"),c.replaceNodeContents(k,b,e),j.then(function(a){return b.removeClass("hidden"),o.addClass("hidden"),a||l(d),a})["catch"](function(){return!1}),b})["catch"](b.exception)};return{init:t,rootSelector:i.ROOT}});
define(["jquery","core/notification","core/templates","core/paged_content_factory","core/str","core/user_date","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g){var h=86400,i={EMPTY_MESSAGE:'[data-region="empty-message"]',ROOT:'[data-region="event-list-container"]',EVENT_LIST_CONTENT:'[data-region="event-list-content"]',EVENT_LIST_LOADING_PLACEHOLDER:'[data-region="event-list-loading-placeholder"]'},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content"},k={ignoreControlWhileLoading:!0,controlPlacementBottom:!0,ariaLabels:{itemsperpagecomponents:"ariaeventlistpagelimit, block_timeline"}},l=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden"),a.find(i.EMPTY_MESSAGE).removeClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden"),a.find(i.EMPTY_MESSAGE).addClass("hidden")},n=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},o=function(a,b){var c={},d={eventsbyday:[]};return a.forEach(function(a){var d=f.getUserMidnightForTimestamp(a.timesort,b);c[d]?c[d].push(a):c[d]=[a]}),Object.keys(c).forEach(function(a){var e=c[a];d.eventsbyday.push({past:a<b,dayTimestamp:a,events:e})}),d},p=function(a,b){var d=o(a,b),e=j.EVENT_LIST_CONTENT;return c.render(e,d)},q=function(a,b,c,d,e,f){var i=a+c*h,j=void 0!=d&&a+d*h,k={starttime:i,limit:b};return e&&(k.aftereventid=e),j&&(k.endtime=j),f?(k.courseid=f,g.queryByCourse(k)):g.queryByTime(k)},r=function(a,b,c,d,e,f,g,h){for(var i=a.pageNumber,j=a.limit,k=i;!d.hasOwnProperty(k);)k--;var l=d[k],m=null;return m=e&&e.hasOwnProperty(i)?e[i]:q(c,j+1,g,h,l,f),m.then(function(a){if(!a.events.length)return b.allItemsLoaded(i),[];var c=a.events.filter(function(a){return"open"!=a.eventtype&&"opensubmission"!=a.eventtype}),d=c.length<=j;return d?b.allItemsLoaded(i):c.pop(),c})},s=function(c,f,g,h,i,j,l,m,n){var o={1:0},q=!1,s=a.extend({},k,n);return e.get_string("ariaeventlistpagelimit","block_timeline",a.isArray(c)?c[0].value:c).then(function(a){return s.ariaLabels.itemsperpage=a,s.ariaLabels.paginationnav=m,a}).then(function(){return d.createWithLimit(c,function(c,d){var e=[];return c.forEach(function(a){var c=a.pageNumber,h=r(a,d,g,o,f,i,j,l).then(function(a){if(a.length){q=!0;var b=a[a.length-1].id;return o[c+1]=b,p(a,g)}return a})["catch"](b.exception);e.push(h)}),a.when.apply(a,e).then(function(){h.resolve(q)})["catch"](function(){h.resolve(q)}),e},s)})},t=function(d,e,f,g,h){d=a(d);var j=a.Deferred(),k=d.find(i.EVENT_LIST_CONTENT),o=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),p=d.attr("data-course-id"),q=parseInt(d.attr("data-days-offset"),10),r=d.attr("data-days-limit"),t=parseInt(d.attr("data-midnight"),10);return n(d),m(d),o.removeClass("hidden"),void 0!=r&&(r=parseInt(r,10)),s(e,f,t,j,p,q,r,g,h).then(function(b,e){return b=a(b),b.addClass("hidden"),c.replaceNodeContents(k,b,e),j.then(function(a){return b.removeClass("hidden"),o.addClass("hidden"),a||l(d),a})["catch"](function(){return!1}),b})["catch"](b.exception)};return{init:t,rootSelector:i.ROOT}});
+4 -1
View File
@@ -265,7 +265,10 @@ function(
return [];
}
var calendarEvents = result.events;
var calendarEvents = result.events.filter(function(event) {
// Do not include events that does not have a due date.
return event.eventtype != "open" && event.eventtype != "opensubmission";
});
// We expect to receive limit + 1 events back from the server.
// Any less means there are no more events to load.
var loadedAll = calendarEvents.length <= limit;
@@ -65,7 +65,7 @@
}}
<div class="border-bottom pb-2">
{{#eventsbyday}}
<h5 class="h6 mt-3 mb-0 {{#past}}text-danger{{/past}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedayshort, core_langconfig {{/str}} {{/userdate}}</h5>
<h5 class="h6 mt-3 mb-0 {{#past}}text-danger{{/past}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}</h5>
{{> block_timeline/event-list-items }}
{{/eventsbyday}}
</div>
+1 -2
View File
@@ -460,8 +460,7 @@ class provider implements
$params = array_merge($inparams, ['userid' => $userid]);
$associds = $DB->get_fieldset_sql($sql, $params);
list($insql, $inparams) = $DB->get_in_or_equal($associds, SQL_PARAMS_NAMED, 'param', true);
$DB->delete_records_select('blog_association', "id $insql", $inparams);
$DB->delete_records_list('blog_association', 'id', $associds);
}
}
+31
View File
@@ -370,6 +370,37 @@ class core_blog_privacy_testcase extends provider_testcase {
$this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes']));
}
/**
* Test provider delete_data_for_user with a context that contains no entries
*
* @return void
*/
public function test_delete_data_for_user_empty_context() {
global $DB;
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
// Create a blog entry for user, associated with course.
$entry = new blog_entry($this->create_post(['userid' => $user->id, 'courseid' => $course->id])->id);
$entry->add_association($context->id);
// Generate list of contexts for user.
$contexts = provider::get_contexts_for_userid($user->id);
$this->assertContains($context->id, $contexts->get_contextids());
// Now delete the blog entry.
$entry->delete();
// Try to delete user data using contexts obtained prior to entry deletion.
$contextlist = new approved_contextlist($user, 'core_blog', $contexts->get_contextids());
provider::delete_data_for_user($contextlist);
// Sanity check to ensure blog_associations is really empty.
$this->assertEmpty($DB->get_records('blog_association', ['contextid' => $context->id]));
}
public function test_delete_data_for_all_users_in_context() {
global $DB;
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core_calendar/selectors","core_calendar/events","core/str","core/templates"],function(a,b,c,d,e){var f=function(d){d.on("click",b.eventFilterItem,function(b){var c=a(b.currentTarget);g(c),b.preventDefault()}),a("body").on(c.viewUpdated,function(){var c=d.find(b.eventFilterItem);c.each(function(b,c){if(c=a(c),c.data("eventtype-hidden")){var d=i(c);h(d)}})})},g=function(a){var b=i(a);return b.hidden=!b.hidden,d.get_string("eventtype"+b.eventtype,"calendar").then(function(a){return b.name=a,b}).then(function(a){return e.render("core_calendar/event_filter_key",a)}).then(function(b,c){return e.replaceNode(a,b,c)}).then(function(){h(b)})},h=function(b){M.util.js_pending("month-mini-filterChanged"),a("body").trigger(c.filterChanged,{type:b.eventtype,hidden:b.hidden}),M.util.js_complete("month-mini-filterChanged")},i=function(a){return{eventtype:a.data("eventtype"),hidden:a.data("eventtype-hidden")}};return{init:function(b){b=a(b),f(b)}}});
define(["jquery","core_calendar/selectors","core_calendar/events","core/str","core/templates"],function(a,b,c,d,e){var f=function(d){d.on("click",b.eventFilterItem,function(b){var c=a(b.currentTarget);g(c),b.preventDefault()}),a("body").on(c.viewUpdated,function(){var c=d.find(b.eventFilterItem);c.each(function(b,c){if(c=a(c),c.data("eventtype-hidden")){var d=i(c);h(d)}})})},g=function(a){var b=i(a);return b.hidden=!b.hidden,d.get_string("eventtype"+b.eventtype,"calendar").then(function(a){return b.name=a,b.icon=!0,b.key="i/"+b.eventtype+"event",b.component="core",b}).then(function(a){return e.render("core_calendar/event_filter_key",a)}).then(function(b,c){return e.replaceNode(a,b,c)}).then(function(){h(b)})},h=function(b){M.util.js_pending("month-mini-filterChanged"),a("body").trigger(c.filterChanged,{type:b.eventtype,hidden:b.hidden}),M.util.js_complete("month-mini-filterChanged")},i=function(a){return{eventtype:a.data("eventtype"),hidden:a.data("eventtype-hidden")}};return{init:function(b){b=a(b),f(b)}}});
+3
View File
@@ -67,6 +67,9 @@ function(
return Str.get_string('eventtype' + data.eventtype, 'calendar')
.then(function(nameStr) {
data.name = nameStr;
data.icon = true;
data.key = 'i/' + data.eventtype + 'event';
data.component = 'core';
return data;
})
@@ -70,11 +70,13 @@ class create_update_form_mapper implements create_update_form_mapper_interface {
'format' => $data->format
];
// We don't want to return the context because it's not a
// form value and breaks the validation.
// Don't return context or subscription because they're not form values and break validation.
if (isset($data->context)) {
unset($data->context);
}
if (isset($data->subscription)) {
unset($data->subscription);
}
return $data;
}
+5 -2
View File
@@ -2203,8 +2203,8 @@ function calendar_view_event_allowed(calendar_event $event) {
if (has_capability('moodle/calendar:manageentries', $event->context)) {
return true;
}
$mycourses = enrol_get_my_courses('id');
return isset($mycourses[$event->courseid]);
return can_access_course(get_course($event->courseid));
} else if ($event->userid) {
if ($event->userid != $USER->id) {
// No-one can ever see another users events.
@@ -3668,6 +3668,9 @@ function calendar_get_filter_types() {
return [
'eventtype' => $type,
'name' => get_string("eventtype{$type}", "calendar"),
'icon' => true,
'key' => 'i/'.$type.'event',
'component' => 'core'
];
}, $types);
}
+10 -4
View File
@@ -127,7 +127,8 @@ $searches = [];
$params = [];
$usedefaultfilters = true;
if (!empty($courseid) && $courseid == SITEID && !empty($types['site'])) {
if (!empty($types['site'])) {
$searches[] = "(eventtype = 'site')";
$usedefaultfilters = false;
}
@@ -144,9 +145,14 @@ if (!empty($courseid) && !empty($types['course'])) {
$usedefaultfilters = false;
}
if (!empty($categoryid) && !empty($types['category'])) {
$searches[] = "(eventtype = 'category' AND categoryid = :categoryid)";
$params += ['categoryid' => $categoryid];
if (!empty($types['category'])) {
if (!empty($categoryid)) {
$searches[] = "(eventtype = 'category' AND categoryid = :categoryid)";
$params += ['categoryid' => $categoryid];
} else {
$searches[] = "(eventtype = 'category')";
}
$usedefaultfilters = false;
}
+1 -1
View File
@@ -37,5 +37,5 @@
{{#pix}} icon, {{modulename}} {{/pix}}
{{/modulename}}
{{^modulename}}
{{#pix}} i/{{eventtype}}event, core {{/pix}}
{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}
{{/modulename}}
+1 -6
View File
@@ -136,12 +136,7 @@
{{$content}}
{{#events}}
<div data-popover-eventtype-{{normalisedeventtype}}="1">
{{#modulename}}
{{#pix}} icon, {{modulename}} {{/pix}}
{{/modulename}}
{{^modulename}}
{{#pix}} i/{{eventtype}}event, core {{/pix}}
{{/modulename}}
{{> core_calendar/event_icon}}
{{{popupname}}}
</div>
{{/events}}
@@ -47,3 +47,30 @@ Feature: Import and edit calendar events
And I view the calendar for "2" "2017"
And I should not see "Event on 2-25-2017"
And I should not see "Event on 2-20-2017"
Scenario: Import events using different event types.
Given I log in as "admin"
And I view the calendar for "1" "2016"
And I press "Manage subscriptions"
And I set the following fields to these values:
| Calendar name | Test Import |
| Import from | Calendar file (.ics) |
| Type of event | User |
And I upload "calendar/tests/fixtures/import.ics" file to "Calendar file (.ics)" filemanager
And I press "Add"
And I should see "User events"
And I set the following fields to these values:
| Calendar name | Test Import |
| Import from | Calendar file (.ics) |
| Type of event | Category |
| Category | Miscellaneous |
And I upload "calendar/tests/fixtures/import.ics" file to "Calendar file (.ics)" filemanager
And I press "Add"
And I should see "Category events"
And I set the following fields to these values:
| Calendar name | Test Import |
| Import from | Calendar file (.ics) |
| Type of event | Site |
And I upload "calendar/tests/fixtures/import.ics" file to "Calendar file (.ics)" filemanager
And I press "Add"
And I should see "Site events"
+68
View File
@@ -772,4 +772,72 @@ class core_calendar_lib_testcase extends advanced_testcase {
$this->assertEquals(array($coursegroups[$courses[0]->id][1]->id), $groupids);
$this->assertEquals($users[1]->id, $userid);
}
/**
* Test for calendar_view_event_allowed for course event types.
*/
public function test_calendar_view_event_allowed_course_event() {
global $USER;
$this->setAdminUser();
$generator = $this->getDataGenerator();
// A student in a course.
$student = $generator->create_user();
// Some user not enrolled in any course.
$someuser = $generator->create_user();
// A course with manual enrolments.
$manualcourse = $generator->create_course();
// Enrol the student to the manual enrolment course.
$generator->enrol_user($student->id, $manualcourse->id);
// A course that allows guest access.
$guestcourse = $generator->create_course(
(object)[
'shortname' => 'guestcourse',
'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
'enrol_guest_password_0' => ''
]);
$manualevent = (object)[
'name' => 'Manual course event',
'description' => '',
'format' => 1,
'categoryid' => 0,
'courseid' => $manualcourse->id,
'groupid' => 0,
'userid' => $USER->id,
'modulename' => 0,
'instance' => 0,
'eventtype' => 'course',
'timestart' => time(),
'timeduration' => 86400,
'visible' => 1
];
$caleventmanual = calendar_event::create($manualevent, false);
// Create a course event for the course with guest access.
$guestevent = clone $manualevent;
$guestevent->name = 'Guest course event';
$guestevent->courseid = $guestcourse->id;
$caleventguest = calendar_event::create($guestevent, false);
// Viewing as admin.
$this->assertTrue(calendar_view_event_allowed($caleventmanual));
$this->assertTrue(calendar_view_event_allowed($caleventguest));
// Viewing as someone enrolled in a course.
$this->setUser($student);
$this->assertTrue(calendar_view_event_allowed($caleventmanual));
// Viewing as someone not enrolled in any course.
$this->setUser($someuser);
// Viewing as someone not enrolled in a course without guest access on.
$this->assertFalse(calendar_view_event_allowed($caleventmanual));
// Viewing as someone not enrolled in a course with guest access on.
$this->assertTrue(calendar_view_event_allowed($caleventguest));
}
}
+1 -1
View File
@@ -564,7 +564,7 @@ class comment {
c.commentarea = :commentarea AND
c.itemid = :itemid AND
$componentwhere
ORDER BY c.timecreated DESC";
ORDER BY c.timecreated DESC, c.id DESC";
$params['contextid'] = $this->contextid;
$params['commentarea'] = $this->commentarea;
$params['itemid'] = $this->itemid;
+3 -2
View File
@@ -96,17 +96,18 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase {
// We need to add the comments manually, the comment API uses the global OUTPUT and this is going to make the WS to fail.
$newcmt = new stdClass;
$timecreated = time();
$newcmt->contextid = $context->id;
$newcmt->commentarea = 'database_entry';
$newcmt->itemid = $recordid;
$newcmt->content = 'New comment';
$newcmt->format = 0;
$newcmt->userid = $user->id;
$newcmt->timecreated = time();
$newcmt->timecreated = $timecreated;
$cmtid1 = $DB->insert_record('comments', $newcmt);
$newcmt->content = 'New comment 2';
$newcmt->timecreated = time() + 1;
$newcmt->timecreated = $timecreated;
$cmtid2 = $DB->insert_record('comments', $newcmt);
$contextlevel = 'module';
+1 -1
View File
@@ -13,7 +13,7 @@
"require-dev": {
"phpunit/phpunit": "7.5.*",
"phpunit/dbunit": "4.0.*",
"moodlehq/behat-extension": "3.37.2",
"moodlehq/behat-extension": "3.37.3",
"mikey179/vfsstream": "^1.6",
"instaclick/php-webdriver": "dev-local as 1.x-dev"
}
Generated
+133 -134
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "563c5366d69fea69cea6809ac2f410cf",
"content-hash": "ee640e103837d18775219772bd0bfb92",
"packages": [],
"packages-dev": [
{
@@ -621,27 +621,28 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.3",
"version": "6.4.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
"reference": "0895c932405407fd3a7368b6910c09a24d26db11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
"reference": "0895c932405407fd3a7368b6910c09a24d26db11",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.4",
"guzzlehttp/psr7": "^1.6.1",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
"psr/log": "^1.1"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
@@ -653,12 +654,12 @@
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -682,7 +683,7 @@
"rest",
"web service"
],
"time": "2018-04-22T15:46:56+00:00"
"time": "2019-10-23T15:58:00+00:00"
},
{
"name": "guzzlehttp/promises",
@@ -905,8 +906,8 @@
"authors": [
{
"name": "Frank Kleine",
"role": "Developer",
"homepage": "http://frankkleine.de/"
"homepage": "http://frankkleine.de/",
"role": "Developer"
}
],
"description": "Virtual file system to mock the real file system in unit tests.",
@@ -915,16 +916,16 @@
},
{
"name": "moodlehq/behat-extension",
"version": "v3.37.2",
"version": "v3.37.3",
"source": {
"type": "git",
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
"reference": "ee293554b4e75b7444576bfe0c9f6779fb1c04cb"
"reference": "c4b69596142fa297ba049e13f04f3b158dcc94e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/ee293554b4e75b7444576bfe0c9f6779fb1c04cb",
"reference": "ee293554b4e75b7444576bfe0c9f6779fb1c04cb",
"url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/c4b69596142fa297ba049e13f04f3b158dcc94e6",
"reference": "c4b69596142fa297ba049e13f04f3b158dcc94e6",
"shasum": ""
},
"require": {
@@ -949,9 +950,9 @@
"authors": [
{
"name": "David Monllaó",
"role": "Developer",
"email": "david.monllao@gmail.com",
"homepage": "http://moodle.com"
"homepage": "http://moodle.com",
"role": "Developer"
}
],
"description": "Moodle behat extension",
@@ -960,7 +961,7 @@
"Behat",
"moodle"
],
"time": "2019-08-26T22:08:54+00:00"
"time": "2019-10-28T11:41:43+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -1048,18 +1049,18 @@
"authors": [
{
"name": "Arne Blankerts",
"role": "Developer",
"email": "arne@blankerts.de"
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"role": "Developer",
"email": "sebastian@phpeople.de"
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"role": "Developer",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
@@ -1095,18 +1096,18 @@
"authors": [
{
"name": "Arne Blankerts",
"role": "Developer",
"email": "arne@blankerts.de"
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"role": "Developer",
"email": "sebastian@phpeople.de"
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"role": "Developer",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Library for handling version information and constraints",
@@ -1114,35 +1115,33 @@
},
{
"name": "phpdocumentor/reflection-common",
"version": "1.0.1",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
"shasum": ""
},
"require": {
"php": ">=5.5"
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^4.6"
"phpunit/phpunit": "~6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src"
]
"phpDocumentor\\Reflection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1164,30 +1163,30 @@
"reflection",
"static analysis"
],
"time": "2017-09-11T18:02:19+00:00"
"time": "2018-08-07T13:53:10+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "4.3.1",
"version": "4.3.2",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c"
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
"reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
"shasum": ""
},
"require": {
"php": "^7.0",
"phpdocumentor/reflection-common": "^1.0.0",
"phpdocumentor/type-resolver": "^0.4.0",
"phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
"phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
"doctrine/instantiator": "~1.0.5",
"doctrine/instantiator": "^1.0.5",
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^6.4"
},
@@ -1215,41 +1214,40 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2019-04-30T17:48:53+00:00"
"time": "2019-09-12T14:27:41+00:00"
},
{
"name": "phpdocumentor/type-resolver",
"version": "0.4.0",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
"reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
"reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
"shasum": ""
},
"require": {
"php": "^5.5 || ^7.0",
"phpdocumentor/reflection-common": "^1.0"
"php": "^7.1",
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^5.2||^4.8.24"
"ext-tokenizer": "^7.1",
"mockery/mockery": "~1",
"phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src/"
]
"phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1262,26 +1260,27 @@
"email": "me@mikevanriel.com"
}
],
"time": "2017-07-14T14:27:02+00:00"
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"time": "2019-08-22T18:11:29+00:00"
},
{
"name": "phpspec/prophecy",
"version": "1.8.1",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
"reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
"reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203",
"reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
"sebastian/comparator": "^1.1|^2.0|^3.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
},
@@ -1325,7 +1324,7 @@
"spy",
"stub"
],
"time": "2019-06-13T12:50:23+00:00"
"time": "2019-10-03T11:07:50+00:00"
},
{
"name": "phpunit/dbunit",
@@ -1430,8 +1429,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"role": "lead",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
@@ -1481,8 +1480,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"role": "lead",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
@@ -1523,8 +1522,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"role": "lead",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
@@ -1572,8 +1571,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"role": "lead",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Utility class for timing",
@@ -1585,16 +1584,16 @@
},
{
"name": "phpunit/php-token-stream",
"version": "3.1.0",
"version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "e899757bb3df5ff6e95089132f32cd59aac2220a"
"reference": "995192df77f63a59e47f025390d2d1fdf8f425ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a",
"reference": "e899757bb3df5ff6e95089132f32cd59aac2220a",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff",
"reference": "995192df77f63a59e47f025390d2d1fdf8f425ff",
"shasum": ""
},
"require": {
@@ -1630,20 +1629,20 @@
"keywords": [
"tokenizer"
],
"time": "2019-07-25T05:29:42+00:00"
"time": "2019-09-17T06:23:10+00:00"
},
{
"name": "phpunit/phpunit",
"version": "7.5.15",
"version": "7.5.17",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "d79c053d972856b8b941bb233e39dc521a5093f0"
"reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d79c053d972856b8b941bb233e39dc521a5093f0",
"reference": "d79c053d972856b8b941bb233e39dc521a5093f0",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c92a15296e58191a4cd74cff3b34fc8e374174a",
"reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a",
"shasum": ""
},
"require": {
@@ -1703,8 +1702,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"role": "lead",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
@@ -1714,7 +1713,7 @@
"testing",
"xunit"
],
"time": "2019-08-21T07:05:16+00:00"
"time": "2019-10-28T10:37:36+00:00"
},
{
"name": "psr/container",
@@ -1817,16 +1816,16 @@
},
{
"name": "psr/log",
"version": "1.1.0",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
"reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
"reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
"shasum": ""
},
"require": {
@@ -1835,7 +1834,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.1.x-dev"
}
},
"autoload": {
@@ -1860,7 +1859,7 @@
"psr",
"psr-3"
],
"time": "2018-11-20T15:27:04+00:00"
"time": "2019-10-25T08:06:51+00:00"
},
{
"name": "ralouphie/getallheaders",
@@ -2122,16 +2121,16 @@
},
{
"name": "sebastian/exporter",
"version": "3.1.1",
"version": "3.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "06a9a5947f47b3029d76118eb5c22802e5869687"
"reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687",
"reference": "06a9a5947f47b3029d76118eb5c22802e5869687",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
"reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
"shasum": ""
},
"require": {
@@ -2185,7 +2184,7 @@
"export",
"exporter"
],
"time": "2019-08-11T12:43:14+00:00"
"time": "2019-09-14T09:02:43+00:00"
},
{
"name": "sebastian/global-state",
@@ -2460,8 +2459,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"role": "lead",
"email": "sebastian@phpunit.de"
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
@@ -2470,16 +2469,16 @@
},
{
"name": "symfony/browser-kit",
"version": "v4.3.4",
"version": "v4.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "9e5dddb637b13db82e35695a8603fe6e118cc119"
"reference": "78b7611c45039e8ce81698be319851529bf040b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/9e5dddb637b13db82e35695a8603fe6e118cc119",
"reference": "9e5dddb637b13db82e35695a8603fe6e118cc119",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/78b7611c45039e8ce81698be319851529bf040b1",
"reference": "78b7611c45039e8ce81698be319851529bf040b1",
"shasum": ""
},
"require": {
@@ -2525,11 +2524,11 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2019-08-26T08:26:39+00:00"
"time": "2019-09-10T11:25:17+00:00"
},
{
"name": "symfony/class-loader",
"version": "v3.4.31",
"version": "v3.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
@@ -2585,16 +2584,16 @@
},
{
"name": "symfony/config",
"version": "v4.3.4",
"version": "v4.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece"
"reference": "0acb26407a9e1a64a275142f0ae5e36436342720"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/07d49c0f823e0bc367c6d84e35b61419188a5ece",
"reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece",
"url": "https://api.github.com/repos/symfony/config/zipball/0acb26407a9e1a64a275142f0ae5e36436342720",
"reference": "0acb26407a9e1a64a275142f0ae5e36436342720",
"shasum": ""
},
"require": {
@@ -2645,7 +2644,7 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2019-08-26T08:26:39+00:00"
"time": "2019-09-19T15:51:53+00:00"
},
{
"name": "symfony/console",
@@ -2717,16 +2716,16 @@
},
{
"name": "symfony/css-selector",
"version": "v3.4.31",
"version": "v3.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "e18c5c4b35e7f17513448a25d02f7af34a4bdb41"
"reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/e18c5c4b35e7f17513448a25d02f7af34a4bdb41",
"reference": "e18c5c4b35e7f17513448a25d02f7af34a4bdb41",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
"reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
"shasum": ""
},
"require": {
@@ -2766,20 +2765,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2019-08-20T13:31:17+00:00"
"time": "2019-10-01T11:57:37+00:00"
},
{
"name": "symfony/debug",
"version": "v3.4.31",
"version": "v3.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
"reference": "0b600300918780001e2821db77bc28b677794486"
"reference": "b3e7ce815d82196435d16dc458023f8fb6b36ceb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/0b600300918780001e2821db77bc28b677794486",
"reference": "0b600300918780001e2821db77bc28b677794486",
"url": "https://api.github.com/repos/symfony/debug/zipball/b3e7ce815d82196435d16dc458023f8fb6b36ceb",
"reference": "b3e7ce815d82196435d16dc458023f8fb6b36ceb",
"shasum": ""
},
"require": {
@@ -2822,7 +2821,7 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
"time": "2019-08-20T13:31:17+00:00"
"time": "2019-09-19T15:32:51+00:00"
},
{
"name": "symfony/dependency-injection",
@@ -2896,16 +2895,16 @@
},
{
"name": "symfony/dom-crawler",
"version": "v4.3.4",
"version": "v4.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "cc686552948d627528c0e2e759186dff67c2610e"
"reference": "e9f7b4d19d69b133bd638eeddcdc757723b4211f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cc686552948d627528c0e2e759186dff67c2610e",
"reference": "cc686552948d627528c0e2e759186dff67c2610e",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/e9f7b4d19d69b133bd638eeddcdc757723b4211f",
"reference": "e9f7b4d19d69b133bd638eeddcdc757723b4211f",
"shasum": ""
},
"require": {
@@ -2953,11 +2952,11 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2019-08-26T08:26:39+00:00"
"time": "2019-09-28T21:25:05+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v3.4.31",
"version": "v3.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
@@ -3020,7 +3019,7 @@
},
{
"name": "symfony/filesystem",
"version": "v4.3.4",
"version": "v4.3.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
@@ -3387,8 +3386,8 @@
"authors": [
{
"name": "Arne Blankerts",
"role": "Developer",
"email": "arne@blankerts.de"
"email": "arne@blankerts.de",
"role": "Developer"
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+4
View File
@@ -834,6 +834,10 @@ $CFG->admin = 'admin';
// Example:
// $CFG->behat_faildump_path = '/my/path/to/save/failure/dumps';
//
// You can make behat pause upon failure to help you diagnose and debug problems with your tests.
//
// $CFG->behat_pause_on_fail = true;
//
// You can specify db, selenium wd_host etc. for behat parallel run by setting following variable.
// Example:
// $CFG->behat_parallel_run = array (
@@ -74,6 +74,10 @@ abstract class course_enrolments extends \core_analytics\local\target\binary {
return get_string('coursenotyetstarted', 'course');
}
if (!$fortraining && !$course->get_course_data()->visible) {
return get_string('hiddenfromstudents');
}
if (!$this->students = $course->get_students()) {
return get_string('nocoursestudents', 'course');
}
+3
View File
@@ -105,6 +105,9 @@ class course_summary_exporter extends \core\external\exporter {
),
'enddate' => array(
'type' => PARAM_INT,
),
'visible' => array(
'type' => PARAM_BOOL,
)
);
}
+3 -5
View File
@@ -166,11 +166,9 @@ if ($editform->is_cancelled()) {
if (!empty($CFG->creatornewroleid) and !is_viewing($context, NULL, 'moodle/role:assign') and !is_enrolled($context, NULL, 'moodle/role:assign')) {
// Deal with course creators - enrol them internally with default role.
if (user_can_assign($context, $CFG->creatornewroleid)) {
enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
} else {
enrol_try_internal_enrol($course->id, $USER->id);
}
// Note: This does not respect capabilities, the creator will be assigned the default role.
// This is an expected behaviour. See MDL-66683 for further details.
enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
}
// The URL to take them to if they chose save and display.
+8 -1
View File
@@ -84,7 +84,14 @@ if ($deletesection) {
}
}
$editoroptions = array('context'=>$context ,'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true);
$editoroptions = array(
'context' => $context,
'maxfiles' => EDITOR_UNLIMITED_FILES,
'maxbytes' => $CFG->maxbytes,
'trusttext' => false,
'noclean' => true,
'subdirs' => true
);
$courseformat = course_get_format($course);
$defaultsectionname = $courseformat->get_default_section_name($section);
+1 -1
View File
@@ -3716,7 +3716,7 @@ class core_course_external extends external_api {
}
/**
* Return structure for edit_module()
* Return structure for get_module()
*
* @since Moodle 3.3
* @return external_description
+1 -1
View File
@@ -50,7 +50,7 @@ if (trim($forum->intro) != '') {
$streditsummary = get_string('editsummary');
$introcontent .= html_writer::start_div('editinglink');
$introcontent .= html_writer::link(
new moodle_url('/modedit.php', [
new moodle_url('/course/modedit.php', [
'update' => $coursemodule->id,
'sesskey' => sesskey(),
]),
+1 -1
View File
@@ -4590,7 +4590,7 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
}
$basefields = array('id', 'idnumber', 'summary', 'summaryformat', 'startdate', 'enddate', 'category',
'shortname', 'fullname', 'timeaccess', 'component');
'shortname', 'fullname', 'timeaccess', 'component', 'visible');
$sort = trim($sort);
if (empty($sort)) {
+7 -1
View File
@@ -28,7 +28,8 @@
"courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
"fullname": "course 3",
"hasprogress": true,
"progress": 10
"progress": 10,
"visible": true
}
]
}
@@ -63,6 +64,11 @@
</span>
{{$coursename}}{{/coursename}}
</a>
{{^visible}}
<div class="d-flex flex-wrap">
<span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
</div>
{{/visible}}
</div>
{{$menu}}{{/menu}}
</div>
+2 -5
View File
@@ -1144,11 +1144,8 @@ class behat_course extends behat_base {
protected function get_activity_element($element, $selectortype, $activityname) {
$activitynode = $this->get_activity_node($activityname);
// Transforming to Behat selector/locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' . $selectortype . '" in "' . $activityname . '" ');
return $this->find($selector, $locator, $exception, $activitynode);
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
return $this->find($selectortype, $element, $exception, $activitynode);
}
/**
@@ -85,7 +85,6 @@ Feature: Managers can create courses
| moodle/course:create | Allow |
| moodle/course:manageactivities | Allow |
| moodle/course:viewparticipants | Allow |
| moodle/role:assign | Allow |
And I log out
And I log in as "kevin"
And I am on site homepage
@@ -96,20 +95,4 @@ Feature: Managers can create courses
And I press "Save and display"
And I follow "Participants"
Then I should see "Kevin the"
And I should not see "Teacher"
And I log out
Given I log in as "admin"
And I define the allowed role assignments for the "Creator" role as:
| Teacher | Assignable |
And I log out
And I log in as "kevin"
And I am on site homepage
And I turn editing mode on
When I press "Add a new course"
And I set the following fields to these values:
| Course full name | My second course |
| Course short name | mysecondcourse |
And I press "Save and display"
And I follow "Participants"
Then I should see "Kevin the"
And I should see "Teacher"
+8
View File
@@ -37,6 +37,14 @@ use \core_customfield\category_controller;
*/
class core_customfield_api_testcase extends advanced_testcase {
/**
* This method is called after the last test of this test class is run.
*/
public static function tearDownAfterClass() {
$handler = core_course\customfield\course_handler::create();
$handler->delete_all();
}
/**
* Tests set up.
*/
@@ -36,6 +36,14 @@ use \core_customfield\field_controller;
*/
class core_customfield_category_controller_testcase extends advanced_testcase {
/**
* This method is called after the last test of this test class is run.
*/
public static function tearDownAfterClass() {
$handler = core_course\customfield\course_handler::create();
$handler->delete_all();
}
/**
* Tests set up.
*/
@@ -34,6 +34,14 @@ use core_customfield\data_controller;
*/
class core_customfield_data_controller_testcase extends advanced_testcase {
/**
* This method is called after the last test of this test class is run.
*/
public static function tearDownAfterClass() {
$handler = core_course\customfield\course_handler::create();
$handler->delete_all();
}
/**
* Tests set up.
*/
@@ -38,6 +38,14 @@ use \core_customfield\field_controller;
*/
class core_customfield_field_controller_testcase extends advanced_testcase {
/**
* This method is called after the last test of this test class is run.
*/
public static function tearDownAfterClass() {
$handler = core_course\customfield\course_handler::create();
$handler->delete_all();
}
/**
* Tests set up.
*/

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