Compare commits

..

381 Commits

Author SHA1 Message Date
Jun Pataleta a2fe75a270 Moodle release 3.9.9 2021-07-29 10:01:07 +08:00
Ilya Tregubov 6b632fef09 weekly release 3.9.8+ 2021-07-28 16:48:25 +02:00
Ilya Tregubov bc3e140d8b Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-07-28 16:48:25 +02:00
Jun Pataleta b53f12d8c9 Merge branch 'MDL-72203-39' of git://github.com/mickhawkins/moodle into MOODLE_39_STABLE 2021-07-28 11:27:48 +08:00
AMOS bot f9c700eb9f Automatically generated installer lang files 2021-07-28 00:07:44 +00:00
Michael Hawkins 0d3495e6b2 MDL-72203 curl: Improve redirect unit testing and update upgrade.txt
lib/upgrade.txt was updated to reflect the fact that all cURL redirects
will be emulated.
2021-07-27 23:27:02 +08:00
David Mudrák 72eac30148 MDL-72203 curl: Remove duplicate unit tests for emulated redirects
Before, we had each redirect test duplicated: one for the native
redirects via native cURL, second for our emulated implementation. Now
all redirects are always emulated so there is no need to have them
tested twice.
2021-07-27 23:22:21 +08:00
David Mudrák e19f451723 MDL-72203 curl: Check each URL in redirect chain to see if it is blocked
The security problem here was that only the first and the last URL in
the redirect chain was checked by the security helper. This patch forces
the curl wrapper to always emulate cURL redirects and check every
redirect URL in the chain before actually visiting it.
2021-07-27 23:22:21 +08:00
David Mudrák db0073291b MDL-72203 curl: Warn if someone actually started to use $maxredirects
The new parameter of curl_security_helper::url_is_blocked() introduced
in MDL-71916 became part of the API. Even if we reverted it quickly,
someone can use a released Moodle version that has that parameter in
place. For that reason and also to avoid potential troubles in the
future (e.g. when yet another argument would be added to this method),
we need to make it clear that the second parameter of this method should
never be used again.

Poor $maxredirects, you did not live long with us. Oh well.
2021-07-27 23:22:21 +08:00
David Mudrák 984e3fc510 MDL-72203 curl: Revert original fix of redirects to blocked URLs
This reverts the original fix introduced in MDL-71916. It introduced an
extra native cURL call inside curl_security_helper to check if the given
URL triggers a redirect to a blocked URL or not.

Shortly after the release, a couple of regressions were reported as a
result of the integrated solution. It was agreed to revert the fix and
progress with implementing an alternative approach.
2021-07-27 23:22:21 +08:00
Ilya Tregubov 698142b1f5 Merge branch 'MDL-72179-39' of git://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2021-07-27 12:39:02 +02:00
Andrew Nicols 8669b0ebb7 MDL-72179 behat: Make use of page resolvers 2021-07-24 18:56:38 +08:00
Andrew Nicols 2204ea8afe MDL-72179 behat: Add page resolvers for activity roles/perms 2021-07-23 20:44:26 +08:00
Andrew Nicols 142e12bee7 MDL-72179 behat: Add page resolver for activity names 2021-07-23 20:32:44 +08:00
Andrew Nicols 2bdebbf6e7 MDL-72179 behat: Add activity editing page resolution 2021-07-23 20:32:15 +08:00
Andrew Nicols f47ec5d0a9 MDL-72179 behat: Fix page resolvers to remove ambiguity
The simple pattern matches were conflicting in some situations. To make
this backwards compatable we need to convert it to a Regex pattern match
instead, and provide the quoted and unquoted variants.
2021-07-23 20:31:45 +08:00
Andrew Nicols 52da2d6eaa MDL-72179 behat: Correct course category page resolving
There were two issues here:
* I am on the [categoryname] category page page (duplicated page)
* the wrong URL was being used

Since an incorrect URL was used I felt it safe to rename the step from
'category page page' to 'category page'.
2021-07-23 20:28:15 +08:00
Andrew Nicols 78b0f6e204 MDL-72179 behat: Lowercase all page instance resolvers 2021-07-23 20:28:14 +08:00
Andrew Nicols 7030fa5972 MDL-72179 behat: Improve core page resolvers
This commit makes the following improvements to core page resolverss:
* allows for mixed case naming (course, Course, etc.)
* allows fields other than the idnumber to be specified:
** course: idnumber, shortname, fullname
** course category: idnumber, name

Whilst some of these fields are not unique, they will typically be
unique in most test scenarios. Where they are not then the idnumber
should be used in preference.
2021-07-23 20:28:10 +08:00
Ilya Tregubov 2bd53efc1b weekly release 3.9.8+ 2021-07-23 11:31:05 +02:00
Ilya Tregubov 9ccb3a2af9 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-07-23 11:31:04 +02:00
AMOS bot 3354f46fd6 Automatically generated installer lang files 2021-07-22 00:07:52 +00:00
Ilya Tregubov 9fb1e3d300 Merge branch 'MDL-72125-39' of git://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2021-07-21 08:02:52 +02:00
Andrew Nicols e4b11bf852 MDL-72125 mod_assign: Use assign submission data generators 2021-07-21 07:37:59 +08:00
Andrew Nicols 28cf3f9119 MDL-72125 mod_assign: Add data generator for assign submissions 2021-07-20 21:38:47 +08:00
Andrew Nicols 1a885a1756 MDL-72125 testing: Make global $CFG available when including generators
This will remove the requirement to add the global $CFG call to the top
of generator scripts.
2021-07-20 21:38:47 +08:00
Andrew Nicols 753035e090 MDL-72125 behat: Add get_activity_id() behat generators helper 2021-07-20 21:38:46 +08:00
Andrew Nicols cb98541563 MDL-72125 testing: Set a default idnumber when creating activities
The activity generator currently requires an idnumber when creating
activities, but this is not a requirement when creating the same
activity through the UI. The requirement comes because we want to
provide a way to refer to activities in subsequent steps.

This commit modifies the behaviour such that the generator uses the name
of the activity as the default idnumber.

This has two  main benefits:
1. it simplfies generation of activities; and
2. it makes the language used when writing behat tests much more natural.

With this change, steps will refer to the activity by its idnumber/title
in all cases, rather than sometimes by an idnumber which bears no
relevance to the title.
2021-07-20 21:38:46 +08:00
Andrew Nicols 2729301721 MDL-72125 testing: Add helper to run generators as a user 2021-07-20 21:38:46 +08:00
abgreeve 54a2d7df22 weekly release 3.9.8+ 2021-07-16 10:16:44 +08:00
abgreeve 59f97a627f Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-07-16 10:16:43 +08:00
AMOS bot 362c67f84d Automatically generated installer lang files 2021-07-16 00:07:46 +00:00
Eloy Lafuente (stronk7) cbe5ec5a51 Merge branch 'MDL-72139-39' of git://github.com/cescobedo/moodle into MOODLE_39_STABLE 2021-07-14 14:50:10 +02:00
cescobedo 201eff4d98 MDL-72139 core_message: Fix encoding UTF-8 in prevent unclosed tags 2021-07-14 07:41:33 +02:00
Sara Arjona 22e52ebb55 Merge branch 'MDL-71782_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-07-13 16:44:53 +02:00
Jun Pataleta 493d3c218e Merge branch 'MDL-72036-39' of git://github.com/sarjona/moodle into MOODLE_39_STABLE 2021-07-13 16:04:57 +08:00
Sara Arjona 97ff875c83 MDL-72036 atto_h5p: Random error with external URL
After a major upgrade was done in h5p.com, some random errors appeared
in the "H5P options are ignored for H5P URLs" scenario.
They have been fixed replacing the URL for different (which should
load quicker). As we're checking external content, no other improvement
can be done on the Moodle site.
2021-07-13 08:37:57 +02:00
AMOS bot 8a5601722d Automatically generated installer lang files 2021-07-11 00:07:43 +00:00
Eloy Lafuente (stronk7) 68aa19ba73 Moodle release 3.9.8 2021-07-10 18:55:57 +02:00
Eloy Lafuente (stronk7) c8d12c3217 MDL-71782 behat: Ensure page after saving changes is there
A similar approach was followed @ MDL-67935, although the
case doesn't seem to be the same.

In any case, it's curious that all local execution are
100% passing and only CIs fail so often. Just guessing if,
maybe, there is some Chrome version factor around (we
are running older, sticky, versions @ CIs.
2021-07-10 15:51:07 +02:00
AMOS bot f9d08701fb Automatically generated installer lang files 2021-07-09 00:07:52 +00:00
Eloy Lafuente (stronk7) 324e1fbb78 weekly release 3.9.7+ 2021-07-08 23:44:00 +02:00
Eloy Lafuente (stronk7) b58a4e8da4 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-07-08 23:43:58 +02:00
Jun Pataleta e7c0ddb942 Merge branch 'MDL-71919-39-fix' of https://github.com/cescobedo/moodle into MOODLE_39_STABLE 2021-07-08 23:34:54 +02:00
Jun Pataleta e9d63ac29c Merge branch 'MDL-70594_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-07-08 23:34:54 +02:00
cescobedo fce440eeed MDL-71919 core_message: Fix tests in message_send 2021-07-08 23:34:54 +02:00
Huong Nguyen 5fae2fc7a1 MDL-71922 file: Enhance endless recursion requests protection 2021-07-08 23:34:54 +02:00
cescobedo 913eb31830 MDL-71919 core_message: Fix unclosed html tags in email notification 2021-07-08 23:34:54 +02:00
Paul Holden 68fbcaf303 MDL-71957 auth_shibboleth: safer session retrieval during logout. 2021-07-08 23:34:54 +02:00
Dongsheng Cai 20403e0e43 MDL-71978 core_calendar: only allow owner to edit user subscriptions 2021-07-08 23:34:54 +02:00
Adrian Greeve 2740e06920 Merge branch 'MDL-71887-39' of https://github.com/snake/moodle into MOODLE_39_STABLE 2021-07-08 23:34:53 +02:00
Mihail Geshoski a84b51046a MDL-58393 core: Remove firstname argument from emailconfirmation string 2021-07-08 23:34:53 +02:00
Sujith Haridasan a19092e71c MDL-71241 course: Validate and sanitize sort arguments
Signed-off-by: Sujith Haridasan <sujith@moodle.com>
2021-07-08 23:34:39 +02:00
Mihail Geshoski b41a748a98 MDL-71242 core_course: Fix failing unit test 2021-07-08 23:34:39 +02:00
Mihail Geshoski cd96cb300e MDL-71242 core_course: Test the validation of the sort value
Adds new unit test, test_course_get_recent_courses_sort_validation(),
which is reponsible for testing the validation of the sort value in
course_get_recent_courses().
2021-07-08 23:34:39 +02:00
Mihail Geshoski e3c031023f MDL-71242 core_course: Update test_course_get_recent_courses() unit test
Adds additional assertions in test_course_get_recent_courses() test to
ensure that the sort funcionality works as expected.
2021-07-08 23:34:39 +02:00
Mihail Geshoski c5b4da7257 MDL-71242 core_course: Validate the value of the sort argument
This change validates the value of the sort argument in
course_get_recent_courses().
2021-07-08 23:34:26 +02:00
Claude Vervoort 7edc33a8ce MDL-71887 mod_lti: repost when no cookie due to crosssite request 2021-07-07 14:19:00 +08:00
Michael Hawkins 61e9318339 MDL-71916 lib: Check cURL redirects for blocked URLs before following 2021-07-06 23:45:24 +02:00
cescobedo f254b2b88f MDL-71917 core_message: Use $USER in WS delete_message_for_all_users 2021-07-06 13:32:08 +08:00
Paul Holden 264fe69743 MDL-71981 user: escape identity fields if writer supports HTML. 2021-07-06 09:29:44 +08:00
Paul Holden 80e2f8473e MDL-71981 dataformat: indicate HTML support in writer callback. 2021-07-06 09:29:44 +08:00
Eloy Lafuente (stronk7) b9b5f4b489 MDL-70594 environment: Apply agreed changes
- PostgreSQL 10 required.
- Exif PHP extension recommended.
2021-07-01 22:42:30 +02:00
Eloy Lafuente (stronk7) dc871f251e MDL-70594 environment: 4.0 base information
This is a direct copy of the 3.11 branch to have
the changes under control in next commits.
2021-07-01 22:42:30 +02:00
AMOS bot f972485484 Automatically generated installer lang files 2021-06-23 00:07:48 +00:00
AMOS bot 1f9488ccb5 Automatically generated installer lang files 2021-06-19 00:07:49 +00:00
Víctor Déniz 1d8512bc0e weekly release 3.9.7+ 2021-06-17 14:55:05 +01:00
Víctor Déniz b751583087 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-06-17 14:55:04 +01:00
Andrew Nicols 94b897a176 Merge branch 'MDL-71939_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-06-16 13:14:34 +08:00
Jun Pataleta 4c6b771c2f MDL-71939 mod_h5pactivity: Increase window size during test
The addition of the activity information moves the h5p content down
out of the window view on Firefox which causes the Check button to be
unclickable. This patch increases the window size to ensure that the
check button is visible and clickable.

Credits to Eloy <stronk7@moodle.org> for the thorough investigation
of the root cause of this issue :)

This is a backport of MDL-71876 that is a backport of MDL-71337
2021-06-15 23:34:32 +02:00
Andrew Nicols b8122e1de3 Merge branch 'MDL-71804_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-06-15 11:28:45 +08:00
AMOS bot a9eae7edf4 Automatically generated installer lang files 2021-06-10 00:07:42 +00:00
Eloy Lafuente (stronk7) a7a9150b62 MDL-71804 travis: Bump to bionic 2021-05-29 10:26:34 +02:00
AMOS bot c283ed04e0 Automatically generated installer lang files 2021-05-29 00:07:42 +00:00
AMOS bot f65e706662 Automatically generated installer lang files 2021-05-27 00:07:43 +00:00
AMOS bot 49e1731fbe Automatically generated installer lang files 2021-05-24 00:07:43 +00:00
AMOS bot cd320b32b5 Automatically generated installer lang files 2021-05-23 00:07:40 +00:00
Sara Arjona 8dea153c6b weekly release 3.9.7+ 2021-05-20 11:09:11 +02:00
Sara Arjona ae9fea0b47 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-05-20 11:09:10 +02:00
Jun Pataleta dc9258574d Merge branch 'MDL-71157-39' of git://github.com/sarjona/moodle into MOODLE_39_STABLE 2021-05-20 12:29:36 +08:00
Eloy Lafuente (stronk7) 5b94aaed06 Merge branch 'MDL-67935-39' of git://github.com/sarjona/moodle into MOODLE_39_STABLE 2021-05-19 22:50:53 +02:00
Sara Arjona a099c8d921 MDL-71157 block_myoverview: remove wait from behat
The step "And I wait until ".block_myoverview
[data-control='next']" "css_element" exists" is not correct
because this [data-control='next'] element exists before and
after the step so, in some cases, it might cause the following
step will start earlier than expected.
As pending JS has been added, this wait steps are not required
any more.
2021-05-19 12:38:28 +02:00
Andrew Nicols 36a3c41b8b MDL-71157 core: Pending JS in paged content bars 2021-05-19 12:38:27 +02:00
AMOS bot d7208f8dd0 Automatically generated installer lang files 2021-05-18 00:07:55 +00:00
Sara Arjona df9c7f755d MDL-67935 tool_lp: fix random errors with login/logout 2021-05-14 10:53:25 +02:00
AMOS bot a76583296b Automatically generated installer lang files 2021-05-14 00:07:50 +00:00
Sara Arjona b56affcfe6 weekly release 3.9.7+ 2021-05-11 12:42:36 +02:00
Sara Arjona 4799f8b7c4 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-05-11 12:42:35 +02:00
AMOS bot 62758b8224 Automatically generated installer lang files 2021-05-11 00:07:51 +00:00
Víctor Déniz cf6aeae427 Merge branch 'MDL-71060-39' of https://github.com/NashTechOpenUniversity/moodle into MOODLE_39_STABLE 2021-05-10 23:43:30 +01:00
Thong Bui 8ea5b30a5d MDL-71060 Questions: Duplicates 'Current category' text. 2021-05-10 00:25:38 +07:00
Eloy Lafuente (stronk7) 270f52bf55 Moodle release 3.9.7 2021-05-08 16:40:39 +02:00
Eloy Lafuente (stronk7) 1affa30299 weekly release 3.9.6+ 2021-05-07 16:54:47 +02:00
Eloy Lafuente (stronk7) f61b2edde0 NOBUG: Fixed SVG browser compatibility 2021-05-07 16:54:46 +02:00
Eloy Lafuente (stronk7) d1b2674eea Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-05-07 16:54:46 +02:00
abgreeve 849f392f3b Merge branch 'MDL-68747-39' of https://github.com/dcai/moodle into MOODLE_39_STABLE 2021-05-07 11:46:50 +08:00
Mathew May 2c73122c60 MDL-70622 mod_lti: Tighten redirect param typing 2021-05-07 11:46:50 +08:00
Adam Olley 3b6104d6dc MDL-70622 mod_lti: Prevent xss on lti 1.3 authentication script
Without this, people can craft URLs that other users might use not realising
what they do - and as a XSS vulnerability, it could do any number of things the
clicking-user has access to do on the site.

Change-Id: I82adc71e8706d8929011b4b24523d5b62b8ccea1
2021-05-07 11:46:41 +08:00
Jun Pataleta be2eb84be9 Merge branch 'MDL-71395-39-2' of git://github.com/marinaglancy/moodle into MOODLE_39_STABLE 2021-05-07 10:30:52 +08:00
abgreeve 711b1a5ae9 Merge branch 'MDL-70181_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-05-07 10:22:50 +08:00
Víctor Déniz df136f7535 Merge branch 'MDL-71303-39' of https://github.com/StudiUM/moodle into MOODLE_39_STABLE 2021-05-06 23:34:49 +01:00
Eloy Lafuente (stronk7) 6c996b1570 Merge branch 'MDL-70049-39_count_submissions' of https://github.com/agrowe/moodle into MOODLE_39_STABLE 2021-05-06 23:27:31 +02:00
Eloy Lafuente (stronk7) f01179355c Merge branch 'MDL-70772-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-05-06 23:20:20 +02:00
Eloy Lafuente (stronk7) 414102ed74 Merge branch 'MDL-71313_39' of https://github.com/timhunt/moodle into MOODLE_39_STABLE 2021-05-06 23:15:18 +02:00
Eloy Lafuente (stronk7) 24cd545eb8 Merge branch 'MDL-71343-m39' of https://github.com/rjnl/moodle into MOODLE_39_STABLE 2021-05-06 23:07:54 +02:00
Sara Arjona bcf349ed45 MDL-70110 mod_assign: make phpunit test DB order agnostic 2021-05-06 19:38:57 +02:00
Sara Arjona ae221af121 Merge branch 'MDL-71491-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-05-06 18:42:41 +02:00
Marina Glancy 4f87b28436 MDL-71395 core: fix bug in google lib wrapper
because of changes in PHP8 the '' == 0 is no longer true and the error in the google api wrapper was discovered
2021-05-06 18:38:42 +02:00
Sara Arjona b3a66b1484 Merge branch 'MDL-71420_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-05-06 17:58:40 +02:00
Víctor Déniz 52b5aa5a18 Merge branch 'MDL-71402-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-05-06 16:47:12 +01:00
Marina Glancy de6a44d7d0 MDL-71395 block_tag_youtube: sometimes exception may have empty message 2021-05-06 17:41:47 +02:00
Paul Holden 21941c6c2a MDL-71491 repository_flickr: return more file data.
See also 643b0cca.
2021-05-06 16:37:19 +01:00
Sara Arjona 54da1506a1 Merge branch 'MDL-71268-39' of git://github.com/lucaboesch/moodle into MOODLE_39_STABLE 2021-05-06 17:11:45 +02:00
Paul Holden 7f3a1dff28 MDL-71402 availability: fix group restriction button in mod form.
* The button to "Add group/grouping access restriction" under
   common module settings should only be present if corresponding
   availability plugins are enabled; and
 * Prevent the same button behaving as a submit button, which
   intercepted the user hitting return in the form and added a
   restriction without deliberate action taken by the user.
2021-05-06 14:20:29 +01:00
Sara Arjona 818182879a Merge branch 'MDL-70110-39' of https://github.com/septatrix/moodle into MOODLE_39_STABLE 2021-05-06 13:16:20 +02:00
Víctor Déniz e995073997 Merge branch 'MDL-71434-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-05-06 11:15:29 +01:00
Sara Arjona bc47266a65 Merge branch 'MDL-71390-39' of git://github.com/marinaglancy/moodle into MOODLE_39_STABLE 2021-05-06 08:05:26 +02:00
Adrian Greeve 799e988f80 Merge branch 'MDL-70786-39' of git://github.com/lucaboesch/moodle into MOODLE_39_STABLE 2021-05-06 10:04:26 +08:00
AMOS bot 9bae0ce6a7 Automatically generated installer lang files 2021-05-06 00:07:53 +00:00
Eloy Lafuente (stronk7) d6a633d7a8 MDL-71420 environment: Lower sodium requirement to recommended 2021-05-05 23:06:22 +02:00
Sara Arjona 16dff14e56 Merge branch 'MDL-71517-39' of https://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-05-05 12:18:03 +02:00
Sara Arjona c0d26fff0d Merge branch 'MDL-71187-39' of git://github.com/lucaboesch/moodle into MOODLE_39_STABLE 2021-05-05 12:14:23 +02:00
Víctor Déniz 3698f34fc3 Merge branch 'MDL-71460-39' of git://github.com/peterRd/moodle into MOODLE_39_STABLE 2021-05-05 10:45:53 +01:00
Paul Holden e52ec9edd4 MDL-71517 mod_data: remove invalid form custom data access.
On PHP7.4, trying to access array offsets of NULL triggers a notice.
2021-05-05 10:18:18 +01:00
Luca Bösch 63b4929729 MDL-71187 quizaccess_seb: Change "Launch Safe Exam Browser" to a href.
In order to allow for correct seb:// or sebs:// calls without browser
warnings of insecure links, it is not possible to send a get request
with an attached cmid parameter to the unknown seb:// or sebs://
URL via a form button.
We've got to use a <a href> link outside a form to circumvent
browsers warning of an insecure link and call Safe Exam Browser
correctly.
2021-05-05 10:54:24 +02:00
Leon Stringer 3afbffe6a0 MDL-70181 database: Quote database object names
Table names and database name now enclosed in backticks.
admin/cli/mysql_collation.php failed if $CFG->prefix was blank with
MySQL 8.0 because table 'groups' conflicted with a new reserved word.

Note that this quotes both mysql_collation.php and mysql_compressed_rows.php
but only the former was mal-functioning.

The case of mysql_compressed_rows.php is a little bit special because
not all tables are processed, only those having big rows. And the groups
table is not one of them. In any case, better add the quotes there for
any future case.

Also, when testing this patch https://tracker.moodle.org/browse/MDL-71512
was discovered and will need to be fixed to make core 100% compliant
with MySQL 8.0 and MariaDB 10.6 and up.
2021-05-05 10:16:59 +02:00
Peter Dias 1dd574efd8 MDL-71460 hub: Use checkboxes instead of dropdowns
Change the yes/no dropdowns to a checkbox. Defaults to unchecked.
2021-05-05 10:28:02 +08:00
Víctor Déniz a426320c54 Merge branch 'MDL-70864-39-zip_packer_windows' of git://github.com/mudrd8mz/moodle into MOODLE_39_STABLE 2021-05-05 00:04:07 +01:00
Eloy Lafuente (stronk7) 3dea860e64 Merge branch 'MDL-71425-39-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_39_STABLE 2021-05-04 23:40:20 +02:00
Paul Holden 69f3e0753e MDL-71359 mod_forum: empty forums shouldn't export anything. 2021-05-04 17:23:49 +02:00
Sara Arjona 690bad040a MDL-71408 h5plib_v124: Add required changes after lib upgrade
- The mbstring extension is required since Moodle 3.9 onwards so
the upgrading steps have been removed because they are not
required any more.
- The pull-request for fixing some minor PHP7.4 problems has been
integrated so this patch is not required.
2021-05-04 17:23:49 +02:00
Sara Arjona 22c30c160e MDL-71408 h5plib_v124: Upgrade H5P core library to 1.24.02 2021-05-04 17:23:49 +02:00
Huong Nguyen b28b6066b4 MDL-70720 Quiz: Improve capability checks when fetching users' best grades 2021-05-04 17:23:49 +02:00
Shamim Rezaie c4e993d546 MDL-69028 repository: Put a rate limit on draft file uploads 2021-05-04 17:23:49 +02:00
Paul Holden 5b7d2a1992 MDL-71130 quiz_grading: escape user idnumber on display. 2021-05-04 17:23:49 +02:00
David Mudrák 23846c5450 MDL-70804 mnet: Use proper DML method to get records from the table
This improves the code and avoids the risk of SQL injection through the
malicious XML-RPC request from the MNet peer.
2021-05-04 17:23:49 +02:00
Sara Arjona b6d65c77cf Merge branch 'MDL-71274-39' of git://github.com/aanabit/moodle into MOODLE_39_STABLE 2021-05-04 17:10:55 +02:00
David Mudrák f77ec5e7fd MDL-71425 lang: Fix reworded strings in tests 2021-05-04 16:59:21 +02:00
Helen Foster 9031cf789d MDL-71425 lang: Import fixed English strings (en_fix) 2021-05-04 16:59:04 +02:00
Luca Bösch 010aa5ee16 MDL-70786 report_loglive: respect fullname setting. 2021-05-04 14:30:33 +02:00
Luca Bösch 2014c08636 MDL-70786 report_log: respect fullname setting. 2021-05-04 14:30:33 +02:00
Luca Bösch f5531458aa MDL-70786 report_completion: respect fullname setting. 2021-05-04 14:30:33 +02:00
Luca Bösch a6e2d950e7 MDL-70786 report_competency: respect fullname setting. 2021-05-04 14:30:33 +02:00
Luca Bösch dddc4cc410 MDL-70786 report_progress: respect fullname setting. 2021-05-04 14:30:33 +02:00
Luca Bösch 2e16fca0f1 MDL-71268 graphics: Use new Moodle logo for RSS feeds by Moodle 2021-05-04 14:14:10 +02:00
Sara Arjona 30d1832f45 weekly release 3.9.6+ 2021-05-04 13:27:53 +02:00
Sara Arjona 24c44b38be Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-05-04 13:27:52 +02:00
Adrian Greeve d848c604dd Merge branch 'MDL-66673-MOODLE_39_STABLE' of https://github.com/ScottVerbeek/moodle into MOODLE_39_STABLE 2021-05-04 15:47:08 +08:00
AMOS bot 9a4034f3bf Automatically generated installer lang files 2021-05-04 00:07:53 +00:00
Paul Holden 4c1d448b5b MDL-71463 dml: escape square brackets for SQL Server LIKE operator. 2021-05-03 19:01:24 +02:00
David Mudrák f92e83fe36 MDL-70864 files: Fix zip_packer extracting files with trailing dots
File and folder names cannot end with dots on Windows. So replace the
trailing dots with underscore consistently with how some other zip tools
(such as 7-zip) handle this case.
2021-05-03 18:15:44 +02:00
Marina Glancy d500b5c62d MDL-71390 core: raise max_input_vars requirement for Moodle 3.11 2021-05-03 13:29:52 +02:00
Adrian Greeve 65fad5f93f Merge branch 'MDL-71440-39' of git://github.com/mihailges/moodle into MOODLE_39_STABLE 2021-05-03 16:04:05 +08:00
Mihail Geshoski 7ea8dc7112 MDL-71440 mod_assign: New capability for viewing the submission summary
Creates a new capability mod/assign:viewownsubmissionsummary which will
be required in order to display the submission summary to the user in
the assignment page.
2021-05-03 15:33:55 +08:00
Jake Dallimore 276476233d Merge branch 'MDL-52724-39' of git://github.com/merrill-oakland/moodle into MOODLE_39_STABLE 2021-05-03 13:25:51 +08:00
Víctor Déniz a565c5d93d Merge branch 'MDL-69956-39' of https://github.com/dcai/moodle into MOODLE_39_STABLE 2021-05-01 13:43:55 +01:00
Víctor Déniz a72e1f1e0d weekly release 3.9.6+ 2021-04-30 12:54:26 +01:00
Rajneel Totaram 2d86d87012 MDL-71343 core_h5p: Convert all line-endings to UNIX format 2021-04-30 23:32:05 +12:00
Andrew Nicols 27ea958dfe MDL-71343 core_h5p: Add unit test for helper::parse_js_array
Signed-off-by: Rajneel Totaram <rjnlfj@yahoo.com>
2021-04-30 23:31:37 +12:00
Dongsheng Cai 2410df1fd2 MDL-69956 forum: throw exception when advanced grading is not ready 2021-04-30 14:59:42 +10:00
Adrian Greeve ccb1a3b39f Merge branch 'MDL-71481-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-30 11:20:17 +08:00
Jun Pataleta ce2058c4c2 Merge branch 'MDL-70947-39' of git://github.com/HuongNV13/moodle into MOODLE_39_STABLE 2021-04-30 10:34:30 +08:00
Sara Arjona b7fb146cca Merge branch 'MDL-60331-39_take3' of git://github.com/lucaboesch/moodle into MOODLE_39_STABLE 2021-04-29 17:50:06 +02:00
Amaia Anabitarte 2695a394fe MDL-71274 analytics: Don't generate insight for hidden courses
Hidden courses can be used for training
but we do not want to generate insights for them
because students do not have access to hidden courses.
This was fixed in MDL-66806 for "Students at risk" model.
Fixed for "Students who have not accessed the course recently" in this issue.
2021-04-29 11:14:12 +02:00
Paul Holden 05e9ae07cd MDL-71481 repository_flickr_public: return more file data.
A HTTP HEAD request is required to get the image filesize.
The remaining data can be obtained from existing calls to
the Flickr API.
2021-04-29 09:49:24 +01:00
Sara Arjona c14603ecd1 Merge branch 'MDL-71016-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-29 09:21:58 +02:00
Jun Pataleta 5d2d5bc542 Merge branch 'MDL-70863-39' of git://github.com/jleyva/moodle into MOODLE_39_STABLE 2021-04-29 15:05:22 +08:00
Andrew Nicols 3d92acac8a Merge branch 'MDL-56609_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-04-29 09:42:14 +08:00
Víctor Déniz ac115326f9 Merge branch 'MDL-71200-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-28 20:57:59 +01:00
Paul Holden cb94a54412 MDL-71016 gradeexport_xml: ensure user/grade idnumbers are encoded. 2021-04-28 19:04:03 +01:00
Luca Bösch 0c2e30809a MDL-60331 questions: prevent double escaping question categories. 2021-04-28 17:59:59 +02:00
Sara Arjona a6c9132e73 Merge branch 'MDL-70979-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-28 13:06:30 +02:00
Sara Arjona 323276969f Merge branch 'MDL-71127-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-28 12:58:35 +02:00
Sara Arjona 18a99edcb9 Merge branch 'MDL-70763-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-28 12:31:13 +02:00
Septatrix 9d6fff9671 MDL-70110 assign: add group submission attemptnumber test 2021-04-28 11:49:53 +02:00
Sara Arjona c4dad5b5e6 Merge branch 'MDL-71244-39' of git://github.com/lucaboesch/moodle into MOODLE_39_STABLE 2021-04-28 09:17:46 +02:00
Andrew Nicols 9c4204c96b Merge branch 'MDL-71168-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-28 10:50:33 +08:00
Andrew Nicols 15da5ba48e MDL-70909 mod_h5pactivity: Whitespace fix 2021-04-28 10:34:00 +08:00
Andrew Nicols c7e65064b1 Merge branch 'MDL-70909-39' of git://github.com/ferranrecio/moodle into MOODLE_39_STABLE 2021-04-28 10:33:54 +08:00
Andrew Nicols 32702452e7 Merge branch 'MDL-71386-39' of git://github.com/NoelDeMartin/moodle into MOODLE_39_STABLE 2021-04-28 10:16:02 +08:00
Eloy Lafuente (stronk7) 2e1151bbb3 MDL-56609 phpunit: Fix bogus expectation on DST changes day
Current test works ok every day of the year but the day of
DST begin changes (last Sunday of March currently).

That day, 01:00 Europe/London simply doesn't exist, because
the time is moved forward by 60 minutes.

Hence the expectation for the next cron execution is not 1 "normal"
day (24h) but 1 less hour.

Note this only happens for the current test on DST begins, the
opposite change (DST ends), happening the last Sunday of October
is not affected, because, at all effects, 01:00 is a perfectly normal
and existing hour.
2021-04-27 18:31:05 +02:00
Luca Bösch 2e7cc5e17c MDL-71244 enrol_manual: Add bootstrap classes to buttons 2021-04-27 17:24:51 +02:00
Ferran Recio 513ac0c086 MDL-70909 mod_h5pactivity: fix reports on freeze context 2021-04-27 15:44:25 +02:00
Ferran Recio 9f7752d6bc MDL-70909 behat: context freeze and not freeze steps 2021-04-27 15:44:25 +02:00
Ferran Recio 79071cdd87 MDL-70909 core: add cannotmatchanyrows to capabilities join 2021-04-27 15:44:25 +02:00
Sara Arjona 69962f36d7 weekly release 3.9.6+ 2021-04-27 15:14:56 +02:00
Víctor Déniz 76ef9c5d1a Merge branch 'MDL-65687-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-27 01:13:36 +01:00
Víctor Déniz b67b742e40 Merge branch 'MDL-71003-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-27 00:38:48 +01:00
Sara Arjona eece2df651 Merge branch 'MDL-70917-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-26 15:18:17 +02:00
Sara Arjona 61ef211525 Merge branch 'MDL-71416-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-26 12:35:56 +02:00
Eloy Lafuente (stronk7) 535fcef31e Merge branch 'MDL-70446-m39' of https://github.com/sammarshallou/moodle into MOODLE_39_STABLE 2021-04-26 10:45:26 +02:00
Paul Holden 817757c088 MDL-71434 mod_data: add form control class to template textarea. 2021-04-25 18:35:05 +01:00
Tim Schroeder 054d5c0f04 MDL-70110 assign: fixed ws get_submissions for groups 2021-04-24 12:12:01 +02:00
Paul Holden adcd50bea0 MDL-71416 report_log: user context header should show users name.
Co-Authored-By: Luca Bösch <luca.boesch@bfh.ch>
2021-04-23 20:24:39 +01:00
Sara Arjona 3018a35249 weekly release 3.9.6+ 2021-04-23 10:47:01 +02:00
Jun Pataleta 1d217301ad Merge branch 'MDL-71171_39' of git://github.com/mdjnelson/moodle into MOODLE_39_STABLE 2021-04-23 12:40:29 +08:00
Mark Nelson 4dc6c7a79d MDL-71171 customfield_text: ensure we set defaults properly 2021-04-23 12:37:49 +08:00
Andrew Nicols 6d6a39e34d Merge branch 'MOODLE_39_STABLE-MDL-71376' of https://github.com/rjnl/moodle into MOODLE_39_STABLE 2021-04-23 11:53:36 +08:00
Andrew Nicols 1b169c82be Merge branch 'MDL-70970-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-23 11:39:52 +08:00
ScottVerbeek 91376cdb69 MDL-66673 groups: Stop notice when messaging system is disabled 2021-04-23 08:55:09 +10:00
Sara Arjona 3f5477f8a0 Merge branch 'MDL-71136_39' of https://github.com/TomoTsuyuki/moodle into MOODLE_39_STABLE 2021-04-22 13:56:55 +02:00
Sara Arjona 2166e3e681 Merge branch 'MDL-71400-39' of git://github.com/mihailges/moodle into MOODLE_39_STABLE 2021-04-22 13:16:44 +02:00
Jun Pataleta 9e3b15f31e Merge branch 'MDL-71170_39' of git://github.com/mdjnelson/moodle into MOODLE_39_STABLE 2021-04-22 18:54:41 +08:00
Noel De Martin dfe187a59d MDL-71386 behat: Support for 3.9.5+ mobile app 2021-04-22 12:16:25 +02:00
Paul Holden 45725c2408 MDL-71200 mod_folder: ensure "Download folder" option is backed up. 2021-04-22 10:29:04 +01:00
Paul Holden 8aea2ef66b MDL-70979 search: validate external user search parameters.
Clean return value in unit test for same.
2021-04-22 10:23:10 +01:00
Mihail Geshoski f065061f08 MDL-71400 assignfeedback_offline: Fix message after worksheet upload 2021-04-22 13:08:59 +08:00
Jun Pataleta 92df285f49 Merge branch 'MDL-71156-39' of git://github.com/lameze/moodle into MOODLE_39_STABLE 2021-04-22 11:53:53 +08:00
Simey Lameze c72839d6d0 MDL-71156 core: machinery to recover orphaned calendar events
The machinery to fix orphaned calendar events that were broken by MDL-67494.

The solution consists of:

1) Upgrade step that checks if this site has executed the problematic upgrade steps and
   if positive, it will schedule a new run for calendar_fix_orphaned_events adhoc task.

2) Adhoc task that will self-spawn calling the recovery machinery, running until
   all the orphaned calendar events are fixed. It also sets the maximum runtime of
   60 seconds. It is also possible to override that number by specifing the desired
   number setting the ->calendareventsmaxseconds in your config.php

3) CLI script that will look for all the calendar events which userids
   where broken by a wrong upgrade step, affecting to Moodle 3.9.5
   and up.

   It performs checks to both:
     a) Detect if the site was affected (ran the wrong upgrade step).
     b) Look for orphaned calendar events, categorising them as:
       - standard: site / category / course / group / user events
       - subscription: events created via subscriptions.
       - action: normal action events, created to show common important dates.
       - override: user and group override events, particular, that some activities support.
       - custom: other events, not being any of the above, common or particular.

   By specifying it (--fix) try to recover as many broken events (missing userid) as
   possible. Standard, subscription, action, override events in core are fully supported but
   override or custom events should be fixed by each plugin as far as there isn't any standard
   API (plugin-wise) to launch a rebuild of the calendar events.

4) Unit tests and helper functions to generate calendar events. We have decided to
   keep the tests simple, testing only true and false and not using data generators because
   the code is purely to recover the calendar events and won't turn into an API or something
   and also due to the urgency of this issue.
   The helpers have been created in calendar/tests/helpers.php since there are no data generators
   for calendar.
2021-04-22 10:45:27 +08:00
Andrew Nicols f33b8bb576 Merge branch 'MDL-70412-39' of git://github.com/aanabit/moodle into MOODLE_39_STABLE 2021-04-22 10:07:40 +08:00
Sara Arjona 7ccf8f2fe4 Merge branch 'MDL-64336-m39' of https://github.com/NeillM/moodle into MOODLE_39_STABLE 2021-04-21 17:15:23 +02:00
Sara Arjona 0997a89e39 Merge branch 'MDL-62244_39_label_view' of https://github.com/sheesania/moodle into MOODLE_39_STABLE 2021-04-20 17:45:12 +02:00
Sara Arjona f63aed03b8 weekly release 3.9.6+ 2021-04-20 15:51:17 +02:00
Sara Arjona cf3215cff4 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-04-20 15:51:16 +02:00
Paul Holden d26512cb63 MDL-70970 quizaccess_seb: consistent property array sorting by key.
In PHP8.0 using `ksort` was producing incorrect results by sorting
keys differing only in case in the wrong order. This change makes
sorting consistent between PHP versions.

Co-Authored-By: Tim Hunt <T.J.Hunt@open.ac.uk>
2021-04-20 14:36:22 +01:00
Rajneel Totaram b4ccc75a03 MDL-71376 mod_quiz: fix undefined object reference 2021-04-20 20:14:53 +12:00
Jun Pataleta 49f994fb03 Merge branch 'MDL-71364_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-04-20 09:23:14 +08:00
AMOS bot ebd1eb3726 Automatically generated installer lang files 2021-04-20 00:07:48 +00:00
Víctor Déniz 88d21b999c Merge branch 'MDL-71087-39' of https://github.com/NashTechOpenUniversity/moodle into MOODLE_39_STABLE 2021-04-19 23:45:29 +01:00
Eloy Lafuente (stronk7) 946cbedf54 Merge branch 'MDL-69256-39' of git://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2021-04-20 00:05:14 +02:00
Víctor Déniz 6ec958e639 Merge branch 'MOODLE_39_STABLE_fix_gradeimport_csv' of https://github.com/intrajp/moodle into MOODLE_39_STABLE 2021-04-19 22:49:51 +01:00
Dongsheng Cai 737682c750 MDL-68747 mod_quiz: force LTR for quiz grading overview chart 2021-04-19 21:06:31 +10:00
Sara Arjona 138e5b9996 Merge branch 'MDL-71365_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-04-19 10:18:46 +02:00
Paul Holden f3f7f28015 MDL-71127 mod_quiz: fix thrown exception for invalid question columns. 2021-04-19 09:07:24 +01:00
Paul Holden 2bada93f17 MDL-71168 report_participation: fix select all users checkbox. 2021-04-19 09:05:06 +01:00
Sara Arjona a5d64155ad Merge branch 'MDL-66475-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-19 09:49:29 +02:00
Sara Arjona e511a7fdc4 Merge branch 'MDL-65553-39' of git://github.com/aanabit/moodle into MOODLE_39_STABLE 2021-04-19 08:53:51 +02:00
AMOS bot 767c610be1 Automatically generated installer lang files 2021-04-18 00:07:44 +00:00
Eloy Lafuente (stronk7) c0f4d9e170 MDL-71365 grunt: fix ignores missing LF @ EOF 2021-04-18 00:42:52 +02:00
Eloy Lafuente (stronk7) e4c7028c20 MDL-71364 behat: fix randomness causes by "now" advancing
Some recent tests do set a date time element
to ##now## or tomorrow and, immediately after that
the look if, effectively, ##now## and #tomorrow#
have been set (with minutes resolution).

Problem is that, between the field is set and the field
is verified, it can happen that the time advances to
next minute (from H:M:59 to H:M+1:00) and then the
assertion fails.

To avoid this, we could have lowered the resolution to be
hours... but that doesn't solve the problem just makes it
to happen less often.

So, instead of that... we are setting the 2 now and tomorrow
cases to be "today noon" and "tomorrow noon" (12:00:00) so
we ensure they won't be ever in the risk of jumping of minute.
2021-04-17 12:34:03 +02:00
Eloy Lafuente (stronk7) 375ec1c034 Merge branch 'MDL-70099-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-17 09:40:45 +02:00
Sara Arjona 1c3b9af49a weekly release 3.9.6+ 2021-04-16 17:03:31 +02:00
Sara Arjona d4d2ef9119 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-04-16 17:03:30 +02:00
Andrew Nicols fdcc9383b1 Merge branch 'MDL-60490-missing_close_div-39' of https://github.com/leonstr/moodle into MOODLE_39_STABLE 2021-04-16 11:55:01 +08:00
Andrew Nicols d1d57dc12d Merge branch 'MDL-69415-39' of git://github.com/sarjona/moodle into MOODLE_39_STABLE 2021-04-16 11:27:42 +08:00
Amaia Anabitarte 27a6bf394d MDL-70412 groups: Improving create_group data generator
Increasing the size of the name for generated groups
to ensure we don't have problems with ordering.
2021-04-15 16:57:23 +02:00
Amaia Anabitarte 5e4aab248f MDL-65553 core_analytics: Don't allow to flag predictions several times
Even if a prediction is hidden from the report once is flagged,
it can be flagged several times if the user visits detailed view via URL.
We remove the checkbox to select a prediction and flag it
once it has already been flagged.
2021-04-15 16:39:09 +02:00
Jake Dallimore c32142cf80 Merge branch 'MDL-71338-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-15 14:43:28 +08:00
Jake Dallimore ea90581cb1 Merge branch 'MDL-71119-39' of git://github.com/sarjona/moodle into MOODLE_39_STABLE 2021-04-15 08:33:03 +08:00
AMOS bot 505366bed8 Automatically generated installer lang files 2021-04-15 00:07:59 +00:00
Paul Holden a6a5130a65 MDL-71338 tool_usertours: correct Content-Type on tour export.
Replace existing code with standard file serving mechanism, in
turn correcting the previously incorrect Content-Type header.
2021-04-14 21:12:03 +01:00
sam marshall a53238a07b MDL-70446 search_solr: File indexing can fail due to multipart upload
Due to Solr bug SOLR-15039, uploading files for indexing can fail if
it uses multipart upload. This changes it to use direct binary upload.

Unfortunately, the direct binary version in PHP curl only supports a
string, so we have to load the file into memory. I added extra code to
restrict the size of files indexed to (memory limit - 100MB), which is
usually 284MB unless configured differently because cron runs under
MEMORY_EXTRA.
2021-04-14 13:19:08 +01:00
Eloy Lafuente (stronk7) e085678bf2 Merge branch 'MDL-70616-39-fix' of https://github.com/izendegi/moodle into MOODLE_39_STABLE 2021-04-14 12:45:31 +02:00
Iñigo Zendegi ad3ac443a9 MDL-70616 gradingform_rubric: Filters not applied to rubric name 2021-04-14 11:48:58 +02:00
Sara Arjona 81da134c2c MDL-71119 core_badges: Add more information to errors
Some errors raised when calling external services were ignored.
Displaying this information helps to debug and find the real reasons
why some action, like connecting to an external backpack, can't be
done.
2021-04-14 11:04:28 +02:00
Sara Arjona dc4a36c3b1 MDL-71119 core_badges: Use basicauth and remove urlencode
Basic authorization header is required to get token. Besides, client
id and secret shouldn't call urlencode.
2021-04-14 11:04:28 +02:00
Sara Arjona 946120eeee MDL-71119 core_badges: Initialise scopes param in backpack-connect.php
The scopes parameter should be passed when creating the OAuth2 badges
client. As it is an optional parameter, when it's empty, it will
be initilised with the supported scopes for the backpack issuer.

It will happen, for instance, when a call is done to oauth2callback.php
because, as defined in RFC6749, OAuth2 authorization response only
supports code and state.
2021-04-14 11:04:28 +02:00
Paul Holden 82d35db47a MDL-70772 mod_forum: check for valid usernode before making active. 2021-04-14 09:20:26 +01:00
Andrew Nicols 83c3d04702 Merge branch 'MDL-71154_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-04-14 12:11:46 +08:00
AMOS bot 16736110d8 Automatically generated installer lang files 2021-04-14 00:07:53 +00:00
Eloy Lafuente (stronk7) 22f1a27aa3 MDL-71154 ci: Prevent upstream branches and tags to be built
Both for travis and GHA:

- master and MOODLE_XXX_STABLE branches
- any vX.Y.Z tag

won't lead to a build anymore.
2021-04-13 16:55:54 +02:00
Eloy Lafuente (stronk7) 30e6bf75dd weekly release 3.9.6+ 2021-04-13 10:26:35 +02:00
Eloy Lafuente (stronk7) 9c4a0b8490 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-04-13 10:26:34 +02:00
Eloy Lafuente (stronk7) da03cf8972 Merge branch 'MDL-71059-39' of git://github.com/abias/moodle into MOODLE_39_STABLE 2021-04-13 00:42:42 +02:00
Tim Hunt 0b3e3d7c54 MDL-71313 repositories: warn if get_instances context is wrong type 2021-04-12 20:32:07 +01:00
Sara Arjona 1b8ced636c Merge branch 'MDL-68716-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-12 17:24:52 +02:00
Sara Arjona 2c73fbfc3d Merge branch 'MDL-71264_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-04-12 16:40:25 +02:00
Sara Arjona 5e3c934691 Merge branch 'MDL-70595-39' of git://github.com/mickhawkins/moodle into MOODLE_39_STABLE 2021-04-12 16:01:49 +02:00
Sara Arjona df3efafbcf Merge branch 'MDL-71107-39' of git://github.com/aanabit/moodle into MOODLE_39_STABLE 2021-04-12 12:41:24 +02:00
Eloy Lafuente (stronk7) e2f2de4885 Merge branch 'MDL-69102-39' of https://github.com/martygilbert/moodle into MOODLE_39_STABLE 2021-04-12 00:02:14 +02:00
Eloy Lafuente (stronk7) 5279159083 MDL-69102 phpunit: Add test to cover ALL cohort criteria
That way we can check that the expectations are
working ok for every database. Previously only the
ANY case was being tested.

Also, convert them to course badges and check, via
review_all_criteria() that the SQL structures returned
by award_criteria_cohort->get_completed_criteria_sql()
doesn't fail and return the expected counters.
2021-04-10 12:37:52 -04:00
Issam Taboubi 8392586599 MDL-71303 core: HTML not escaped in block skip link 2021-04-09 17:34:25 -04:00
AMOS bot 26a0581c1b Automatically generated installer lang files 2021-04-09 00:07:54 +00:00
Eric Merrill 6037a3dde0 MDL-52724 editor_atto: Scrub atto html for invalid ol, ul, and li tags
Try to correct broken ul/ol/li tags, as they have an outsized impact
on course layout. Uses basic regex and loops to track open and closed
tags.

Also adds a deep clean option to the HTML cleaner, that runs less
frequent, more intensive cleanings. This is because normally _cleanHTML
gets called after each keystroke, which could cause problems with
large content on weak systems.

Behat changes are a fix for setting multiline strings in Atto, and
creating a multiline match step.
2021-04-08 15:37:24 -04:00
Eloy Lafuente (stronk7) 471d1a4b6d Merge branch 'MDL-70802-39' of git://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2021-04-08 18:38:58 +02:00
Víctor Déniz 3db93ec2a2 Merge branch 'MDL-71116-39' of git://github.com/sarjona/moodle into MOODLE_39_STABLE 2021-04-08 01:06:30 +01:00
Amaia Anabitarte ff6ac0b2fd MDL-71107 core_contentbank: Behat test. Authoring when copying a course 2021-04-07 10:03:37 +02:00
Amaia Anabitarte 57d5be04f8 MDL-71107 core_contentbank: Fill content author when copying a course
'usercreated' and 'usermodified' fields can not be always mapped.
We are filling those fields with 'old' users when working on the same
site, or with current user when working on a different instance.
2021-04-07 10:03:37 +02:00
Alex Rowe c9d3c97e74 MDL-70049 assign: count_submissions_need_grading call when can't grade 2021-04-07 08:56:10 +10:00
Eloy Lafuente (stronk7) a772325afc Merge branch 'MDL-70959-39' of git://github.com/junpataleta/moodle into MOODLE_39_STABLE 2021-04-06 22:29:58 +02:00
Eloy Lafuente (stronk7) 4c1c1aa6c6 Merge branch 'MDL-68635-39' of https://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-06 22:23:50 +02:00
Víctor Déniz 05e79651b6 Merge branch 'MDL-71128-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-04-06 19:51:32 +01:00
Michael Hawkins a8c569c368 MDL-70595 core: Updated security.txt expiry 2021-04-06 13:14:01 +08:00
AMOS bot 12afd60ba2 Automatically generated installer lang files 2021-04-06 00:08:03 +00:00
Paul Holden c4fb2a2141 MDL-70099 enrol: increase accuracy of current enrolments by date.
By rounding the current time it was possible that the most recently
created user enrolments (e.g. self enrolments) were being excluded.

This would manifest itself in a user being enrolled on a course,
but it not appearing under "My courses" navigation or on their own
Dashboard until the rounded time had caught up with the current
time.
2021-04-05 19:32:07 +01:00
Paul Holden 97d63b6272 MDL-71003 user: fix alignment of autocomplete participant filters. 2021-04-04 23:21:33 +01:00
Paul Holden 39944aee16 MDL-70917 tool_behat: fix profile/replace option comparison.
The `isset` call always returned true for the 'replace' option,
because even if not specified it receives a non-null value. The
`strpos` call now does a strict equality check rather than
greater-than-or-equal (which always returns true).
2021-04-04 23:18:44 +01:00
Paul Holden 3284f1fe7e MDL-70763 core: export preferences for correct user.
Ensure we are using the ID of the given user rather than falling
back to that of the current user during privacy export.
2021-04-04 23:15:51 +01:00
Paul Holden a96ad13616 MDL-70763 quiz_overview: export preferences for correct user. 2021-04-04 23:15:51 +01:00
Paul Holden 3c872b34c6 MDL-70763 gradereport_grader: export preferences for correct user. 2021-04-04 23:15:51 +01:00
Paul Holden 4699142c9b MDL-66475 task: fix random timing failures in logger cleanup test. 2021-04-04 23:12:27 +01:00
AMOS bot 613e7f8a88 Automatically generated installer lang files 2021-04-04 00:08:01 +00:00
Eloy Lafuente (stronk7) b240173b66 MDL-71264 behat: Change date(time) element update order
This includes 2 change to the order date(time) elements are filled,
each one addressing one type of problem, where current order is
problematic and can lead to unexpected dates.

1) Changing date, when current month only has X days and target
   month has more than X days. Example, being 1 April, change
   the date to 31 May.
     This is solved by changing the order of introduction
     from current D => M => Y to Y => M => D.
2) Changing date, when target month only has X days and current
   month has more that X days. Example, being 31 March, change
   the date to 28 Feb.
     This is solved by always setting the D to 1, before the
     Y => M => D sequence commented @ 1) begins.
2021-04-03 18:09:47 +02:00
Eloy Lafuente (stronk7) 74730a2d63 MDL-71264 behat: New feature covering all day/month/year date changes
Because of the order that dates and datetimes are filled by behat
sometimes there are some intermediate results that are impossible
and then the form (javascript) automatically reacts and fixes the
date, ultimate leading to a different date that the one we wanted
to set with Behat.

This is noticeable when switching between months (with some day
being the last day of the month) and the 2 months have a different
number of days. For example April date => March date (or the opposite).

This test covers all the critical changes (day, month and year),
back and forth. All times are Perth/Australia.
2021-04-03 11:00:04 +02:00
Huong Nguyen 0273fc7731 MDL-70947 File upload: Upload protection
Interacts badly with double-submit protection, and not all uploads protected
2021-04-02 15:27:29 +07:00
AMOS bot eecb28b386 Automatically generated installer lang files 2021-04-02 00:07:46 +00:00
Marty Gilbert 88f81cf8d1 MDL-69102 badge: Fixes broken WHERE clause
When buildilng the 'WHERE' clause, the SQL generated
a query that selected any user who was a member of the
**last** group, not all of the groups.

I believe the query has to be re-worked so that it only
returns users who are in ALL of the groups. This can
be done by a GROUP BY and HAVING.
2021-04-01 14:51:27 -04:00
Alison Blomenberg fe177d5dec MDL-62244 Resource: label view redirects to label, not course 2021-04-01 11:53:53 -06:00
Leon Stringer d80644a986 MDL-60490 tool_lp: Missing div close
Mustache template had opening <div> with missing </div>.
2021-04-01 12:17:24 +01:00
AMOS bot bfe2779cea Automatically generated installer lang files 2021-04-01 00:07:48 +00:00
Juan Leyva 39916b12fd MDL-70863 mod_forum: Allow NULL in timecreated 2021-03-31 17:51:20 +02:00
Neill Magill b861c1dec7 MDL-64336 assign: Add missing PHP doc 2021-03-31 08:46:28 +01:00
Neill Magill 808a335c90 MDL-64336 assign: Teachers should be able to see all submissions
Before this change a teacher would be able to see users listed if:

* They have an active enrolment and can submit
* They have an an inactive enrolment for a role that can submit

After this change they will additonally be able to see users listed:

* That have an active enrolment and have submitted
* That have an inactive enrolment and have submitted

This means that if an assignment has it's context frozen all users
that have made some form of submission will still be listed.

It will also apply if the submission capability is removed from a
user.

If a user's enrolment is deleted they will not be listed.

The submission and grading counts have also been updated so
they will reflect the new rules.
2021-03-31 08:46:17 +01:00
Neill Magill 3b55f2847a MDL-64336 assign: Submissions should be visible while frozen
Before this change if a student visited an assignment that is
frozen they would only see the title and description even if
they had made a submission to it.

After the change they will be able to see the status of their
submission and any feedback and grades they have recived.

It will also make the Moodle app recognise that submission
should not happen because the assignment is frozen.

Tests based on ones created by Andrew Nicols
2021-03-31 08:46:06 +01:00
Víctor Déniz a2e4730a35 weekly release 3.9.6+ 2021-03-30 15:37:38 +01:00
Víctor Déniz 7c2b6ed44f Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-03-30 15:37:37 +01:00
Paul Holden 3c4d30bcf4 MDL-65687 message: increase confirmation dialog zindex.
Make sure that confirmation dialogs (such as block/unblock user,
delete conversation) are shown with prominence in the messaging
drawer.
2021-03-30 10:50:57 +01:00
Tomo Tsuyuki c34c6a75df MDL-71136 backup: Fix moodle_exception path. 2021-03-30 10:55:38 +11:00
Paul Holden 0464ae85ca MDL-68635 tool_cohortroles: observe user initial table filter. 2021-03-29 14:12:01 +01:00
Paul Holden 3211454a2a MDL-68716 mod_forum: extra column sorting options in privacy export.
Accounts for extra sort options that were added in bc23cbaf.
2021-03-29 14:05:27 +01:00
Eloy Lafuente (stronk7) 06bebd6820 Merge branch 'MDL-70963-39' of git://github.com/sarjona/moodle into MOODLE_39_STABLE 2021-03-29 12:40:07 +02:00
Andrew Nicols 3f0cc3d712 Merge branch 'MDL-71004_39' of https://github.com/stronk7/moodle into MOODLE_39_STABLE 2021-03-29 11:54:53 +08:00
AMOS bot f71bfc90b1 Automatically generated installer lang files 2021-03-29 00:07:45 +00:00
AMOS bot ae29d57085 Automatically generated installer lang files 2021-03-28 00:07:43 +00:00
Víctor Déniz a2cf272e00 Merge branch 'MDL-70537-m39' of https://github.com/sammarshallou/moodle into MOODLE_39_STABLE 2021-03-26 20:16:22 +00:00
Sara Arjona 4736877c43 MDL-70963 core_badges: Use expected JSON format for OBv2.1
This patch adds some changes to guarantee that OpenBadges 2.1 (OBv2.1)
will return always the same JSON format that OBv2.0 (because the only
difference between both versions it's the BadgeConnect API).
2021-03-26 09:02:41 +01:00
Shintaro Fujiwara aa6c15c975 MDL-69304 gradeimport_csv: Validate if the user id is numerical. 2021-03-26 04:26:51 +09:00
Víctor Déniz d7d8a654f3 Merge branch 'MDL-71194-39' of git://github.com/NoelDeMartin/moodle into MOODLE_39_STABLE 2021-03-25 15:15:26 +00:00
Noel De Martin 2794943d3c MDL-71194 behat: Fix app tests 2021-03-25 13:50:27 +01:00
Andrew Nicols 8c62673dc5 Merge branch 'MDL-70980-39' of git://github.com/ferranrecio/moodle into MOODLE_39_STABLE 2021-03-25 08:07:54 +08:00
AMOS bot f7a80e3f2b Automatically generated installer lang files 2021-03-25 00:07:53 +00:00
Eloy Lafuente (stronk7) 773e7cd612 Moodle release 3.9.6 2021-03-24 20:12:40 +01:00
Eloy Lafuente (stronk7) 8f23a60ba8 MDL-71004 git: Ignore moodle-plugin-ci.phar in any directory 2021-03-24 20:10:38 +01:00
Víctor Déniz ce059497ac weekly release 3.9.5+ 2021-03-24 14:20:57 +00:00
Eloy Lafuente (stronk7) 6c712c9caf Merge branch 'MDL-71182-39-2' of git://github.com/lameze/moodle into MOODLE_39_STABLE 2021-03-24 10:51:13 +01:00
Simey Lameze 6cfed9eebb MDL-71182 calendar: add logic for category events in get_context
This commit adds the missing logic for category events in
get_context() method. Without this fix, users will get errors
if they have category events with userid set to 0.
2021-03-24 17:35:09 +08:00
Andrew Nicols 2370d0c629 MDL-70802 javascript: Normalise component consistently in templates 2021-03-24 11:18:33 +08:00
Simey Lameze 7cae4c7570 MDL-71182 calendar: Revert "Merge branch 'MDL-67494-39' of git://github.com/lameze/moodle into MOODLE_39_STABLE"
This reverts commit c0c9be6d03, reversing
changes made to 226a690c0d.
2021-03-24 09:31:36 +08:00
Víctor Déniz 86f6671fe6 Merge branch 'MDL-71022-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-03-23 23:02:22 +00:00
Thinh Pham 91a40ccce5 MDL-70727 Behat: function scrollIntoView not working in the app 2021-03-23 17:59:27 +00:00
Thinh Pham 20c93ebb6f MDL-71087 File picker: Focus lost after 'Create folder' 2021-03-23 10:44:25 +07:00
Andrew Nicols 62ee8fd5c8 Merge branch 'MDL-71039-39' of git://github.com/marinaglancy/moodle into MOODLE_39_STABLE 2021-03-23 11:18:52 +08:00
Andrew Nicols 35c6838f14 Merge branch 'MDL-69036-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-03-23 09:56:57 +08:00
Andrew Nicols 596e7c7b0c Merge branch 'MDL-67870-39' of git://github.com/mihailges/moodle into MOODLE_39_STABLE 2021-03-23 09:25:04 +08:00
Andrew Nicols 1720bef450 MDL-69256 tests: Fix random slow test failures in completion cron 2021-03-23 08:08:37 +08:00
Mark Nelson c197d3f14b MDL-71170 customfield_text: use correct error string 2021-03-22 21:57:02 +08:00
Sara Arjona 42934afbed MDL-69415 core_h5p: prepend H5P libraries in autoloader
Core and mod_hvp are using the same namespace for some H5P
libraries. This is causing some random errors.
In order to get the expected behaviour in Moodle core, this patch
prepends H5P libraries in order to guarantee they are loaded first.
Plugins using same libraries will need to use a different namespace
if they overlap and a different version of these libraries should
be used.
2021-03-22 12:41:08 +01:00
Ferran Recio a97de58aa7 MDL-70980 mod_h5pactivity: fix review mode 2021-03-22 10:58:04 +01:00
Mihail Geshoski 47d87f7243 MDL-67870 backup: Skip value change if setting is locked by hierarchy
The given condition in the if statement did not take into account cases
when the setting is locked by hierarchy. Settings which are locked by
hierarchy should should keep their current values which were inherited
from the parent.
2021-03-22 15:33:57 +08:00
Chris Brown 56f849dcc6 MDL-71139 trademark: Improve the trademark messaging 2021-03-22 12:00:44 +08:00
Sara Arjona 3784dcc912 MDL-71116 core_badges: Backpack URLs with more than 50 chars
Backpack URLs (web and API) can have up to 255. However, the form
for creating/editing them, doesn't allow URLs with more than 50
characters. This patch align limitation to DB value (255).
2021-03-19 15:09:12 +01:00
Eloy Lafuente (stronk7) 2ad3d1a7f1 weekly release 3.9.5+ 2021-03-19 14:06:29 +01:00
Eloy Lafuente (stronk7) 3265a02f6c Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-03-19 14:06:28 +01:00
Jun Pataleta 6bc25f23fd MDL-70959 completion: Fix completion_info::get_data() unit tests
* Fixed inverted $sameuser test data.
* Fixed caching expectation check. Caching only relies on whether the
user accessing the completion data is the same user or not.
* Fixed checking for the caching of other modules. Should have been
checking cm ID and not instance ID.
* Additional test case when whole course parameter is passed as
true, but the requesting user is different from the target user.
2021-03-19 11:52:23 +08:00
Jun Pataleta 42631c3e34 MDL-70959 completion: Fix unit tests for get_data()
The unit tests for completion_info::get_data() does not make a lot of
sense with mocking being incorrectly used and the actual functionality
is not being properly tested. I have rewritten the test to use actual
cm_info instances and data providers for better coverage.
2021-03-19 11:52:23 +08:00
Jun Pataleta 4e75827d72 MDL-70959 completion: Documentation improvements
* Fix typo in $cm parameter's type in PHPDoc
* Improve the comment to better explain the logic why fetching the
completion data for the whole course can only be done when caching is
used.
Even if $wholecourse is set to true, there's no point in fetching the
completion data for all the course modules unless we're caching the
results.
2021-03-19 11:52:23 +08:00
sam marshall 6b06a36646 MDL-70537 Availability: Avoid recursive calls to $cm->name
These recursive calls didn't work in PHP 7.3 and below, but in PHP
7.4 they also cause a fatal error which means if you have invalid
availability data, the whole page might die.
2021-03-18 15:56:55 +00:00
sangnguyen bbf96cb595 MDL-70992 Folder: Unnecessary tab stops to the left of files 2021-03-18 12:24:23 +08:00
Adrian Greeve 918d45dfbd Merge branch 'MDL-70924-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-03-18 11:51:20 +08:00
Adrian Greeve 35b29cf219 Merge branch 'MDL-71048-39' of https://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-03-18 11:08:55 +08:00
Adrian Greeve 6f5266c717 Merge branch 'MDL-70977-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-03-18 10:26:16 +08:00
Adrian Greeve 841fd94499 Merge branch 'MDL-71068-protectusername-confirmed-MOODLE_39_STABLE' of https://github.com/brendanheywood/moodle into MOODLE_39_STABLE 2021-03-18 09:26:13 +08:00
AMOS bot 3c8abccaaa Automatically generated installer lang files 2021-03-18 00:07:45 +00:00
Brendan Heywood 23cc13e8ef MDL-71068 login: Fix edge cases with $CFG->protectusernames 2021-03-17 17:40:01 +11:00
Paul Holden b665e5aa33 MDL-71128 mod_quiz: escape question idnumber on display. 2021-03-16 20:28:50 +00:00
Sara Arjona 5fc3b83497 Merge branch 'MDL-44613-integration-39' of git://github.com/mihailges/moodle into MOODLE_39_STABLE 2021-03-16 08:27:09 +01:00
Mihail Geshoski 56fdffa68e MDL-44613 mod_assign: Fix behat failure in classic
The link to the calendar page is missing in the navigation block in
classic which causes a behat failure in the 'Set availibility dates
for an assignment' feature. The fix includes adding the calendar block
to the Course page which can be used to navigate to the calendar page.
2021-03-16 11:57:26 +08:00
Adrian Greeve 2b442c2fb7 Merge branch 'MDL-70780-39' of git://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2021-03-16 11:26:22 +08:00
Sara Arjona 7215c71486 Merge branch 'MDL-44613-39-1' of git://github.com/mihailges/moodle into MOODLE_39_STABLE 2021-03-15 15:54:05 +01:00
Paul Holden 9b3a8f7bd1 MDL-70977 mod_forum: fix empty equality check updating post content.
Using the external method for updating posts, the check for empty
subject/message content wasn't correct (disallowed the string '0'),
in addition to being impossible to set a posts message format
property to FORMAT_MOODLE (integer 0).
2021-03-15 12:49:12 +00:00
Paul Holden 51b2acfae3 MDL-70924 course: ensure required fields present when getting courses. 2021-03-15 12:46:58 +00:00
Mihail Geshoski 5dbf93200a MDL-44613 mod_assign: Automate testing of the assignment availability 2021-03-15 20:05:25 +08:00
Mihail Geshoski 4b987e5f7a MDL-44613 tool_behat: Update datetime format usages to strftime
Updates all existing datetime format usages to strftime in the
'time to timestamp' transformation in tool_behat behat tests.
2021-03-15 20:05:25 +08:00
Mihail Geshoski 337704e555 MDL-44613 core_message: Update datetime format usages to strftime
Updates all existing datetime format usages to strftime in the
'time to timestamp' transformation in core_message behat tests.
2021-03-15 20:05:25 +08:00
Mihail Geshoski 9f01cf6122 MDL-44613 behat: strftime format in time to timestamp transformation
Modifies the 'string time to timestamp' behat trasformation to use
userdate() instead of date(). The userdate() method is generally used
throught Moodle for constructing formatted date strings and this change
will provide more consistency and prevent any potential behat failures.
Therefore, if the date format is defined in the given trasformation, it
has to be strftime compatible. Example:
'I should see "##tomorrow noon##%A, %d %B %Y, %I:%M %p##"'
2021-03-15 20:05:25 +08:00
Adrian Greeve 8872202049 Merge branch 'MDL-70306-39' of git://github.com/peterRd/moodle into MOODLE_39_STABLE 2021-03-15 11:13:47 +08:00
Peter Dias 855b9e504b MDL-70306 auth: Update the thirdpartylib.xml 2021-03-15 11:10:34 +08:00
AMOS bot f253fa7f7e Automatically generated installer lang files 2021-03-13 00:07:46 +00:00
Sara Arjona 8ee6f49384 weekly release 3.9.5+ 2021-03-11 17:18:47 +01:00
Sara Arjona f4f6534b40 Merge branch 'install_39_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_39_STABLE 2021-03-11 17:18:47 +01:00
Adrian Greeve 1d70790168 Merge branch 'MDL-66025-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-03-11 11:38:07 +08:00
Adrian Greeve 17a5e73a9b Merge branch 'MDL-71026-39' of git://github.com/ilyatregubov/moodle into MOODLE_39_STABLE 2021-03-11 11:01:52 +08:00
Eloy Lafuente (stronk7) 5987bed350 Merge branch 'MDL-70726-3.9' of https://github.com/NashTechOpenUniversity/moodle into MOODLE_39_STABLE 2021-03-11 00:12:19 +01:00
Eloy Lafuente (stronk7) b6d0dec01e Merge branch 'MDL-70789-39' of git://github.com/paulholden/moodle into MOODLE_39_STABLE 2021-03-10 23:50:22 +01:00
Eloy Lafuente (stronk7) c0d519b426 Merge branch 'MDL-70759_39' of https://github.com/ffhs/moodle into MOODLE_39_STABLE 2021-03-10 23:42:25 +01:00
Eloy Lafuente (stronk7) 05c66fcebd Merge branch 'MDL-70967-39' of git://github.com/marinaglancy/moodle into MOODLE_39_STABLE 2021-03-10 23:35:33 +01:00
AMOS bot 5c545ca2ff Automatically generated installer lang files 2021-03-10 00:07:39 +00:00
Paul Holden 4037bdd825 MDL-69036 theme_boost: consistent table row hovering for links.
When hovering over table rows with the `dimmed_text` class we
should consistently set the text/link colour.

Co-Authored-By: Mikel Martín <mikel@moodle.com>
2021-03-09 16:22:48 +00:00
Andrew Nicols c211e62465 MDL-70780 customfield: Address random behat fails
A number of behat fails have been creeping in with the customfield code
as behat is moving onto the next step before the current step finishes.

This change introduces some additional pending JS tracking to prevent
this.
2021-03-09 07:52:24 +08:00
AMOS bot ff27bc6c5a Automatically generated installer lang files 2021-03-07 00:07:42 +00:00
Alexander Bias bab8ea6608 MDL-71059 repository_contentbank: set default returntype
The content bank was designed for making easier to reuse content.
So "Link to the file" should be the default option when using the
contentbank repository, in order to let teachers modify their
content in the content bank and update automatically it everywhere
is used.
2021-03-05 14:20:38 +01:00
Sara Arjona 4501c37045 MDL-70759 core_h5p: add renderer to let personalize styles
This is a backport of MDL-69087

Existing mod_hvp pluging has a renderer to let Moodle instances
alter styles, Javascript, semantics (fields in the editor) and
content: https://github.com/h5p/moodle-mod_hvp/blob/stable/renderer.php

The approach for core_h5p is exactly the same, to let people
to reuse their existing code.
2021-03-05 14:07:18 +01:00
Paul Holden b8322f7dd1 MDL-71048 lang: fix null equality check of lang string args. 2021-03-04 19:43:54 +00:00
Marina Glancy a2221d6d8d MDL-71039 core_form: support valuehtmlcallback in autocomplete inline 2021-03-04 13:39:56 +01:00
Ilya Tregubov f702707ef7 MDL-71026 user: Fix incorrect get_string call. 2021-03-03 11:44:30 +02:00
Paul Holden 71bb7f306e MDL-71022 gradereport_history: prevent double escaping of users.
The user identity fields are already escaped by the AJAX endpoint
that returns them, we don't need to do it again at the client.
2021-03-02 09:14:42 +00:00
Amaia Anabitarte 616935475b MDL-70726 behat: Fixing existing fail tests
Some of the current behat tests were wrong but not failing because of the bug in 'should not exist' step.
Once the bug is fixed, we must fix also wrong behats.
2021-03-01 16:09:20 +07:00
Thong Bui 8d6cbcb248 MDL-70726 behat: should_not_exist function does not work correctly 2021-03-01 16:09:20 +07:00
Marina Glancy 31b1a9601f MDL-70967 enrol_meta: fix error in unittest 2021-02-22 18:06:49 +01:00
Paul Holden b70b06bc61 MDL-66025 user: require message content in bulk action.
Also import missing 'core/notification' dependency.
2021-02-14 20:10:38 +00:00
Paul Holden f8811d5661 MDL-70789 theme_boost: fix double click on drawer hiding content.
If the drawer toggle button was clicked in quick succession to close
and then re-open, then the delayed Aria.hide method would be called
which hid the drawer content when it finished opening.
2021-02-11 21:49:14 +00:00
537 changed files with 10648 additions and 5022 deletions
+1 -1
View File
@@ -89,4 +89,4 @@ theme/boost/amd/src/bootstrap/toast.js
theme/boost/amd/src/bootstrap/tooltip.js
theme/boost/amd/src/bootstrap/util.js
theme/boost/amd/src/tether.js
theme/boost/scss/fontawesome/
theme/boost/scss/fontawesome/
+7 -1
View File
@@ -1,6 +1,12 @@
name: Core
on: [push]
on:
push:
branches-ignore:
- master
- MOODLE_[0-9]+_STABLE
tags-ignore:
- v[0-9]+.[0-9]+.[0-9]+*
env:
php: 7.4
+1
View File
@@ -44,3 +44,4 @@ composer.phar
/lib/yuilib/*/*/*-coverage.js
atlassian-ide-plugin.xml
/node_modules/
moodle-plugin-ci.phar
+1 -1
View File
@@ -90,4 +90,4 @@ theme/boost/amd/src/bootstrap/toast.js
theme/boost/amd/src/bootstrap/tooltip.js
theme/boost/amd/src/bootstrap/util.js
theme/boost/amd/src/tether.js
theme/boost/scss/fontawesome/
theme/boost/scss/fontawesome/
+7 -1
View File
@@ -10,7 +10,7 @@ language: php
os: linux
dist: xenial
dist: bionic
services:
- mysql
@@ -19,6 +19,12 @@ services:
addons:
postgresql: "9.6"
branches:
except:
- master
- /MOODLE_[0-9]+_STABLE/
- /^v[0-9]+\.[0-9]+\.[0-9]+.*/
jobs:
# Enable fast finish.
# This will fail the build if a single job fails (except those in allow_failures).
+2 -2
View File
@@ -424,7 +424,7 @@ module.exports = function(grunt) {
'*/**/yui/src/*/meta/',
'*/**/build/',
].concat(thirdPartyPaths);
grunt.file.write('.eslintignore', eslintIgnores.join('\n'));
grunt.file.write('.eslintignore', eslintIgnores.join('\n') + '\n');
// Generate .stylelintignore.
const stylelintIgnores = [
@@ -433,7 +433,7 @@ module.exports = function(grunt) {
'theme/boost/style/moodle.css',
'theme/classic/style/moodle.css',
].concat(thirdPartyPaths);
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n') + '\n');
};
/**
+1
View File
@@ -0,0 +1 @@
mod/forum/tests/behat/forum_subscriptions_default.feature
+1 -1
View File
@@ -2,7 +2,7 @@ MOODLE TRADEMARKS POLICY
========================
The word "Moodle" is trademarked in many countries around the world. The word
was originally an acronym: Modular Object-Oriented Dynamic Learning
was originally coined as an acronym: Modular Object-Oriented Dynamic Learning
Environment. The trademark is owned by Martin Dougiamas, Founder and Director
of Moodle.
+138
View File
@@ -0,0 +1,138 @@
<?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/>.
/**
* Fix orphaned calendar events that were broken by MDL-67494.
*
* This script will look for all the calendar events which userids
* where broken by a wrong upgrade step, affecting to Moodle 3.9.5
* and up.
*
* It performs checks to both:
* a) Detect if the site was affected (ran the wrong upgrade step).
* b) Look for orphaned calendar events, categorising them as:
* - standard: site / category / course / group / user events
* - subscription: events created via subscriptions.
* - action: normal action events, created to show common important dates.
* - override: user and group override events, particular, that some activities support.
* - custom: other events, not being any of the above, common or particular.
* By specifying it (--fix) try to recover as many broken events (missing userid) as
* possible. Standard, subscription, action, override events in core are fully supported but
* override or custom events should be fixed by each plugin as far as there isn't any standard
* API (plugin-wise) to launch a rebuild of the calendar events.
*
* @package core
* @copyright 2021 onwards Simey Lameze <simey@moodle.com>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . "/clilib.php");
require_once($CFG->libdir . '/db/upgradelib.php');
// Supported options.
$long = ['fix' => false, 'help' => false];
$short = ['f' => 'fix', 'h' => 'help'];
// CLI options.
[$options, $unrecognized] = cli_get_params($long, $short);
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
if ($options['help']) {
$help = <<<EOT
Fix orphaned calendar events.
This script detects calendar events that have had their
userid lost. By default it will perform various checks
and report them, showing the site status in an easy way.
Also, optionally (--fix), it wil try to recover as many
lost userids as possible from different sources. Note that
this script aims to process well-know events in core,
leaving custom events in 3rd part plugins mostly unmodified
because there isn't any consistent way to regenerate them.
For more details: https://tracker.moodle.org/browse/MDL-71156
Options:
-h, --help Print out this help.
-f, --fix Fix the orphaned calendar events in the DB.
If not specified only check and report problems to STDERR.
Usage:
- Only report: \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php
- Report and fix: \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php -f
EOT;
cli_writeln($help);
die;
}
// Check various usual pre-requisites.
if (empty($CFG->version)) {
cli_error('Database is not yet installed.');
}
$admin = get_admin();
if (!$admin) {
cli_error('Error: No admin account was found.');
}
if (moodle_needs_upgrading()) {
cli_error('Moodle upgrade pending, script execution suspended.');
}
// Do everything as admin by default.
\core\session\manager::set_user($admin);
// Report current site status.
cli_heading('Checking the site status');
$needsfix = upgrade_calendar_site_status();
// Report current calendar events status.
cli_heading('Checking the calendar events status');
$info = upgrade_calendar_events_status();
$hasbadevents = $info['total']->bad > 0 || $info['total']->bad != $info['other']->bad;
$needsfix = $needsfix || $hasbadevents;
// If, selected, fix as many calendar events as possible.
if ($options['fix']) {
// If the report has told us that the fix was not needed... ask for confirmation!
if (!$needsfix) {
cli_writeln("This site DOES NOT NEED to run the calendar events fix.");
$input = cli_input('Are you completely sure that you want to run the fix? (y/N)', 'N', ['y', 'Y', 'n', 'N']);
if (strtolower($input) != 'y') {
exit(0);
}
cli_writeln("");
}
cli_heading('Fixing as many as possible calendar events');
upgrade_calendar_events_fix_remaining($info);
// Report current (after fix) calendar events status.
cli_heading('Checking the calendar events status (after fix)');
upgrade_calendar_events_status();
} else if ($needsfix) {
// Fix option was not provided but problem events have been found. Notify the user and provide info how to fix these events.
cli_writeln("This site NEEDS to run the calendar events fix!");
cli_writeln("To fix the calendar events, re-run this script with the --fix option.");
}
+8 -8
View File
@@ -119,7 +119,7 @@ if (!empty($options['collation'])) {
if ($dbcollation->value !== $collation || $dbcharset->value !== $charset) {
// Try to convert the DB.
echo "Converting database to '$collation' for $CFG->wwwroot:\n";
$sql = "ALTER DATABASE $CFG->dbname DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation";
$sql = "ALTER DATABASE `$CFG->dbname` DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation";
try {
$DB->change_database_structure($sql);
} catch (exception $e) {
@@ -146,7 +146,7 @@ if (!empty($options['collation'])) {
} else {
try {
$DB->change_database_structure("ALTER TABLE $table->name CONVERT TO CHARACTER SET $charset COLLATE $collation");
$DB->change_database_structure("ALTER TABLE `$table->name` CONVERT TO CHARACTER SET $charset COLLATE $collation");
echo "CONVERTED\n";
$converted++;
} catch (ddl_exception $e) {
@@ -163,7 +163,7 @@ if (!empty($options['collation'])) {
}
}
$sql = "SHOW FULL COLUMNS FROM $table->name WHERE collation IS NOT NULL";
$sql = "SHOW FULL COLUMNS FROM `$table->name` WHERE collation IS NOT NULL";
$rs2 = $DB->get_recordset_sql($sql);
foreach ($rs2 as $column) {
$column = (object)array_change_key_case((array)$column, CASE_LOWER);
@@ -181,7 +181,7 @@ if (!empty($options['collation'])) {
$notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
$default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
// primary, unique and inc are not supported for texts
$sql = "ALTER TABLE $table->name
$sql = "ALTER TABLE `$table->name`
MODIFY COLUMN $column->field $column->type
CHARACTER SET $charset
COLLATE $collation $notnull $default";
@@ -192,11 +192,11 @@ if (!empty($options['collation'])) {
$default = !is_null($column->default) ? "DEFAULT '$column->default'" : '';
if ($rowformat != '') {
$sql = "ALTER TABLE $table->name $rowformat";
$sql = "ALTER TABLE `$table->name` $rowformat";
$DB->change_database_structure($sql);
}
$sql = "ALTER TABLE $table->name
$sql = "ALTER TABLE `$table->name`
MODIFY COLUMN $column->field $column->type
CHARACTER SET $charset
COLLATE $collation $notnull $default";
@@ -315,8 +315,8 @@ function mysql_set_row_format($tablename, $charset, $collation, $engine) {
if ($rs->row_format == 'Compact' || $rs->row_format == 'Redundant') {
$rowformat = $DB->get_row_format_sql($engine, $collation);
// Try to convert to compressed format and then try updating the collation again.
$DB->change_database_structure("ALTER TABLE $tablename $rowformat");
$DB->change_database_structure("ALTER TABLE $tablename CONVERT TO CHARACTER SET $charset COLLATE $collation");
$DB->change_database_structure("ALTER TABLE `$tablename` $rowformat");
$DB->change_database_structure("ALTER TABLE `$tablename` CONVERT TO CHARACTER SET $charset COLLATE $collation");
} else {
// Row format may not be the problem. Can not diagnose problem. Send fail reply.
return false;
+2 -2
View File
@@ -158,7 +158,7 @@ if (!empty($options['info'])) {
}
foreach ($fixtables as $table) {
$DB->change_database_structure("ALTER TABLE {$prefix}$table ROW_FORMAT=Compressed");
$DB->change_database_structure("ALTER TABLE `{$prefix}$table` ROW_FORMAT=Compressed");
echo str_pad($prefix . $table, 32, ' ', STR_PAD_RIGHT) . " ... Compressed\n";
}
@@ -189,7 +189,7 @@ if (!empty($options['info'])) {
echo "SET GLOBAL innodb_file_per_table=1;\n";
echo "SET GLOBAL innodb_file_format=Barracuda;\n";
foreach ($fixtables as $table) {
echo "ALTER TABLE {$prefix}$table ROW_FORMAT=Compressed;\n";
echo "ALTER TABLE `{$prefix}$table` ROW_FORMAT=Compressed;\n";
}
echo "\n";
exit(0);
+188 -1
View File
@@ -3447,7 +3447,7 @@
</PHP_EXTENSION>
<PHP_EXTENSION name="hash" level="required"/>
<PHP_EXTENSION name="fileinfo" level="required"/>
<PHP_EXTENSION name="sodium" level="required"/>
<PHP_EXTENSION name="sodium" level="optional"/>
</PHP_EXTENSIONS>
<PHP_SETTINGS>
<PHP_SETTING name="memory_limit" value="96M" level="required">
@@ -3527,6 +3527,193 @@
<ON_CHECK message="sixtyfourbitswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="4.0" requires="3.6">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="10.2.29" />
<VENDOR name="mysql" version="5.7" />
<VENDOR name="postgres" version="10" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="oracle" version="11.2" />
</DATABASE>
<PHP version="7.3.0" level="required">
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<ON_CHECK message="pcreunicodewarning" />
</FEEDBACK>
</PCREUNICODE>
<PHP_EXTENSIONS>
<PHP_EXTENSION name="iconv" level="required">
<FEEDBACK>
<ON_ERROR message="iconvrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="mbstring" level="required">
<FEEDBACK>
<ON_ERROR message="mbstringrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="curl" level="required">
<FEEDBACK>
<ON_ERROR message="curlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="openssl" level="required">
<FEEDBACK>
<ON_ERROR message="opensslrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="tokenizer" level="optional">
<FEEDBACK>
<ON_CHECK message="tokenizerrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlrpc" level="optional">
<FEEDBACK>
<ON_CHECK message="xmlrpcrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="soap" level="optional">
<FEEDBACK>
<ON_CHECK message="soaprecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="ctype" level="required">
<FEEDBACK>
<ON_ERROR message="ctyperequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zip" level="required">
<FEEDBACK>
<ON_ERROR message="ziprequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zlib" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="gd" level="required">
<FEEDBACK>
<ON_ERROR message="gdrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="simplexml" level="required">
<FEEDBACK>
<ON_ERROR message="simplexmlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="spl" level="required">
<FEEDBACK>
<ON_ERROR message="splrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="pcre" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="dom" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xml" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlreader" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="intl" level="required">
<FEEDBACK>
<ON_ERROR message="intlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="json" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="hash" level="required"/>
<PHP_EXTENSION name="fileinfo" level="required"/>
<PHP_EXTENSION name="sodium" level="optional"/>
<PHP_EXTENSION name="exif" level="optional"/>
</PHP_EXTENSIONS>
<PHP_SETTINGS>
<PHP_SETTING name="memory_limit" value="96M" level="required">
<FEEDBACK>
<ON_ERROR message="settingmemorylimit" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="file_uploads" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="settingfileuploads" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="opcache.enable" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="opcacherecommended" />
</FEEDBACK>
</PHP_SETTING>
</PHP_SETTINGS>
<CUSTOM_CHECKS>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbstorageengine" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
<FEEDBACK>
<ON_ERROR message="quizattemptsupgradedmessage" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
<FEEDBACK>
<ON_CHECK message="slashargumentswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
<FEEDBACK>
<ON_CHECK message="unsupporteddbtablerowformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
<FEEDBACK>
<ON_CHECK message="unoconvwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
<FEEDBACK>
<ON_CHECK message="libcurlwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfileformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfilepertable" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddblargeprefix" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
<FEEDBACK>
<ON_CHECK message="ishttpswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
<FEEDBACK>
<ON_CHECK message="incompleteunicodesupport" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
<FEEDBACK>
<ON_CHECK message="sixtyfourbitswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+5 -4
View File
@@ -45,7 +45,7 @@ list($options, $unrecognised) = cli_get_params(
array(
'stop-on-failure' => 0,
'verbose' => false,
'replace' => false,
'replace' => '',
'help' => false,
'tags' => '',
'profile' => '',
@@ -70,7 +70,7 @@ $help = "
Behat utilities to run behat tests in parallel
Usage:
php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace] [--fromrun=value --torun=value] [--help]
php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace=\"{run}\"] [--fromrun=value --torun=value] [--help]
Options:
--BEHAT_OPTION Any combination of behat option specified in http://behat.readthedocs.org/en/v2.5/guides/6.cli.html
@@ -144,9 +144,10 @@ $extraopts = $unrecognised;
if ($options['profile']) {
$profile = $options['profile'];
// If profile passed is not set, then exit.
// If profile passed is not set, then exit (note we skip if the 'replace' option is found within the 'profile' value).
if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile]) &&
!(isset($options['replace']) && (strpos($options['profile'], $options['replace']) >= 0 ))) {
!($options['replace'] && (strpos($profile, (string) $options['replace']) !== false))) {
echo "Invalid profile passed: " . $profile . PHP_EOL;
exit(1);
}
@@ -21,5 +21,5 @@ Feature: Transform date time string arguments
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Test assignment name"
And I should see "##yesterday##l, j F Y##"
And I should see "##yesterday##%A, %d %B %Y##"
And I log out
@@ -48,7 +48,7 @@ Feature: Verify that all form fields values can be get and set
And the "Unenrol users" select box should not contain "Baker"
And the "Unenrol users" select box should not contain "President, Baker"
And I am on "Course 1" course homepage with editing mode on
And I follow "Test this one"
And I am on the "Test this one" "wiki activity" page
And I press "Create page"
# Text (textarea & editor) & Select (multi-select) - Checking "I set the following fields to these values".
When I set the following fields to these values:
@@ -61,8 +61,7 @@ Feature: Verify that all form fields values can be get and set
# Checkbox - Checking "I set the field" and "The field matches value" ticked.
And I set the field "Force format" to "1"
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I am on the "Test this one" "wiki activity" page
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
And the field "Force format" matches value "1"
@@ -70,8 +69,7 @@ Feature: Verify that all form fields values can be get and set
# Checkbox - Checking "I set the field" and "The field matches value" unticked.
And I set the field "Force format" to ""
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I am on the "Test this one" "wiki activity" page
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
And the field "Force format" matches value ""
@@ -80,8 +78,7 @@ Feature: Verify that all form fields values can be get and set
And I set the following fields to these values:
| Force format | 1 |
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I am on the "Test this one" "wiki activity" page
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
And the following fields match these values:
@@ -92,8 +89,7 @@ Feature: Verify that all form fields values can be get and set
And I set the following fields to these values:
| Force format | |
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I am on the "Test this one" "wiki activity" page
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
And the following fields match these values:
@@ -115,20 +111,21 @@ Feature: Verify that all form fields values can be get and set
| Wiki name | Test this one baby |
| Default format | HTML |
And I press "Cancel"
And I am on "Course 1" course homepage
# Radio - Checking "I set the field" and "the field matches value".
And I add a "Choice" to section "1" and I fill the form with:
| Choice name | Test choice name |
| Description | Test choice description |
| Allow choice to be updated | Yes |
| Option 1 | one |
| Option 2 | two |
| Option 3 | three |
And I follow "Test choice name"
And I set the field "one" to "1"
And the following "activity" exists:
| activity | choice |
| course | C1 |
| idnumber | choice1 |
| intro | Test choice description |
| name | Test choice name |
| option | Option 1, Option 2, Option 3 |
| section | 1 |
| allowupdate | 1 |
And I am on the "Test choice name" "choice activity" page
And I set the field "Option 1" to "1"
And I press "Save my choice"
And the field "one" matches value "1"
And the field "two" matches value ""
And the field "Option 1" matches value "1"
And the field "Option 2" matches value ""
# Check if field xpath set/match works.
And I am on "Course 1" course homepage
And I navigate to "Edit settings" in current page administration
@@ -174,31 +171,23 @@ Feature: Verify that all form fields values can be get and set
And I press "Cancel"
# Advanced checkbox requires real browser to allow uncheck to work. MDL-58681. MDL-55386.
# Advanced checkbox - Checking "I set the field" and "The field matches value" ticked.
And I am on "Course 1" course homepage
And I follow "Test this one"
And I navigate to "Edit settings" in current page administration
And I am on the "Test choice name" "choice activity editing" page
And I set the field "Display description on course page" to "1"
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I navigate to "Edit settings" in current page administration
And I am on the "Test choice name" "choice activity editing" page
And the field "Display description on course page" matches value "1"
And the field "Display description on course page" does not match value ""
# Advanced checkbox - Checking "I set the field" and "The field matches value" unticked.
And I set the field "Display description on course page" to ""
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I navigate to "Edit settings" in current page administration
And I am on the "Test choice name" "choice activity editing" page
And the field "Display description on course page" matches value ""
And the field "Display description on course page" does not match value "1"
# Advanced checkbox - Checking "I set the following fields to these values:" and "The following fields match these values" ticked.
And I set the following fields to these values:
| Display description on course page | 1 |
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I navigate to "Edit settings" in current page administration
And I am on the "Test choice name" "choice activity editing" page
And the following fields match these values:
| Display description on course page | 1 |
And the following fields do not match these values:
@@ -207,9 +196,7 @@ Feature: Verify that all form fields values can be get and set
And I set the following fields to these values:
| Display description on course page | |
And I press "Save and return to course"
And I should see "Test this one"
And I follow "Test this one"
And I navigate to "Edit settings" in current page administration
And I am on the "Test choice name" "choice activity editing" page
And the following fields match these values:
| Display description on course page | |
And the following fields do not match these values:
@@ -0,0 +1,85 @@
@tool @tool_behat
Feature: Use core page resolvers for the I am on the page steps
In order to write tests correctly
As a developer
I need to have steps which take me straight to a page
Scenario Outline: When I am on an instance
Given the following "categories" exist:
| name | category | idnumber |
| Cat 1 | 0 | CAT1 |
And the following "course" exists:
| fullname | Economics 101 |
| shortname | ECON101 |
| idnumber | 2021-econ101 |
And the following "activity" exists:
| course | ECON101 |
| activity | forum |
| name | Fundamentals of Economics |
| idnumber | fundamentalsofeconomics |
And I log in as "admin"
When I am on the <identifier> <type> page
Then I should see "<shouldsee>"
Examples:
| description | identifier | type | shouldsee |
| Course Category idnumber | CAT1 | category | Add a new course |
| Course Category name | "Cat 1" | Category | Add a new course |
| Course Full name | "Economics 101" | course | Fundamentals of Economics |
| Course Short name | ECON101 | COURSE | Fundamentals of Economics |
| Course idnumber | "2021-econ101" | Course | Fundamentals of Economics |
| Forum idnumber | fundamentalsofeconomics | Activity | Add a new discussion |
| Generic activity editing | fundamentalsofeconomics | "Activity editing" | Updating: Forum |
| Forum name | "Fundamentals of Economics" | "Forum activity" | Add a new discussion |
| Forum name editing | "Fundamentals of Economics" | "Forum activity editing" | Updating: Forum |
| Forum name permissions | "Fundamentals of Economics" | "Forum activity permissions" | Permissions in Forum: Fun |
| Forum name roles | "Fundamentals of Economics" | "Forum activity roles" | Assign roles in Forum: Fun |
Scenario Outline: When I am on an instance logged in as
Given the following "categories" exist:
| name | category | idnumber |
| Cat 1 | 0 | CAT1 |
And the following "course" exists:
| fullname | Economics 101 |
| shortname | ECON101 |
| idnumber | 2021-econ101 |
And the following "activity" exists:
| course | ECON101 |
| activity | forum |
| name | Fundamentals of Economics |
| idnumber | fundamentalsofeconomics |
When I am on the <identifier> <type> page logged in as admin
Then I should see "<shouldsee>"
Examples:
| description | identifier | type | shouldsee |
| Course Category idnumber | CAT1 | category | Add a new course |
| Course Category name | "Cat 1" | Category | Add a new course |
| Course Full name | "Economics 101" | course | Fundamentals of Economics |
| Course Short name | ECON101 | COURSE | Fundamentals of Economics |
| Course idnumber | "2021-econ101" | Course | Fundamentals of Economics |
| Forum idnumber | fundamentalsofeconomics | Activity | Add a new discussion |
| Generic activity editing | fundamentalsofeconomics | "Activity editing" | Updating: Forum |
| Forum name | "Fundamentals of Economics" | "Forum activity" | Add a new discussion |
| Forum name editing | "Fundamentals of Economics" | "Forum activity editing" | Updating: Forum |
| Forum name permissions | "Fundamentals of Economics" | "Forum activity permissions" | Permissions in Forum: Fun |
| Forum name roles | "Fundamentals of Economics" | "Forum activity roles" | Assign roles in Forum: Fun |
Scenario Outline: When I am on a named page
Given I log in as "admin"
When I am on the <identifier> page
Then I should see "<shouldsee>"
Examples:
| description | identifier | shouldsee |
| Admin page | "Admin notifications" | Check for available updates |
| Home page | Homepage | Recently accessed courses |
Scenario Outline: When I am on a named page logged in as
When I am on the <identifier> page logged in as admin
Then I should see "<shouldsee>"
Examples:
| description | identifier | shouldsee |
| Admin page | "Admin notifications" | Check for available updates |
| Home page | Homepage | Recently accessed courses |
@@ -189,7 +189,12 @@ class cohort_role_assignments_table extends table_sql {
FROM {tool_cohortroles} uca
JOIN {user} u ON u.id = uca.userid
JOIN {cohort} c ON c.id = uca.cohortid";
$params = array();
// Check if any additional filtering is required.
[$sqlwhere, $params] = $this->get_sql_where();
if ($sqlwhere) {
$sql .= " WHERE {$sqlwhere}";
}
// Add order by if needed.
if (!$count && $sqlsort = $this->get_sql_sort()) {
+2 -2
View File
@@ -86,8 +86,8 @@ if ($removeid) {
$title = get_string('existingcohortroles', 'tool_cohortroles');
echo $output->heading($title);
$url = new moodle_url('/admin/tool/cohortroles/index.php');
$table = new tool_cohortroles\output\cohort_role_assignments_table(uniqid(), $url);
$table = new tool_cohortroles\output\cohort_role_assignments_table('cohort-role-assignments', $pageurl);
echo $table->out(50, true);
echo $output->spacer();
@@ -37,67 +37,68 @@
This template does not have an example context because it includes ajax functionality.
}}
<div data-region="user-competency-full-info" data-node="user-competency" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}" data-region-id="{{uniqid}}">
<div data-region="competency-summary">
{{#competency}}
{{> tool_lp/competency_summary }}
{{/competency}}
<dl>
{{#usercompetency}}
<dt>{{#str}}reviewstatus, tool_lp{{/str}}</dt>
<dd data-region="user-competency-status">{{statusname}}
<div data-region="competency-summary">
{{#competency}}
{{> tool_lp/competency_summary }}
{{/competency}}
<dl>
{{#usercompetency}}
<dt>{{#str}}reviewstatus, tool_lp{{/str}}</dt>
<dd data-region="user-competency-status">{{statusname}}
{{#isstatusinreview}}
- {{reviewer.fullname}}
{{/isstatusinreview}}
{{#isstatusinreview}}
- {{reviewer.fullname}}
{{/isstatusinreview}}
{{#isrequestreviewallowed}}<button class="btn btn-secondary" data-action="request-review">{{#str}}requestreview, tool_lp{{/str}}</button>{{/isrequestreviewallowed}}
{{#iscancelreviewrequestallowed}}<button class="btn btn-secondary" data-action="cancel-review-request">{{#str}}cancelreviewrequest, tool_lp{{/str}}</button>{{/iscancelreviewrequestallowed}}
{{#isstartreviewallowed}}<button class="btn btn-secondary" data-action="start-review">{{#str}}startreview, tool_lp{{/str}}</button>{{/isstartreviewallowed}}
{{#isstopreviewallowed}}<button class="btn btn-secondary" data-action="stop-review">{{#str}}stopreview, tool_lp{{/str}}</button>{{/isstopreviewallowed}}
</dd>
<dt>{{#str}}proficient, tool_lp{{/str}}</dt>
<dd>
<span class="badge {{#proficiency}}badge-success{{/proficiency}}{{^proficiency}}badge-danger{{/proficiency}} float-left">
{{proficiencyname}}
</span>
</dd>
<dt>{{#str}}rating, tool_lp{{/str}}</dt>
<dd>{{gradename}}
{{#cangrade}}
<button class="btn btn-secondary" id="rate_{{uniqid}}">{{#str}}rate, tool_lp{{/str}}</button>
{{/cangrade}}
</dd>
{{#js}}
require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info', 'tool_lp/user_competency_workflow'], function($, mod, info, UserCompWorkflow) {
{{#isrequestreviewallowed}}<button class="btn btn-secondary" data-action="request-review">{{#str}}requestreview, tool_lp{{/str}}</button>{{/isrequestreviewallowed}}
{{#iscancelreviewrequestallowed}}<button class="btn btn-secondary" data-action="cancel-review-request">{{#str}}cancelreviewrequest, tool_lp{{/str}}</button>{{/iscancelreviewrequestallowed}}
{{#isstartreviewallowed}}<button class="btn btn-secondary" data-action="start-review">{{#str}}startreview, tool_lp{{/str}}</button>{{/isstartreviewallowed}}
{{#isstopreviewallowed}}<button class="btn btn-secondary" data-action="stop-review">{{#str}}stopreview, tool_lp{{/str}}</button>{{/isstopreviewallowed}}
</dd>
<dt>{{#str}}proficient, tool_lp{{/str}}</dt>
<dd>
<span class="badge {{#proficiency}}badge-success{{/proficiency}}{{^proficiency}}badge-danger{{/proficiency}} float-left">
{{proficiencyname}}
</span>
</dd>
<dt>{{#str}}rating, tool_lp{{/str}}</dt>
<dd>{{gradename}}
{{#cangrade}}
<button class="btn btn-secondary" id="rate_{{uniqid}}">{{#str}}rate, tool_lp{{/str}}</button>
{{/cangrade}}
</dd>
{{#js}}
require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info', 'tool_lp/user_competency_workflow'], function($, mod, info, UserCompWorkflow) {
var competencyElement = $('[data-region-id="{{uniqid}}"]');
var infoReloader = new info(competencyElement, '{{competency.competency.id}}', '{{user.id}}');
var competencyElement = $('[data-region-id="{{uniqid}}"]');
var infoReloader = new info(competencyElement, '{{competency.competency.id}}', '{{user.id}}');
var ucw = new UserCompWorkflow();
ucw.registerEvents('[data-region="user-competency-status"]');
ucw.on('status-changed', infoReloader.reload.bind(infoReloader));
ucw.on('error-occured', infoReloader.reload.bind(infoReloader));
var ucw = new UserCompWorkflow();
ucw.registerEvents('[data-region="user-competency-status"]');
ucw.on('status-changed', infoReloader.reload.bind(infoReloader));
ucw.on('error-occured', infoReloader.reload.bind(infoReloader));
var inlineGrader = new mod('#rate_{{uniqid}}', '{{competency.scaleid}}', '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
inlineGrader.on('competencyupdated', infoReloader.reload.bind(infoReloader));
});
{{/js}}
{{/usercompetency}}
</dl>
{{#commentarea}}
{{#canpostorhascomments}}
{{>tool_lp/comment_area}}
{{/canpostorhascomments}}
{{/commentarea}}
<dl data-region="evidence-listing">
<dt>{{#str}}evidence, tool_lp{{/str}}</dt>
<dd>
{{#evidence}}
{{> tool_lp/evidence_summary }}
{{/evidence}}
{{^evidence}}
<p>{{#str}}noevidence, tool_lp{{/str}}</p>
{{/evidence}}
</dd>
</dl>
var inlineGrader = new mod('#rate_{{uniqid}}', '{{competency.scaleid}}', '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
inlineGrader.on('competencyupdated', infoReloader.reload.bind(infoReloader));
});
{{/js}}
{{/usercompetency}}
</dl>
{{#commentarea}}
{{#canpostorhascomments}}
{{>tool_lp/comment_area}}
{{/canpostorhascomments}}
{{/commentarea}}
<dl data-region="evidence-listing">
<dt>{{#str}}evidence, tool_lp{{/str}}</dt>
<dd>
{{#evidence}}
{{> tool_lp/evidence_summary }}
{{/evidence}}
{{^evidence}}
<p>{{#str}}noevidence, tool_lp{{/str}}</p>
{{/evidence}}
</dd>
</dl>
</div>
</div>
@@ -24,9 +24,7 @@ Feature: See the competencies for an activity on the course competencies page.
| activity | name | intro | course | idnumber | completion | completionview |
| page | PageName1 | PageDesc1 | C1 | PAGE1 | 1 | 1 |
| page | PageName2 | PageDesc2 | C1 | PAGE2 | 1 | 1 |
And I log in as "admin"
And I am on site homepage
And I follow "Course 1"
And I am on the "Course 1" course page logged in as admin
And I follow "Competencies"
And I press "Add competencies to course"
And "Competency picker" "dialogue" should be visible
@@ -36,9 +34,7 @@ Feature: See the competencies for an activity on the course competencies page.
And "Competency picker" "dialogue" should be visible
And I select "Test-Comp2" of the competency tree
And I click on "Add" "button" in the "Competency picker" "dialogue"
And I am on "Course 1" course homepage
And I follow "PageName1"
And I navigate to "Edit settings" in current page administration
And I am on the PageName1 "page activity editing" page
And I follow "Expand all"
And I set the field "Course competencies" to "Test-Comp1"
And I press "Save and return to course"
@@ -46,10 +42,8 @@ Feature: See the competencies for an activity on the course competencies page.
@javascript
Scenario: Go to the competency course competencies page.
When I log in as "student1"
And I am on site homepage
And I follow "Course 1"
And I follow "Competencies"
Given I am on the "Course 1" course page logged in as student1
When I follow "Competencies"
Then I should see "Test-Comp1"
And I should see "Test-Comp2"
And I set the field "Filter competencies by resource or activity" to "PageName1"
@@ -64,12 +58,5 @@ Feature: See the competencies for an activity on the course competencies page.
@javascript
Scenario: None course competencies page.
When I log in as "student1"
And I am on site homepage
And I follow "Course 1"
And I follow "PageName1"
Then I should see "Test page content"
And I am on site homepage
And I follow "Course 1"
And I follow "PageName1"
When I am on the PageName1 "page activity" page logged in as student1
Then I should see "Test page content"
@@ -42,9 +42,7 @@ Feature: Backup user data
| Feedback for the response 'True'. | So you think it is true |
| Feedback for the response 'False'. | So you think it is false |
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Quiz 1"
When I am on the "Quiz 1" "quiz activity" page logged in as student1
And I press "Attempt quiz now"
And I click on "True" "radio" in the "First question" "question"
And I click on "False" "radio" in the "Second question" "question"
@@ -132,12 +132,12 @@ Feature: Upload users
# Boost check.
And I log in as "jonest"
And I am on "Maths" course homepage
And "Turn editing on" "button" should not exist
And "Calendar" "link" should be visible
And I log out
# Classic check.
And I log in as "reznor"
And I am on "Maths" course homepage
And "Turn editing on" "button" should exist
And "Calendar" "link" should not be visible
@javascript
Scenario: Upload users setting their user theme when allowuserthemes is false
+1 -11
View File
@@ -438,17 +438,7 @@ class manager {
$filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
// Force download.
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . 'GMT');
header('Pragma: no-cache');
header('Accept-Ranges: none');
header('Content-disposition: attachment; filename=' . $filename);
header('Content-length: ' . strlen($exportstring));
header('Content-type: text/calendar; charset=utf-8');
echo $exportstring;
die;
send_file($exportstring, $filename, 0, 0, true, true);
}
/**
+5 -1
View File
@@ -68,7 +68,9 @@ if ($dataformat) {
$downloadusers = new ArrayObject($SESSION->bulk_users);
$iterator = $downloadusers->getIterator();
\core\dataformat::download_data($filename, $dataformat, $fields, $iterator, function($userid) use ($extrafields, $fields) {
\core\dataformat::download_data($filename, $dataformat, $fields, $iterator, function($userid, $supportshtml)
use ($extrafields, $fields) {
global $DB;
if (!$user = $DB->get_record('user', array('id' => $userid))) {
@@ -86,6 +88,8 @@ if ($dataformat) {
// We only take the text.
if (is_array($user->$field)) {
$userprofiledata[$field] = reset($user->$field);
} else if ($supportshtml) {
$userprofiledata[$field] = s($user->$field);
} else {
$userprofiledata[$field] = $user->$field;
}
+27
View File
@@ -178,6 +178,33 @@ class prediction {
\core\event\prediction_action_started::create($eventdata)->trigger();
}
/**
* Get the executed actions.
*
* Actions could be filtered by actionname.
*
* @param array $actionnamefilter Limit the results obtained to this list of action names.
* @param int $userid the user id. Current user by default.
* @return array of actions.
*/
public function get_executed_actions(array $actionnamefilter = null, int $userid = 0): array {
global $USER, $DB;
$conditions[] = "predictionid = :predictionid";
$params['predictionid'] = $this->get_prediction_data()->id;
if (!$userid) {
$userid = $USER->id;
}
$conditions[] = "userid = :userid";
$params['userid'] = $userid;
if ($actionnamefilter) {
list($actionsql, $actionparams) = $DB->get_in_or_equal($actionnamefilter, SQL_PARAMS_NAMED);
$conditions[] = "actionname $actionsql";
$params = $params + $actionparams;
}
return $DB->get_records_select('analytics_prediction_actions', implode(' AND ', $conditions), $params);
}
/**
* format_calculations
*
@@ -112,6 +112,86 @@ class analytics_prediction_actions_testcase extends advanced_testcase {
$this->assertEquals(2, $DB->count_records('analytics_prediction_actions'));
}
/**
* Data provider for test_get_executed_actions.
*
* @return array
*/
public function execute_actions_provider(): array {
return [
'Empty actions with no filter' => [
[],
[],
0
],
'Empty actions with filter' => [
[],
[\core_analytics\prediction::ACTION_FIXED],
0
],
'Multiple actions with no filter' => [
[
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
],
[],
3
],
'Multiple actions applying filter' => [
[
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
],
[\core_analytics\prediction::ACTION_FIXED],
2
],
'Multiple actions not applying filter' => [
[
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
],
[\core_analytics\prediction::ACTION_NOT_APPLICABLE],
0
],
'Multiple actions with multiple filter' => [
[
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
],
[\core_analytics\prediction::ACTION_FIXED, \core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED],
3
],
];
}
/**
* Tests for get_executed_actions() function.
*
* @dataProvider execute_actions_provider
* @param array $actionstoexecute An array of actions to execute
* @param array $actionnamefilter Actions to filter
* @param int $returned Number of actions returned
*
* @covers \core_analytics\prediction::get_executed_actions
*/
public function test_get_executed_actions(array $actionstoexecute, array $actionnamefilter, int $returned) {
$this->setUser($this->teacher2);
list($ignored, $predictions) = $this->model->get_predictions($this->context, true);
$prediction = reset($predictions);
$target = $this->model->get_target();
foreach($actionstoexecute as $action) {
$prediction->action_executed($action, $target);
}
$filteredactions = $prediction->get_executed_actions($actionnamefilter);
$this->assertCount($returned, $filteredactions);
}
/**
* test_get_predictions
*/
+5
View File
@@ -1,6 +1,11 @@
This files describes API changes in analytics sub system,
information provided here is intended especially for developers.
=== 3.9.7 ===
* A new function get_executed_actions() has been added to \core_analytics\prediction class
to get all (or filtered by action name) executed actions of a prediction
=== 3.8 ===
* "Time-splitting method" have been replaced by "Analysis interval" for the language strings that are
+1 -1
View File
@@ -1,3 +1,3 @@
Description of phpCAS 1.3.8 library import
* downloaded from http://downloads.jasig.org/cas-clients/php/current/
* downloaded source code from https://github.com/apereo/phpCAS/releases
+1 -1
View File
@@ -4,7 +4,7 @@
<location>CAS</location>
<name>CAS</name>
<license>Apache</license>
<version>1.3.7</version>
<version>1.3.8</version>
<licenseversion>2.0</licenseversion>
</library>
</libraries>
+1 -1
View File
@@ -45,7 +45,7 @@ $string['auth_dbname'] = 'Name of the database itself. Leave empty if using an O
$string['auth_dbname_key'] = 'DB name';
$string['auth_dbpass'] = 'Password matching the above username';
$string['auth_dbpass_key'] = 'Password';
$string['auth_dbpasstype'] = '<p>Specify the format that the password field is using.</p> <p>Use \'internal\' if you want the external database to manage usernames and email addresses, but Moodle to manage passwords. If you use \'internal\', you <i>must</i> provide a populated email address field in the external database, and you must execute both admin/cron.php and auth/db/cli/sync_users.php regularly. Moodle will send an email to new users with a temporary password.</p>';
$string['auth_dbpasstype'] = '<p>Specify the format that the password field is using.</p> <p>Use \'internal\' if you want the external database to manage usernames and email addresses, but Moodle to manage passwords. If you use \'internal\', you must provide a populated email address field in the external database, and you must enable the \auth_db\task\sync_users scheduled task. Moodle will send an email to new users with a temporary password.</p>';
$string['auth_dbpasstype_key'] = 'Password format';
$string['auth_dbreviveduser'] = 'Revived user {$a->name} id {$a->id}';
$string['auth_dbrevivedusererror'] = 'Error reviving user {$a}';
+1 -1
View File
@@ -75,7 +75,7 @@ $string['auth_ldap_passwdexpire_settings'] = 'LDAP password expiry settings';
$string['auth_ldap_preventpassindb'] = 'Select yes to prevent passwords from being stored in Moodle\'s DB.';
$string['auth_ldap_preventpassindb_key'] = 'Prevent password caching';
$string['auth_ldap_rolecontext'] = '{$a->localname} context';
$string['auth_ldap_rolecontext_help'] = 'LDAP context used to select for <i>{$a->localname}</i> mapping. Separate multiple groups with \';\'. Usually something like "cn={$a->shortname},ou=staff,o=myorg".';
$string['auth_ldap_rolecontext_help'] = 'LDAP context used to select for <i>{$a->localname}</i> mapping. Separate multiple groups with \';\'. Usually something like "cn={$a->shortname},ou=first-ou-with-role-groups,o=myorg; cn={$a->shortname},ou=second-ou-with-role-groups,o=myorg".';
$string['auth_ldap_search_sub'] = 'Search users from subcontexts.';
$string['auth_ldap_search_sub_key'] = 'Search subcontexts';
$string['auth_ldap_server_settings'] = 'LDAP server settings';
+1 -3
View File
@@ -716,9 +716,7 @@ class auth_plugin_mnet extends auth_plugin_base {
foreach($superArray as $subArray) {
$subArray = array_values($subArray);
$instring = "('".implode("', '",$subArray)."')";
$query = "select id, session_id, username from {mnet_session} where username in $instring";
$results = $DB->get_records_sql($query);
$results = $DB->get_records_list('mnet_session', 'username', $subArray, '', 'id, session_id, username');
if ($results == false) {
// We seem to have a username that breaks our query:
+15 -4
View File
@@ -113,11 +113,22 @@ class helper {
*/
private static function unserializesession($serializedstring) {
$variables = array();
$a = preg_split("/(\w+)\|/", $serializedstring, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$counta = count($a);
for ($i = 0; $i < $counta; $i = $i + 2) {
$variables[$a[$i]] = unserialize($a[$i + 1]);
$index = 0;
// Find next delimiter after current index. It's key being the characters between those points.
while ($delimiterpos = strpos($serializedstring, '|', $index)) {
$key = substr($serializedstring, $index, $delimiterpos - $index);
// Start unserializing immediately after the delimiter. PHP will read as much valid data as possible.
$value = unserialize(substr($serializedstring, $delimiterpos + 1),
['allowed_classes' => ['stdClass']]);
$variables[$key] = $value;
// Advance index beyond the length of the previously captured serialized value.
$index = $delimiterpos + 1 + strlen(serialize($value));
}
return $variables;
}
}
+6 -6
View File
@@ -290,9 +290,9 @@ abstract class info {
*/
protected function warn_about_invalid_availability(\coding_exception $e) {
$name = $this->get_thing_name();
// If it occurs while building modinfo based on somebody calling $cm->name,
// we can't get $cm->name, and this line will cause a warning.
$htmlname = @$this->format_info($name, $this->course);
$htmlname = $this->format_info($name, $this->course);
// Because we call format_info here, likely in the middle of building dynamic data for the
// activity, there could be a chance that the name might not be available.
if ($htmlname === '') {
// So instead use the numbers (cmid) from the tag.
$htmlname = preg_replace('~[^0-9]~', '', $name);
@@ -739,11 +739,11 @@ abstract class info {
$info = preg_replace_callback('~<AVAILABILITY_CMNAME_([0-9]+)/>~',
function($matches) use($modinfo, $context) {
$cm = $modinfo->get_cm($matches[1]);
if ($cm->has_view() and $cm->uservisible) {
if ($cm->has_view() and $cm->get_user_visible()) {
// Help student by providing a link to the module which is preventing availability.
return \html_writer::link($cm->url, format_string($cm->name, true, array('context' => $context)));
return \html_writer::link($cm->get_url(), format_string($cm->get_name(), true, ['context' => $context]));
} else {
return format_string($cm->name, true, array('context' => $context));
return format_string($cm->get_name(), true, ['context' => $context]);
}
}, $info);
@@ -48,10 +48,10 @@ Feature: Confirm that conditions on completion no longer cause a bug
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I set the field with xpath "//div[contains(concat(' ', normalize-space(@class), ' '), ' availability-item ')][preceding-sibling::div]//select[@name='cm']" to "Page2"
And I press "Save and return to course"
And I should see "Not available unless:" in the ".activity.glossary" "css_element"
Then I should see "Not available unless:" in the ".activity.glossary" "css_element"
And I should see "The activity Page1 is marked complete" in the ".activity.glossary" "css_element"
And I should see "The activity Page2 is marked complete" in the ".activity.glossary" "css_element"
And I follow "TestGlossary"
# Behat will automatically check there is no error on this page.
Then I should see "TestGlossary"
And I am on the TestGlossary "glossary activity" page
And I should see "TestGlossary"
@@ -86,13 +86,12 @@ Feature: availability_grade
And I set the field "Minimum grade percentage (inclusive)" to "10"
And I press "Save and return to course"
And I log out
# Log in as student without a grade yet.
When I log out
And I log in as "student1"
And I am on "Course 1" course homepage
When I am on the "A1" "assign activity" page logged in as student1
# Do the assignment.
And I follow "A1"
And I click on "Add submission" "button"
And I set the field "Online text" to "Q"
And I click on "Save changes" "button"
@@ -103,14 +102,12 @@ Feature: availability_grade
And I should not see "P3" in the "region-main" "region"
And I should not see "P4" in the "region-main" "region"
And I should see "A1" in the "region-main" "region"
And I log out
# Log back in as teacher.
When I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
When I am on the "A1" "assign activity" page logged in as teacher1
# Give the assignment 40%.
And I follow "A1"
And I navigate to "View all submissions" in current page administration
# Pick the grade link in the row that has s@example.com in it.
And I click on "Grade" "link" in the "s@example.com" "table_row"
@@ -118,11 +115,10 @@ Feature: availability_grade
And I click on "Save changes" "button"
And I press "OK"
And I click on "Edit settings" "link"
And I log out
# Log back in as student.
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I am on the "Course 1" course page logged in as student1
# Check pages are visible.
Then I should see "P2" in the "region-main" "region"
@@ -95,8 +95,7 @@ Feature: availability_profile
And I click on "Save and return to course" "button"
# Edit it again and check the setting still works.
When I follow "P1"
And I navigate to "Edit settings" in current page administration
When I am on the P1 "page activity editing" page
And I expand all fieldsets
Then the field "User profile field" matches value "Super field"
And the field "Value to compare against" matches value "Bananaman"
+29
View File
@@ -511,4 +511,33 @@ class info_testcase extends advanced_testcase {
sort($result);
$this->assertEquals($expected, $result);
}
/**
* Tests the info_module class when involved in a recursive call to $cm->name.
*/
public function test_info_recursive_name_call() {
global $DB;
$this->resetAfterTest();
// Create a course and page.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$page1 = $generator->create_module('page', ['course' => $course->id, 'name' => 'Page1']);
// Set invalid availability.
$DB->set_field('course_modules', 'availability', 'not valid', ['id' => $page1->cmid]);
// Get the cm_info object.
$this->setAdminUser();
$modinfo = get_fast_modinfo($course);
$cm1 = $modinfo->get_cm($page1->cmid);
// At this point we will generate dynamic data for $cm1, which will cause the debugging
// call below.
$this->assertEquals('Page1', $cm1->name);
$this->assertDebuggingCalled('Error processing availability data for ' .
'&lsquo;Page1&rsquo;: Invalid availability text');
}
}
@@ -198,8 +198,11 @@ M.core_availability.form = {
// If the groupmode and grouping id aren't set, disable it.
var groupmode = Y.one('#id_groupmode');
var groupingid = Y.one('#id_groupingid');
if ((!groupmode || Number(groupmode.get('value')) === 0) &&
(!groupingid || Number(groupingid.get('value')) === 0)) {
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
if ((!groupmode || Number(groupmode.get('value')) === 0 || !groupavailability) &&
(!groupingid || Number(groupingid.get('value')) === 0 || !groupingavailability)) {
this.restrictByGroup.set('disabled', true);
return;
}
@@ -220,22 +223,28 @@ M.core_availability.form = {
e.preventDefault();
// Add the condition.
var groupmode = Y.one('#id_groupmode');
var groupingid = Y.one('#id_groupingid');
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
var newChild;
if (groupingid && Number(groupingid.get('value')) !== 0) {
if (groupingid && Number(groupingid.get('value')) !== 0 && groupingavailability) {
// Add a grouping restriction if one is specified.
newChild = new M.core_availability.Item(
{type: 'grouping', id: Number(groupingid.get('value'))}, true);
} else {
} else if (groupmode && groupavailability) {
// Otherwise just add a group restriction.
newChild = new M.core_availability.Item({type: 'group'}, true);
}
// Refresh HTML.
this.rootList.addChild(newChild);
this.update();
this.rootList.renumber();
this.rootList.updateHtml();
if (newChild !== null) {
this.rootList.addChild(newChild);
this.update();
this.rootList.renumber();
this.rootList.updateHtml();
}
}
};
File diff suppressed because one or more lines are too long
@@ -198,8 +198,11 @@ M.core_availability.form = {
// If the groupmode and grouping id aren't set, disable it.
var groupmode = Y.one('#id_groupmode');
var groupingid = Y.one('#id_groupingid');
if ((!groupmode || Number(groupmode.get('value')) === 0) &&
(!groupingid || Number(groupingid.get('value')) === 0)) {
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
if ((!groupmode || Number(groupmode.get('value')) === 0 || !groupavailability) &&
(!groupingid || Number(groupingid.get('value')) === 0 || !groupingavailability)) {
this.restrictByGroup.set('disabled', true);
return;
}
@@ -220,22 +223,28 @@ M.core_availability.form = {
e.preventDefault();
// Add the condition.
var groupmode = Y.one('#id_groupmode');
var groupingid = Y.one('#id_groupingid');
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
var newChild;
if (groupingid && Number(groupingid.get('value')) !== 0) {
if (groupingid && Number(groupingid.get('value')) !== 0 && groupingavailability) {
// Add a grouping restriction if one is specified.
newChild = new M.core_availability.Item(
{type: 'grouping', id: Number(groupingid.get('value'))}, true);
} else {
} else if (groupmode && groupavailability) {
// Otherwise just add a group restriction.
newChild = new M.core_availability.Item({type: 'group'}, true);
}
// Refresh HTML.
this.rootList.addChild(newChild);
this.update();
this.rootList.renumber();
this.rootList.updateHtml();
if (newChild !== null) {
this.rootList.addChild(newChild);
this.update();
this.rootList.renumber();
this.rootList.updateHtml();
}
}
};
+17 -8
View File
@@ -196,8 +196,11 @@ M.core_availability.form = {
// If the groupmode and grouping id aren't set, disable it.
var groupmode = Y.one('#id_groupmode');
var groupingid = Y.one('#id_groupingid');
if ((!groupmode || Number(groupmode.get('value')) === 0) &&
(!groupingid || Number(groupingid.get('value')) === 0)) {
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
if ((!groupmode || Number(groupmode.get('value')) === 0 || !groupavailability) &&
(!groupingid || Number(groupingid.get('value')) === 0 || !groupingavailability)) {
this.restrictByGroup.set('disabled', true);
return;
}
@@ -218,22 +221,28 @@ M.core_availability.form = {
e.preventDefault();
// Add the condition.
var groupmode = Y.one('#id_groupmode');
var groupingid = Y.one('#id_groupingid');
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
var newChild;
if (groupingid && Number(groupingid.get('value')) !== 0) {
if (groupingid && Number(groupingid.get('value')) !== 0 && groupingavailability) {
// Add a grouping restriction if one is specified.
newChild = new M.core_availability.Item(
{type: 'grouping', id: Number(groupingid.get('value'))}, true);
} else {
} else if (groupmode && groupavailability) {
// Otherwise just add a group restriction.
newChild = new M.core_availability.Item({type: 'group'}, true);
}
// Refresh HTML.
this.rootList.addChild(newChild);
this.update();
this.rootList.renumber();
this.rootList.updateHtml();
if (newChild !== null) {
this.rootList.addChild(newChild);
this.update();
this.rootList.renumber();
this.rootList.updateHtml();
}
}
};
+23 -2
View File
@@ -4046,9 +4046,30 @@ class restore_contentbankcontent_structure_step extends restore_structure_step {
$exists = $DB->record_exists('contentbank_content', $params);
if (!$exists) {
$params['configdata'] = $data->configdata;
$params['usercreated'] = $this->get_mappingid('user', $data->usercreated);
$params['usermodified'] = $this->get_mappingid('user', $data->usermodified);
$params['timemodified'] = time();
// Trying to map users. Users cannot always be mapped, e.g. when copying.
$params['usercreated'] = $this->get_mappingid('user', $data->usercreated);
if (!$params['usercreated']) {
// Leave the content creator unchanged when we are restoring the same site.
// Otherwise use current user id.
if ($this->task->is_samesite()) {
$params['usercreated'] = $data->usercreated;
} else {
$params['usercreated'] = $this->task->get_userid();
}
}
$params['usermodified'] = $this->get_mappingid('user', $data->usermodified);
if (!$params['usermodified']) {
// Leave the content modifier unchanged when we are restoring the same site.
// Otherwise use current user id.
if ($this->task->is_samesite()) {
$params['usermodified'] = $data->usermodified;
} else {
$params['usermodified'] = $this->task->get_userid();
}
}
$newitemid = $DB->insert_record('contentbank_content', $params);
$this->set_mapping('contentbank_content', $oldid, $newitemid, true);
}
+67
View File
@@ -278,6 +278,73 @@ class core_backup_automated_backup_testcase extends advanced_testcase {
$this->assertTrue($skipped);
$this->expectOutputRegex("/Skipping $course->fullname \(Not modified since previous backup\)/");
}
/**
* Test the task completes when coureid is missing.
*/
public function test_task_complete_when_courseid_is_missing() {
global $DB;
$admin = get_admin();
$classobject = $this->backupcronautomatedhelper->return_this();
// Create this backup course.
$backupcourse = new stdClass;
$backupcourse->courseid = $this->course->id;
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]);
// Create a backup task.
$method = new ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task');
$method->setAccessible(true); // Allow accessing of private method.
$method->invokeArgs($classobject, [$backupcourse, $admin]);
// Delete course for this test.
delete_course($this->course->id, false);
$task = core\task\manager::get_next_adhoc_task(time());
ob_start();
$task->execute();
$output = ob_get_clean();
$this->assertStringContainsString('Invalid course id: ' . $this->course->id . ', task aborted.', $output);
core\task\manager::adhoc_task_complete($task);
}
/**
* Test the task completes when backup course is missing.
*/
public function test_task_complete_when_backup_course_is_missing() {
global $DB;
$admin = get_admin();
$classobject = $this->backupcronautomatedhelper->return_this();
// Create this backup course.
$backupcourse = new stdClass;
$backupcourse->courseid = $this->course->id;
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]);
// Create a backup task.
$method = new ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task');
$method->setAccessible(true); // Allow accessing of private method.
$method->invokeArgs($classobject, [$backupcourse, $admin]);
// Delete backup course for this test.
$DB->delete_records('backup_courses', ['courseid' => $this->course->id]);
$task = core\task\manager::get_next_adhoc_task(time());
ob_start();
$task->execute();
$output = ob_get_clean();
$this->assertStringContainsString('Automated backup for course: ' . $this->course->fullname . ' encounters an error.',
$output);
core\task\manager::adhoc_task_complete($task);
}
}
/**
+3 -1
View File
@@ -116,7 +116,9 @@ class backup_ui_stage_initial extends backup_ui_stage {
if (isset($data->$name) && $data->$name != $setting->get_value()) {
$setting->set_value($data->$name);
$changes++;
} else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
} else if (!isset($data->$name) && $setting->get_value() &&
$setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX &&
$setting->get_status() !== backup_setting::LOCKED_BY_HIERARCHY) {
$setting->set_value(0);
$changes++;
}
+1 -1
View File
@@ -52,7 +52,7 @@ if (!is_null($action)) {
} else { // Revoked badge.
header("HTTP/1.0 410 Gone");
$assertion = array();
if ($obversion == OPEN_BADGES_V2) {
if ($obversion >= OPEN_BADGES_V2) {
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash));
$assertion['id'] = $assertionurl->out();
}
+4
View File
@@ -42,6 +42,10 @@ if ($persistedissuer) {
$returnurl = new moodle_url('/badges/backpack-connect.php',
['action' => 'authorization', 'sesskey' => sesskey()]);
// If scope is not passed as parameter, use the issuer supported scopes.
if (empty($scope)) {
$scope = $issuer->get('scopessupported');
}
$client = new core_badges\oauth2\client($issuer, $returnurl, $scope, $externalbackpack);
if ($client) {
if (!$client->is_logged_in()) {
+2 -2
View File
@@ -128,7 +128,7 @@ class core_badges_assertion {
$email = empty($this->_data->backpackemail) ? $this->_data->email : $this->_data->backpackemail;
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
if ($this->_obversion == OPEN_BADGES_V2) {
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));
@@ -325,7 +325,7 @@ class core_badges_assertion {
*/
protected function embed_data_badge_version2 (&$json, $type = OPEN_BADGES_V2_TYPE_ASSERTION) {
// Specification Version 2.0.
if ($this->_obversion == OPEN_BADGES_V2) {
if ($this->_obversion >= OPEN_BADGES_V2) {
$badge = new badge($this->_data->id);
if (empty($this->_data->courseid)) {
$context = context_system::instance();
+2 -2
View File
@@ -61,12 +61,12 @@ class external_backpack extends \moodleform {
$mform->addElement('text', 'backpackapiurl', get_string('backpackapiurl', 'core_badges'));
$mform->setType('backpackapiurl', PARAM_URL);
$mform->addRule('backpackapiurl', null, 'required', null, 'client');
$mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 50, 'client');
$mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->addElement('text', 'backpackweburl', get_string('backpackweburl', 'core_badges'));
$mform->setType('backpackweburl', PARAM_URL);
$mform->addRule('backpackweburl', null, 'required', null, 'client');
$mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 50, 'client');
$mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$apiversions = badges_get_badge_api_versions();
$mform->addElement('select', 'apiversion', get_string('apiversion', 'core_badges'), $apiversions);
+6 -3
View File
@@ -222,6 +222,7 @@ class client extends \core\oauth2\client {
$callbackurl = self::callback_url();
if ($granttype == 'authorization_code') {
$this->basicauth = true;
$params = array('code' => $code,
'grant_type' => $granttype,
'redirect_uri' => $callbackurl->out(false),
@@ -236,7 +237,7 @@ class client extends \core\oauth2\client {
);
}
if ($this->basicauth) {
$idsecret = urlencode($this->clientid) . ':' . urlencode($this->clientsecret);
$idsecret = $this->clientid . ':' . $this->clientsecret;
$this->setHeader('Authorization: Basic ' . base64_encode($idsecret));
} else {
$params['client_id'] = $this->clientid;
@@ -244,11 +245,13 @@ class client extends \core\oauth2\client {
}
// Requests can either use http GET or POST.
$response = $this->post($this->token_url(), $this->build_post_data($params));
$r = json_decode($response);
if ($this->info['http_code'] !== 200) {
throw new moodle_exception('Could not upgrade oauth token');
$debuginfo = !empty($this->error) ? $this->error : $response;
throw new moodle_exception('oauth2refreshtokenerror', 'core_error', '', $this->info['http_code'], $debuginfo);
}
$r = json_decode($response);
if (is_null($r)) {
throw new moodle_exception("Could not decode JSON token response");
}
+13 -3
View File
@@ -240,13 +240,23 @@ class award_criteria_cohort extends award_criteria {
return array($join, $where, $params);
} else {
// User is a member of ALL of the specified cohorts.
$join = " LEFT JOIN {cohort_members} cm ON cm.userid = u.id";
$join = ' LEFT JOIN {cohort_members} cm ON cm.userid = u.id';
$where = ' AND (';
$i = 0;
foreach ($this->params as $param) {
$i++;
$where = ' AND cm.cohortid = :cohortid'.$i;
if ($i == 0) {
$where .= 'cm.cohortid = :cohortid'.$i;
} else {
$where .= ' OR cm.cohortid = :cohortid'.$i;
}
$params['cohortid'.$i] = $param['cohort'];
$i++;
}
$where .= ')
GROUP BY u.id, bi.badgeid
HAVING COUNT(cm.cohortid) = :cohortcount';
$params['cohortcount'] = $i;
return array($join, $where, $params);
}
}
+110 -8
View File
@@ -620,35 +620,127 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
}
/**
* Test badges observer when cohort_member_added event is fired.
* Test badges observer when cohort_member_added event is fired and user required to belong to any cohort.
*
* @covers award_criteria_cohort
*/
public function test_badges_observer_cohort_criteria_review() {
public function test_badges_observer_any_cohort_criteria_review() {
global $CFG;
require_once("$CFG->dirroot/cohort/lib.php");
$cohort = $this->getDataGenerator()->create_cohort();
$cohort1 = $this->getDataGenerator()->create_cohort();
$cohort2 = $this->getDataGenerator()->create_cohort();
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
$badge = new badge($this->badgeid);
$this->assertFalse($badge->is_issued($this->user->id));
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
// Set up the badge criteria.
$criteriaoverall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
$criteriaoverall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
$criteriaoverall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COHORT, 'badgeid' => $badge->id));
$criteriaoverall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'cohort_cohorts' => array('0' => $cohort->id)));
// Make the badge active.
$criteriaoverall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY,
'cohort_cohorts' => array('0' => $cohort1->id, '1' => $cohort2->id)));
$badge->set_status(BADGE_STATUS_ACTIVE);
// Reload it to contain criteria.
$badge = new badge($this->badgeid);
$this->assertFalse($badge->is_issued($this->user->id));
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
// Add the user to the cohort.
cohort_add_member($cohort->id, $this->user->id);
cohort_add_member($cohort2->id, $this->user->id);
$this->assertDebuggingCalled();
// Verify that the badge was awarded.
$this->assertDebuggingCalled();
$this->assertTrue($badge->is_issued($this->user->id));
// As the badge has been awarded to user because core_badges_observer been called when the member has been added to the
// cohort, there are no other users that can award this badge.
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
}
/**
* Test badges observer when cohort_member_added event is fired and user required to belong to multiple (all) cohorts.
*
* @covers award_criteria_cohort
*/
public function test_badges_observer_all_cohort_criteria_review() {
global $CFG;
require_once("$CFG->dirroot/cohort/lib.php");
$cohort1 = $this->getDataGenerator()->create_cohort();
$cohort2 = $this->getDataGenerator()->create_cohort();
$cohort3 = $this->getDataGenerator()->create_cohort();
// Add user2 to cohort1 and cohort3.
$user2 = $this->getDataGenerator()->create_user();
cohort_add_member($cohort3->id, $user2->id);
cohort_add_member($cohort1->id, $user2->id);
// Add user3 to cohort1, cohort2 and cohort3.
$user3 = $this->getDataGenerator()->create_user();
cohort_add_member($cohort1->id, $user3->id);
cohort_add_member($cohort2->id, $user3->id);
cohort_add_member($cohort3->id, $user3->id);
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
// Cohort criteria are used in site badges.
$badge = new badge($this->badgeid);
$this->assertFalse($badge->is_issued($this->user->id));
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
// Set up the badge criteria.
$criteriaoverall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
$criteriaoverall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
$criteriaoverall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COHORT, 'badgeid' => $badge->id));
$criteriaoverall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL,
'cohort_cohorts' => array('0' => $cohort1->id, '1' => $cohort2->id, '2' => $cohort3->id)));
$badge->set_status(BADGE_STATUS_ACTIVE);
// Reload it to contain criteria.
$badge = new badge($this->badgeid);
// Verify that the badge was not awarded yet (ALL cohorts are needed and review_all_criteria has to be called).
$this->assertFalse($badge->is_issued($this->user->id));
$this->assertFalse($badge->is_issued($user2->id));
$this->assertFalse($badge->is_issued($user3->id));
// Verify that after calling review_all_criteria, users with the criteria (user3) award the badge instantly.
$this->assertSame(1, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
$this->assertFalse($badge->is_issued($this->user->id));
$this->assertFalse($badge->is_issued($user2->id));
$this->assertTrue($badge->is_issued($user3->id));
$this->assertDebuggingCalled();
// Add the user to the cohort1.
cohort_add_member($cohort1->id, $this->user->id);
// Verify that the badge was not awarded yet (ALL cohorts are needed).
$this->assertFalse($badge->is_issued($this->user->id));
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
// Add the user to the cohort3.
cohort_add_member($cohort3->id, $this->user->id);
// Verify that the badge was not awarded yet (ALL cohorts are needed).
$this->assertFalse($badge->is_issued($this->user->id));
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
// Add user to cohort2.
cohort_add_member($cohort2->id, $this->user->id);
$this->assertDebuggingCalled();
// Verify that the badge was awarded (ALL cohorts).
$this->assertTrue($badge->is_issued($this->user->id));
// As the badge has been awarded to user because core_badges_observer been called when the member has been added to the
// cohort, there are no other users that can award this badge.
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
}
/**
@@ -694,6 +786,16 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
$this->assertStringMatchesFormat($testassertion2->badge, json_encode($assertion2->get_badge_assertion()));
$this->assertStringMatchesFormat($testassertion2->class, json_encode($assertion2->get_badge_class()));
$this->assertStringMatchesFormat($testassertion2->issuer, json_encode($assertion2->get_issuer()));
// Test Openbadge specification version 2.1. It has the same format as OBv2.0.
// Get assertion version 2.1.
$award = reset($awards);
$assertion2 = new core_badges_assertion($award->uniquehash, OPEN_BADGES_V2P1);
// Make sure JSON strings have the same structure.
$this->assertStringMatchesFormat($testassertion2->badge, json_encode($assertion2->get_badge_assertion()));
$this->assertStringMatchesFormat($testassertion2->class, json_encode($assertion2->get_badge_class()));
$this->assertStringMatchesFormat($testassertion2->issuer, json_encode($assertion2->get_issuer()));
}
/**
+1 -1
View File
@@ -120,4 +120,4 @@ Feature: Backpack badges
And I click on "Delete" "button" in the "Delete site backpack" "dialogue"
Then I should see "The site backpack has been deleted."
And I should not see "https://dc.imsglobal.org"
And "Delete" "button" should not exist
And "Delete" "button" should not be visible
+5 -5
View File
@@ -51,18 +51,18 @@ Feature: Award badges based on activity completion
And I log out
Scenario: Student earns a badge using activity completion, but does not get passing grade
When I log in as "student1"
Given I log in as "student1"
And I am on "Course 1" course homepage
And the "Test quiz name" "quiz" activity with "auto" completion should be marked as not complete
And I follow "Test quiz name"
When I am on the "Test quiz name" "quiz activity" page
And I press "Re-attempt quiz"
And I set the field "False" to "1"
And I press "Finish attempt ..."
And I press "Submit all and finish"
And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
And I log out
Then I log in as "teacher1"
And I am on "Course 1" course homepage
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Badges > Manage badges" in current page administration
And I follow "Course Badge"
And I should see "Recipients (1)"
Then I should see "Recipients (1)"
@@ -64,6 +64,7 @@ Feature: Award badges based on competency completion
And I set the following fields to these values:
| Rating | C |
And I click on "Rate" "button" in the "Rate" "dialogue"
And I should see "The competency rating was manually set in the course"
And I log out
# See if we got the badge
Then I log in as "user1"
@@ -114,8 +115,8 @@ Feature: Award badges based on competency completion
And I press "Rate"
And I set the following fields to these values:
| Rating | C |
And I wait until the page is ready
And I click on "Rate" "button" in the "Rate" "dialogue"
And I should see "The competency rating was manually set in the course"
And I log out
# See if we got the badge
Then I log in as "user1"
@@ -168,8 +169,8 @@ Feature: Award badges based on competency completion
And I press "Rate"
And I set the following fields to these values:
| Rating | C |
And I wait until the page is ready
And I click on "Rate" "button" in the "Rate" "dialogue"
And I should see "The competency rating was manually set in the course"
And I log out
# We should not get the badge yet.
Then I log in as "user1"
@@ -184,8 +185,8 @@ Feature: Award badges based on competency completion
And I press "Rate"
And I set the following fields to these values:
| Rating | C |
And I wait until the page is ready
And I click on "Rate" "button" in the "Rate" "dialogue"
And I should see "The competency rating was manually set in the course"
And I log out
# See if we got the badge now.
Then I log in as "user1"
@@ -147,7 +147,6 @@ Feature: My overview block pagination
| student1 | C13 | student |
When I log in as "student1"
And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
And I wait until ".block_myoverview [data-control='next']" "css_element" exists
Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should contain "disabled"
And I log out
@@ -181,7 +180,6 @@ Feature: My overview block pagination
| student1 | C25 | student |
When I log in as "student1"
And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
And I wait until ".block_myoverview [data-control='next']" "css_element" exists
Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should not contain "disabled"
And the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should not contain "disabled"
And I should see "Course 13" in the "Course overview" "block"
@@ -27,9 +27,7 @@ Feature: Course overview block show users their progress on courses
Then I should not see "0%" in the "Course overview" "block"
Scenario: User complete activity and verify his progress
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "Test choice 1"
Given I am on the "Test choice 1" "choice activity" page logged in as teacher1
And I navigate to "Edit settings" in current page administration
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
@@ -40,8 +38,8 @@ Feature: Course overview block show users their progress on courses
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
Then I should see "Course 1" in the "Course overview" "block"
And I should see "0%" in the "Course overview" "block"
And I am on "Course 1" course homepage
And I follow "Test choice 1"
And I am on the "Test choice 1" "choice activity" page
And I follow "Dashboard" in the user menu
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
And I should see "100%" in the "Course overview" "block"
@@ -23,7 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['otherusers'] = 'Other Users ({$a})';
$string['otherusers'] = 'Other users ({$a})';
$string['onlinestatushiding_desc'] = 'If enabled, users have the option to hide their online status from other users.';
$string['configtimetosee'] = 'Number of minutes determining the period of inactivity after which a user is no longer considered to be online.';
$string['onlinestatushiding'] = 'Online status hiding';
@@ -37,7 +37,7 @@ Feature: The online users block allow you to see who is currently online on fron
When I log in as "student1"
And I am on site homepage
Then I should not see "Admin User" in the "Online users" "block"
And I should see "Other Users (1)" in the "Online users" "block"
And I should see "Other users (1)" in the "Online users" "block"
And I should see "Student 1" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
And I should see "3 online users" in the "Online users" "block"
@@ -79,7 +79,7 @@ Feature: The online users block allow you to see who is currently online on fron
And I am on site homepage
Then I should see "2 online user" in the "Online users" "block"
And I should not see "Admin" in the "Online users" "block"
And I should see "Other Users (1)" in the "Online users" "block"
And I should see "Other users (1)" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
And I should not see "Student 1" in the "Online users" "block"
And I log out
@@ -94,7 +94,7 @@ Feature: The online users block allow you to see who is currently online on fron
And I am on site homepage
Then I should see "3 online users" in the "Online users" "block"
And I should not see "Admin" in the "Online users" "block"
And I should see "Other Users (1)" in the "Online users" "block"
And I should see "Other users (1)" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
And I should see "Student 1" in the "Online users" "block"
@@ -138,7 +138,7 @@ Feature: The online users block allow you to see who is currently online on fron
And I am on site homepage
And I should see "2 online user" in the "Online users" "block"
And I should not see "Admin" in the "Online users" "block"
And I should see "Other Users (1)" in the "Online users" "block"
And I should see "Other users (1)" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
And I should not see "Student 1" in the "Online users" "block"
And I log out
@@ -148,6 +148,6 @@ Feature: The online users block allow you to see who is currently online on fron
When I am on site homepage
Then I should see "3 online users" in the "Online users" "block"
And I should not see "Admin" in the "Online users" "block"
And I should see "Other Users (1)" in the "Online users" "block"
And I should see "Other users (1)" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
And I should see "Student 1" in the "Online users" "block"
@@ -19,7 +19,7 @@ Feature: The private files block allows users to store files privately in moodle
| page | C1 | page1 | Test page name | Test page description |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "Test page name"
And I am on the "Test page name" "page activity" page
And I add the "Private files" block
And I should see "No files available" in the "Private files" "block"
When I follow "Manage private files..."
@@ -16,24 +16,20 @@ Feature: The recently accessed items block allows users to easily access their m
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
And I log in as "admin"
And I am on "Course 1" course homepage
And I turn editing mode on
And I add a "Forum" to section "1" and I fill the form with:
| Forum name | Test forum name |
| Forum type | Standard forum for general use |
| Description | Test forum description |
And I log out
And the following "activity" exists:
| course | C1 |
| activity | forum |
| idnumber | Test forum name |
| name | Test forum name |
And I log in as "student1"
When I press "Customise this page"
And I press "Customise this page"
And I add the "Recently accessed items" block
Scenario: User has not accessed any item
Then I should see "No recent items" in the "Recently accessed items" "block"
Scenario: User has accessed some items
Given I am on "Course 1" course homepage
When I follow "Test forum name"
Given I change window size to "large"
When I am on the "Test forum name" "forum activity" page
And I follow "Dashboard" in the user menu
And I change window size to "large"
Then I should see "Test forum name" in the "Recently accessed items" "block"
+3 -1
View File
@@ -42,19 +42,21 @@ class block_tag_youtube_edit_form extends block_edit_form {
// Category setting.
$categorychoices = ['0' => get_string('anycategory', 'block_tag_youtube')];
$hascategoryerror = false;
$categoryerror = '';
try {
// Get all video categories through an API call and add them to the category list.
$categorychoices += $this->block->get_categories();
} catch (Exception $e) {
$hascategoryerror = true;
$categoryerror = $e->getMessage();
}
$mform->addElement('select', 'config_category', get_string('category', 'block_tag_youtube'),
$categorychoices);
$mform->setDefault('config_category', 0);
if ($categoryerror) {
if ($hascategoryerror) {
$notification = $OUTPUT->notification(get_string('categoryerror', 'block_tag_youtube', $categoryerror),
'error');
$mform->addElement('static', 'config_category_error', '', $notification);
+135 -30
View File
@@ -97,18 +97,26 @@ class provider implements
public static function get_contexts_for_userid(int $userid) : contextlist {
$contextlist = new contextlist();
// Only events belonging to that user will be exported.
// Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
$params = [
'usercontext' => CONTEXT_USER,
'modulecontext' => CONTEXT_MODULE,
'cuserid' => $userid,
'muserid' => $userid,
'sitecontext' => CONTEXT_SYSTEM,
'categorycontext' => CONTEXT_COURSECAT,
'coursecontext' => CONTEXT_COURSE,
'groupcontext' => CONTEXT_COURSE,
'usercontext' => CONTEXT_USER,
'cuserid' => $userid,
'modulecontext' => CONTEXT_MODULE,
'muserid' => $userid
];
// Get contexts of Calendar Events for the owner.
$sql = "SELECT ctx.id
FROM {context} ctx
JOIN {event} e ON
(e.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
(e.categoryid = ctx.instanceid AND e.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
(e.courseid = ctx.instanceid AND e.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
(e.courseid = ctx.instanceid AND e.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
(e.userid = ctx.instanceid AND e.eventtype = 'user' AND ctx.contextlevel = :usercontext)
WHERE e.userid = :cuserid
UNION
@@ -120,8 +128,12 @@ class provider implements
WHERE e.userid = :muserid";
$contextlist->add_from_sql($sql, $params);
// Only export subscriptions belonging to that user.
// Calendar Subscriptions can exist at Site, Course Category, Course, Course Group, or User contexts.
$params = [
'sitecontext' => CONTEXT_SYSTEM,
'categorycontext' => CONTEXT_COURSECAT,
'coursecontext' => CONTEXT_COURSE,
'groupcontext' => CONTEXT_COURSE,
'usercontext' => CONTEXT_USER,
'userid' => $userid
];
@@ -130,6 +142,10 @@ class provider implements
$sql = "SELECT ctx.id
FROM {context} ctx
JOIN {event_subscriptions} s ON
(s.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
(s.categoryid = ctx.instanceid AND s.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
(s.courseid = ctx.instanceid AND s.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
(s.courseid = ctx.instanceid AND s.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
(s.userid = ctx.instanceid AND s.eventtype = 'user' AND ctx.contextlevel = :usercontext)
WHERE s.userid = :userid";
$contextlist->add_from_sql($sql, $params);
@@ -148,7 +164,8 @@ class provider implements
$context = $userlist->get_context();
// Calendar Events can exist at user (CONTEXT_USER), or Course Modules (CONTEXT_MODULE) contexts.
// Calendar Events can exist at Site (CONTEXT_SYSTEM), Course Category (CONTEXT_COURSECAT),
// Course and Course Group (CONTEXT_COURSE), User (CONTEXT_USER), or Course Modules (CONTEXT_MODULE) contexts.
if ($context->contextlevel == CONTEXT_MODULE) {
$params = ['cmid' => $context->instanceid];
@@ -159,21 +176,43 @@ class provider implements
WHERE cm.id = :cmid";
$userlist->add_from_sql('userid', $sql, $params);
} else if ($context->contextlevel == CONTEXT_USER) {
$params = ['instanceid' => $context->instanceid];
} else if ($context->contextlevel == CONTEXT_SYSTEM) {
// Get contexts of Calendar Events for the owner.
$sql = "SELECT userid FROM {event} WHERE eventtype = 'site'";
$userlist->add_from_sql('userid', $sql, []);
// Get contexts for Calendar Subscriptions for the owner.
$sql = "SELECT userid FROM {event_subscriptions} WHERE eventtype = 'site'";
$userlist->add_from_sql('userid', $sql, []);
} else if (in_array($context->contextlevel, [CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_USER])) {
$eventfields = [
CONTEXT_COURSECAT => 'categoryid',
CONTEXT_COURSE => 'courseid',
CONTEXT_USER => 'userid'
];
$eventfield = $eventfields[$context->contextlevel];
$eventtypes = [
CONTEXT_COURSECAT => 'category',
CONTEXT_COURSE => ['course' , 'group'],
CONTEXT_USER => 'user'
];
list($eventtypesql, $eventtypeparams) = $DB->get_in_or_equal($eventtypes[$context->contextlevel], SQL_PARAMS_NAMED);
$params = $eventtypeparams + ['instanceid' => $context->instanceid];
// Get contexts of Calendar Events for the owner.
$sql = "SELECT userid
FROM {event}
WHERE eventtype = 'user'
AND userid = :instanceid";
WHERE eventtype $eventtypesql
AND $eventfield = :instanceid";
$userlist->add_from_sql('userid', $sql, $params);
// Get contexts for Calendar Subscriptions for the owner.
$sql = "SELECT userid
FROM {event_subscriptions}
WHERE eventtype = 'user'
AND userid = :instanceid";
WHERE eventtype $eventtypesql
AND $eventfield = :instanceid";
$userlist->add_from_sql('userid', $sql, $params);
}
}
@@ -237,6 +276,9 @@ class provider implements
$userids = $userlist->get_userids();
$allowedcontexts = [
CONTEXT_SYSTEM,
CONTEXT_COURSECAT,
CONTEXT_COURSE,
CONTEXT_MODULE,
CONTEXT_USER
];
@@ -407,8 +449,9 @@ class provider implements
protected static function get_calendar_event_ids_by_context(\context $context, $userids = array()) {
global $DB;
// Only events can exist at user and course module contexts can be exported.
if (!in_array($context->contextlevel, [CONTEXT_USER, CONTEXT_MODULE])) {
// Calendar Events can exist at Site (CONTEXT_SYSTEM), Course Category (CONTEXT_COURSECAT),
// Course and Course Group (CONTEXT_COURSE), User (CONTEXT_USER), or Course Modules (CONTEXT_MODULE) contexts.
if (!in_array($context->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_USER, CONTEXT_MODULE])) {
return [];
}
@@ -428,16 +471,35 @@ class provider implements
JOIN {modules} m ON m.id = cm.module
JOIN {event} e ON e.modulename = m.name AND e.courseid = cm.course AND e.instance = cm.instance
WHERE cm.id = :cmid
AND e.userid <> 0
$whereusersql";
} else {
$params = ['instanceid' => $context->instanceid];
} else if ($context->contextlevel == CONTEXT_SYSTEM) { // Site events.
$params = [];
$sql = "SELECT DISTINCT e.id AS eventid
FROM {event} e
WHERE e.eventtype = 'site'
$whereusersql";
} else { // The rest.
$eventfields = [
CONTEXT_COURSECAT => 'categoryid',
CONTEXT_COURSE => 'courseid',
CONTEXT_USER => 'userid'
];
$eventfield = $eventfields[$context->contextlevel];
$eventtypes = [
CONTEXT_COURSECAT => 'category',
CONTEXT_COURSE => ['course' , 'group'],
CONTEXT_USER => 'user'
];
list($eventtypesql, $eventtypeparams) = $DB->get_in_or_equal($eventtypes[$context->contextlevel], SQL_PARAMS_NAMED);
$params = $eventtypeparams + ['instanceid' => $context->instanceid];
// Get Calendar Events for the specified Moodle context.
$sql = "SELECT DISTINCT e.id AS eventid
FROM {event} e
WHERE e.eventtype = 'user'
AND e.userid = :instanceid
WHERE e.eventtype $eventtypesql
AND e.{$eventfield} = :instanceid
$whereusersql";
}
@@ -458,8 +520,9 @@ class provider implements
protected static function get_calendar_subscription_ids_by_context(\context $context, $userids = array()) {
global $DB;
// Only own user event subscriptions can be exported.
if ($context->contextlevel != CONTEXT_USER) {
// Calendar Subscriptions can exist at Site (CONTEXT_SYSTEM), Course Category (CONTEXT_COURSECAT),
// Course and Course Group (CONTEXT_COURSE), or User (CONTEXT_USER) contexts.
if (!in_array($context->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_USER])) {
return [];
}
@@ -469,14 +532,40 @@ class provider implements
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$whereusersql = "AND s.userid {$usersql}";
}
$params = ['instanceid' => $context->instanceid];
// Get Calendar Subscriptions for the specified context.
$sql = "SELECT DISTINCT s.id AS subscriptionid
FROM {event_subscriptions} s
WHERE s.eventtype = 'user'
AND s.userid = :instanceid
$whereusersql";
if ($context->contextlevel == CONTEXT_SYSTEM) {
$params = [];
// Get Calendar Subscriptions for the system context.
$sql = "SELECT DISTINCT s.id AS subscriptionid
FROM {event_subscriptions} s
WHERE s.eventtype = 'site'
$whereusersql";
} else {
$eventfields = [
CONTEXT_COURSECAT => 'categoryid',
CONTEXT_COURSE => 'courseid',
CONTEXT_USER => 'userid'
];
$eventfield = $eventfields[$context->contextlevel];
$eventtypes = [
CONTEXT_COURSECAT => 'category',
CONTEXT_COURSE => ['course' , 'group'],
CONTEXT_USER => 'user'
];
list($eventtypesql, $eventtypeparams) = $DB->get_in_or_equal($eventtypes[$context->contextlevel], SQL_PARAMS_NAMED);
$params = $eventtypeparams + ['instanceid' => $context->instanceid];
// Get Calendar Subscriptions for the specified context.
$sql = "SELECT DISTINCT s.id AS subscriptionid
FROM {event_subscriptions} s
WHERE s.eventtype $eventtypesql
AND s.{$eventfield} = :instanceid
$whereusersql";
}
$params += $userparams;
return $DB->get_records_sql($sql, $params);
@@ -498,8 +587,12 @@ class provider implements
list($contextsql1, $contextparams1) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
list($contextsql2, $contextparams2) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
// Only User and Course Modules contexts belonging to the user can be exported.
// Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
$params = [
'sitecontext' => CONTEXT_SYSTEM,
'categorycontext' => CONTEXT_COURSECAT,
'coursecontext' => CONTEXT_COURSE,
'groupcontext' => CONTEXT_COURSE,
'usercontext' => CONTEXT_USER,
'cuserid' => $userid,
'modulecontext' => CONTEXT_MODULE,
@@ -522,6 +615,10 @@ class provider implements
ctx.id AS ctxid
FROM {context} ctx
INNER JOIN {event} e ON
(e.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
(e.categoryid = ctx.instanceid AND e.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
(e.courseid = ctx.instanceid AND e.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
(e.courseid = ctx.instanceid AND e.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
(e.userid = ctx.instanceid AND e.eventtype = 'user' AND ctx.contextlevel = :usercontext)
WHERE e.userid = :cuserid
AND ctx.id {$contextsql1}
@@ -557,6 +654,10 @@ class provider implements
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$params = [
'sitecontext' => CONTEXT_SYSTEM,
'categorycontext' => CONTEXT_COURSECAT,
'coursecontext' => CONTEXT_COURSE,
'groupcontext' => CONTEXT_COURSE,
'usercontext' => CONTEXT_USER,
'userid' => $user->id
];
@@ -571,6 +672,10 @@ class provider implements
s.eventtype as eventtype
FROM {context} c
INNER JOIN {event_subscriptions} s ON
(s.eventtype = 'site' AND c.contextlevel = :sitecontext) OR
(s.categoryid = c.instanceid AND s.eventtype = 'category' AND c.contextlevel = :categorycontext) OR
(s.courseid = c.instanceid AND s.eventtype = 'course' AND c.contextlevel = :coursecontext) OR
(s.courseid = c.instanceid AND s.eventtype = 'group' AND c.contextlevel = :groupcontext) OR
(s.userid = c.instanceid AND s.eventtype = 'user' AND c.contextlevel = :usercontext)
WHERE s.userid = :userid
AND c.id {$contextsql}";
+42 -92
View File
@@ -234,18 +234,9 @@ class calendar_event {
$data->eventtype = 'user';
}
// Only user and user override type of events should record the user id.
// For all other event types we set userid to 0 as they are considered shared events.
// Default to the current user.
if (empty($data->userid)) {
if ($this->allow_custom_userid($data)) {
$data->userid = $USER->id;
} else {
$data->userid = 0;
}
}
if (empty($data->courseid) && $data->eventtype == 'site') {
$data->courseid = SITEID;
$data->userid = $USER->id;
}
if (!empty($data->timeduration) && is_array($data->timeduration)) {
@@ -323,28 +314,6 @@ class calendar_event {
return !empty($this->properties->{$key});
}
/**
* Decide whether user id should be stored on the event or not.
*
* Only user events and user overrides type of events should retain user id.
*
* @param stdClass $data The event data object.
* @return bool
*/
protected function allow_custom_userid(stdClass $data): bool {
if ($data->eventtype === 'user') {
return true;
}
$isactionevent = !empty($data->type) && $data->type == CALENDAR_EVENT_TYPE_ACTION;
$isuseroverride = !empty($data->priority) && $data->priority == CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
if ($isactionevent && $isuseroverride) {
return true;
}
return false;
}
/**
* Calculate the context value needed for an event.
*
@@ -519,18 +488,49 @@ class calendar_event {
}
if ($usingeditor) {
switch ($this->properties->eventtype) {
case 'user':
$this->properties->courseid = 0;
$this->properties->course = 0;
$this->properties->groupid = 0;
$this->properties->userid = $USER->id;
break;
case 'site':
$this->properties->courseid = SITEID;
$this->properties->course = SITEID;
$this->properties->groupid = 0;
$this->properties->userid = $USER->id;
break;
case 'course':
$this->properties->groupid = 0;
$this->properties->userid = $USER->id;
break;
case 'category':
$this->properties->groupid = 0;
$this->properties->category = 0;
$this->properties->userid = $USER->id;
break;
case 'group':
$this->properties->userid = $USER->id;
break;
default:
// We should NEVER get here, but just incase we do lets fail gracefully.
$usingeditor = false;
break;
}
// If we are actually using the editor, we recalculate the context because some default values
// were set when calculate_context() was called from the constructor.
$this->properties->context = $this->calculate_context();
$this->editorcontext = $this->get_context();
if ($usingeditor) {
$this->properties->context = $this->calculate_context();
$this->editorcontext = $this->get_context();
}
$editor = $this->properties->description;
$this->properties->format = $this->properties->description['format'];
$this->properties->description = $this->properties->description['text'];
}
$this->set_default_event_ids();
// Insert the event into the database.
$this->properties->id = $DB->insert_record('event', $this->properties);
@@ -675,8 +675,6 @@ class calendar_event {
$event->trigger();
}
} else {
$this->set_default_event_ids();
$DB->update_record('event', $this->properties);
$event = self::load($this->properties->id);
$this->properties = $event->properties();
@@ -871,49 +869,6 @@ class calendar_event {
return $properties;
}
/**
* Set the default event ids.
*/
protected function set_default_event_ids(): void {
global $USER;
$userid = (!empty($this->properties->userid)) ? $this->properties->userid : $USER->id;
switch ($this->properties->eventtype) {
case 'user':
$this->properties->courseid = 0;
$this->properties->course = 0;
$this->properties->groupid = 0;
$this->properties->userid = $userid;
break;
case 'site':
$this->properties->courseid = SITEID;
$this->properties->course = SITEID;
$this->properties->groupid = 0;
$this->properties->userid = 0;
break;
case 'course':
$this->properties->groupid = 0;
$this->properties->userid = 0;
break;
case 'category':
$this->properties->groupid = 0;
$this->properties->category = 0;
$this->properties->userid = 0;
break;
case 'group':
$this->properties->userid = 0;
break;
}
// Only user overrides type of events should store user's id.
$isactionevent = !empty($this->properties->type) && $this->properties->type == CALENDAR_EVENT_TYPE_ACTION;
$isuseroverride = $isactionevent && !empty($this->properties->priority) &&
$this->properties->priority == CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
if ($isuseroverride) {
$this->properties->userid = $userid;
}
}
/**
* Toggles the visibility of an event
*
@@ -2441,9 +2396,7 @@ function calendar_get_default_courses($courseid = null, $fields = '*', $canmanag
$prefixedfields = array_map(function($value) {
return 'c.' . trim(strtolower($value));
}, $fieldlist);
if (!in_array('c.visible', $prefixedfields) && !in_array('c.*', $prefixedfields)) {
$prefixedfields[] = 'c.visible';
}
$courses = get_courses('all', 'c.shortname', implode(',', $prefixedfields));
} else {
$courses = enrol_get_users_courses($userid, true, $fields);
@@ -2820,9 +2773,7 @@ function calendar_add_subscription($sub) {
// User events.
$sub->courseid = 0;
}
// Only subscriptions for user type should store user id, shared events (course,site...) should have userid set to 0.
$sub->userid = ($sub->eventtype == 'user') ? $USER->id : 0;
$sub->userid = $USER->id;
// File subscriptions never update.
if (empty($sub->url)) {
@@ -2951,7 +2902,7 @@ function calendar_add_icalendar_event($event, $unused = null, $subscriptionid, $
// We should never do anything with an event without a subscription reference.
$sub = calendar_get_subscription($subscriptionid);
$eventrecord->subscriptionid = $subscriptionid;
$eventrecord->userid = ($sub->eventtype == 'user') ? $sub->userid : 0;
$eventrecord->userid = $sub->userid;
$eventrecord->groupid = $sub->groupid;
$eventrecord->courseid = $sub->courseid;
$eventrecord->categoryid = $sub->categoryid;
@@ -3253,6 +3204,7 @@ function calendar_update_subscription($subscription) {
* @return bool true if current user can edit the subscription else false
*/
function calendar_can_edit_subscription($subscriptionorid) {
global $USER;
if (is_array($subscriptionorid)) {
$subscription = (object)$subscriptionorid;
} else if (is_object($subscriptionorid)) {
@@ -3273,7 +3225,7 @@ function calendar_can_edit_subscription($subscriptionorid) {
calendar_get_allowed_types($allowed, $courseid, null, $category);
switch ($subscription->eventtype) {
case 'user':
return $allowed->user;
return ($USER->id == $subscription->userid && $allowed->user);
case 'course':
if (isset($allowed->courses[$courseid])) {
return $allowed->courses[$courseid];
@@ -3310,8 +3262,6 @@ function calendar_get_calendar_context($subscription) {
// Determine context based on calendar type.
if ($subscription->eventtype === 'site') {
$context = \context_course::instance(SITEID);
} else if ($subscription->eventtype === 'category') {
$context = \context_coursecat::instance($subscription->categoryid);
} else if ($subscription->eventtype === 'group' || $subscription->eventtype === 'course') {
$context = \context_course::instance($subscription->courseid);
} else {
+28 -28
View File
@@ -608,8 +608,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 6; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -619,8 +619,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 6; $i < 12; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course2->id,
@@ -663,8 +663,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 6; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -674,8 +674,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 6; $i < 12; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course2->id,
@@ -719,8 +719,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 6; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -730,8 +730,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 6; $i < 12; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course2->id,
@@ -773,8 +773,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 21; $i++) {
$records[] = create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -784,8 +784,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 21; $i < 41; $i++) {
$records[] = create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course2->id,
@@ -830,8 +830,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 41; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -901,8 +901,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 21; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -912,8 +912,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 21; $i < 41; $i++) {
create_event([
'name' => sprintf('Event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course2->id,
@@ -979,8 +979,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('1 event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -990,8 +990,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('2 event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course1->id,
@@ -1002,8 +1002,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
for ($i = 1; $i < 11; $i++) {
$records[] = create_event([
'name' => sprintf('3 event %d', $i),
'eventtype' => 'due',
'userid' => 0,
'eventtype' => 'user',
'userid' => $user->id,
'timesort' => $i,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course2->id,
+35 -39
View File
@@ -96,6 +96,13 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
} else {
$prop->repeat = 1;
}
if (empty($prop->userid)) {
if (!empty($userid)) {
$prop->userid = $userid;
} else {
$prop->userid = 0;
}
}
if (!isset($prop->courseid)) {
$prop->courseid = $SITE->id;
}
@@ -384,54 +391,44 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$this->setUser($user);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// There should be a single user event.
$this->assertEquals(1, count($events['events']));
// There should be only two events (course and group).
$this->assertEquals(2, count($events['warnings']));
$this->assertEquals(2, count($events['events'])); // site, user.
$this->assertEquals(2, count($events['warnings'])); // course, group.
$role = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// There should be a site and both course events.
$this->assertEquals(3, count($events['events']));
// There should be a warning related to group event.
$this->assertEquals(1, count($events['warnings']));
$this->assertEquals(4, count($events['events'])); // site, user, both course events.
$this->assertEquals(1, count($events['warnings'])); // group.
$options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + HOURSECS);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// There should be a site and a course event.
$this->assertEquals(2, count($events['events']));
// There should be a warning related to group event.
$this->assertEquals(1, count($events['warnings']));
$this->assertEquals(3, count($events['events'])); // site, user, one course event.
$this->assertEquals(1, count($events['warnings'])); // group.
groups_add_member($group, $user);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// There should be a site, group, one course event.
$this->assertEquals(3, count($events['events']));
$this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
$this->assertEquals(0, count($events['warnings']));
$paramevents = array ('courseids' => array($course->id), 'groupids' => array($group->id));
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// There should be only site, course and group events.
$this->assertEquals(3, count($events['events']));
$this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
$this->assertEquals(0, count($events['warnings']));
$paramevents = array ('groupids' => array($group->id, 23));
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// There should be only site and group events.
$this->assertEquals(2, count($events['events']));
$this->assertEquals(3, count($events['events'])); // site, user, group.
$this->assertEquals(1, count($events['warnings']));
$paramevents = array ('courseids' => array(23));
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// There should be a single site event.
$this->assertEquals(1, count($events['events']));
$this->assertEquals(2, count($events['events'])); // site, user.
$this->assertEquals(1, count($events['warnings']));
$paramevents = array ();
@@ -472,7 +469,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
$this->assertCount(4, $events['events']);
$this->assertCount(5, $events['events']);
// Hide the assignment.
set_coursemodule_visible($assign->cmid, 0);
@@ -483,7 +480,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
// Expect one less.
$this->assertCount(3, $events['events']);
$this->assertCount(4, $events['events']);
// Create some category events.
$this->setAdminUser();
@@ -842,7 +839,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$now = time();
// Create two events - one for everybody in the course and one only for the first student.
$event1 = $this->create_calendar_event('Base event', 0, 'due', 0, $now + DAYSECS, $params + ['courseid' => $course->id]);
$event2 = $this->create_calendar_event('User event', 0, 'user', 0, $now + 2 * DAYSECS, $params + ['courseid' => 0]);
$event2 = $this->create_calendar_event('User event', $user->id, 'due', 0, $now + 2*DAYSECS, $params + ['courseid' => 0]);
// Retrieve course events for the second student - only one "Base event" is returned.
$this->setUser($user2);
@@ -854,13 +851,14 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$this->assertEquals(0, count($events['warnings']));
$this->assertEquals('Base event', $events['events'][0]['name']);
// Retrieve events for the first student.
// Retrieve events for the first student - both events are returned.
$this->setUser($user);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
$this->assertEquals(1, count($events['events']));
$this->assertEquals(2, count($events['events']));
$this->assertEquals(0, count($events['warnings']));
$this->assertEquals('Base event', $events['events'][0]['name']);
$this->assertEquals('User event', $events['events'][1]['name']);
// Retrieve events by id as a teacher, 'User event' should be returned since teacher has access to this course.
$this->setUser($teacher);
@@ -1723,7 +1721,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$timedurationuntil->add($interval);
$formdata = [
'id' => 0,
'userid' => 0,
'userid' => $user->id,
'modulename' => '',
'instance' => 0,
'visible' => 1,
@@ -1784,7 +1782,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$timedurationuntil->add($interval);
$formdata = [
'id' => 0,
'userid' => 0,
'userid' => $user->id,
'modulename' => '',
'instance' => 0,
'visible' => 1,
@@ -1830,7 +1828,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
);
$event = $result['event'];
$this->assertEquals(0, $event['userid']);
$this->assertEquals($user->id, $event['userid']);
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
$this->assertEquals($formdata['name'], $event['name']);
}
@@ -1850,7 +1848,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$timedurationuntil->add($interval);
$formdata = [
'id' => 0,
'userid' => 0,
'userid' => $user->id,
'modulename' => '',
'instance' => 0,
'visible' => 1,
@@ -1961,7 +1959,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
);
$event = $result['event'];
$this->assertEquals(0, $event['userid']);
$this->assertEquals($user->id, $event['userid']);
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
$this->assertEquals($formdata['name'], $event['name']);
$this->assertEquals($formdata['courseid'], $event['course']['id']);
@@ -2049,7 +2047,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$timedurationuntil->add($interval);
$formdata = [
'id' => 0,
'userid' => 0,
'userid' => $user->id,
'modulename' => '',
'instance' => 0,
'visible' => 1,
@@ -2165,7 +2163,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
);
$event = $result['event'];
$this->assertEquals(0, $event['userid']);
$this->assertEquals($user->id, $event['userid']);
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
$this->assertEquals($formdata['name'], $event['name']);
$this->assertEquals($group->id, $event['groupid']);
@@ -2188,7 +2186,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$timedurationuntil->add($interval);
$formdata = [
'id' => 0,
'userid' => 0,
'userid' => $user->id,
'modulename' => '',
'instance' => 0,
'visible' => 1,
@@ -2239,7 +2237,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
);
$event = $result['event'];
$this->assertEquals(0, $event['userid']);
$this->assertEquals($user->id, $event['userid']);
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
$this->assertEquals($formdata['name'], $event['name']);
$this->assertEquals($group->id, $event['groupid']);
@@ -2262,7 +2260,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$timedurationuntil->add($interval);
$formdata = [
'id' => 0,
'userid' => 0,
'userid' => $user->id,
'modulename' => '',
'instance' => 0,
'visible' => 1,
@@ -2312,7 +2310,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
);
$event = $result['event'];
$this->assertEquals(0, $event['userid']);
$this->assertEquals($user->id, $event['userid']);
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
$this->assertEquals($formdata['name'], $event['name']);
$this->assertEquals($group->id, $event['groupid']);
@@ -2584,10 +2582,8 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$category = $generator->create_category(['visible' => 0]);
$name = 'Category Event (category: ' . $category->id . ')';
$record = new stdClass();
$record->eventtype = 'category';
$record->categoryid = $category->id;
$record->userid = 0;
$categoryevent = $this->create_calendar_event($name, 0, 'category', 0, time(), $record);
$categoryevent = $this->create_calendar_event($name, $USER->id, 'category', 0, time(), $record);
$events = [
'eventids' => [$categoryevent->id]
+96
View File
@@ -66,6 +66,102 @@ function create_event($properties) {
return $event->create($record);
}
/**
* Helper function to create a x number of events for each event type.
*
* @param int $quantity The quantity of events to be created.
* @return array List of created events.
*/
function create_standard_events(int $quantity): array {
$types = ['site', 'category', 'course', 'group', 'user'];
$events = [];
foreach ($types as $eventtype) {
// Create five events of each event type.
for ($i = 0; $i < $quantity; $i++) {
$events[] = create_event(['eventtype' => $eventtype]);
}
}
return $events;
}
/**
* Helper function to create an action event.
*
* @param array $data The event data.
* @return bool|calendar_event
*/
function create_action_event(array $data) {
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
if (!isset($data['modulename']) || !isset($data['instance'])) {
throw new coding_exception('Module and instance should be specified when creating an action event.');
}
$isuseroverride = isset($data->priority) && $data->priority == CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
if ($isuseroverride) {
if (!in_array($data['modulename'], ['assign', 'lesson', 'quiz'])) {
throw new coding_exception('Only assign, lesson and quiz modules supports overrides');
}
}
$event = array_merge($data, [
'eventtype' => isset($data['eventtype']) ? $data['eventtype'] : 'open',
'courseid' => isset($data['courseid']) ? $data['courseid'] : 0,
'instance' => $data['instance'],
'modulename' => $data['modulename'],
'type' => CALENDAR_EVENT_TYPE_ACTION,
]);
return create_event($event);
}
/**
* Helper function to create an user override calendar event.
*
* @param string $modulename The modulename.
* @param int $instanceid The instance id.
* @param int $userid The user id.
* @return calendar_event|false
*/
function create_user_override_event(string $modulename, int $instanceid, int $userid) {
if (!isset($userid)) {
throw new coding_exception('Must specify userid when creating a user override.');
}
return create_action_event([
'modulename' => $modulename,
'instance' => $instanceid,
'userid' => $userid,
'priority' => CALENDAR_EVENT_USER_OVERRIDE_PRIORITY,
]);
}
/**
* Helper function to create an group override calendar event.
*
* @param string $modulename The modulename.
* @param int $instanceid The instance id.
* @param int $courseid The course id.
* @param int $groupid The group id.
* @return calendar_event|false
*/
function create_group_override_event(string $modulename, int $instanceid, int $courseid, int $groupid) {
if (!isset($groupid)) {
throw new coding_exception('Must specify groupid when creating a group override.');
}
return create_action_event([
'groupid' => $groupid,
'courseid' => $courseid,
'modulename' => $modulename,
'instance' => $instanceid,
]);
}
/**
* A test factory that will create action events.
*
+6 -6
View File
@@ -154,8 +154,8 @@ class core_calendar_local_api_testcase extends advanced_testcase {
'courseid' => $course->id,
'modulename' => 'assign',
'instance' => $moduleinstance->id,
'userid' => 0,
'eventtype' => 'due',
'userid' => 1,
'eventtype' => 'user',
'repeats' => 0,
'timestart' => 1,
];
@@ -205,8 +205,8 @@ class core_calendar_local_api_testcase extends advanced_testcase {
'courseid' => $course->id,
'modulename' => 'assign',
'instance' => $moduleinstance->id,
'userid' => 0,
'eventtype' => 'due',
'userid' => 1,
'eventtype' => 'user',
'repeats' => 0,
'timestart' => 1,
];
@@ -257,8 +257,8 @@ class core_calendar_local_api_testcase extends advanced_testcase {
'courseid' => $course->id,
'modulename' => 'assign',
'instance' => $moduleinstance->id,
'userid' => 0,
'eventtype' => 'due',
'userid' => 1,
'eventtype' => 'user',
'repeats' => 0,
'timestart' => 1,
];
+90 -103
View File
@@ -80,8 +80,8 @@ class core_calendar_privacy_testcase extends provider_testcase {
$course3context = context_course::instance($course3->id);
// Add Category Calendar Events for Category.
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
$this->create_test_standard_calendar_event('category', $user->id, time(), '', $category->id);
$this->create_test_standard_calendar_event('category', $user->id, time(), '', $category->id);
// Add User Calendar Events for User.
$this->create_test_standard_calendar_event('user', $user->id, time(), '');
@@ -89,43 +89,38 @@ class core_calendar_privacy_testcase extends provider_testcase {
$this->create_test_standard_calendar_event('user', $user->id, time(), '', 0, $course2->id);
// Add a Course Calendar Event for Course 1.
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('course', $user->id, time(), '', 0, $course1->id);
// Add a Course Assignment Action Calendar Event for Course 2.
$assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$params['course'] = $course2->id;
$params['assignsubmission_onlinetext_enabled'] = 1;
$assign1 = $assigngenerator->create_instance($params);
$assign1cm = get_coursemodule_from_instance('assign', $assign1->id);
$assign1context = context_module::instance($assign1cm->id);
$this->create_test_action_calendar_event('duedate', $course2->id, $assign1->id, 'assign', 0, time());
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $assign1->id, 'assign', 0, time());
// Add a due date event similar to a user override in another assign instance.
$assign2 = $assigngenerator->create_instance($params);
$assign2cm = get_coursemodule_from_instance('assign', $assign2->id);
$assign2context = context_module::instance($assign2cm->id);
$this->create_test_action_calendar_event('duedate', $course2->id, $assign2->id, 'assign', $user->id, time());
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $assign2->id, 'assign', $user->id, time());
$instance = $generator->create_instance($params);
$cm = get_coursemodule_from_instance('assign', $instance->id);
$modulecontext = context_module::instance($cm->id);
$assign = new assign($modulecontext, $cm, $course2);
$this->create_test_action_calendar_event('duedate', $course2->id, $instance->id, 'assign', $user->id, time());
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $instance->id, 'assign', $user->id, time());
// Add a Calendar Subscription and Group Calendar Event to Course 3.
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', 0, 0, $course3->id);
$this->create_test_standard_calendar_event('group', 0, time(), '', 0, $course3->id, $course3group->id);
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user->id, 0, $course3->id);
$this->create_test_standard_calendar_event('group', $user->id, time(), '', 0, $course3->id, $course3group->id);
// The user will be in these contexts.
$usercontextids = [
$usercontext->id,
$assign2context->id,
$categorycontext->id,
$course1context->id,
$modulecontext->id,
$course3context->id
];
// Retrieve the user's context ids.
$contextids = provider::get_contexts_for_userid($user->id);
// Check the user context list and retrieved user context lists contains the same number of records.
$this->assertCount(count($usercontextids), $contextids->get_contextids());
$this->assertEquals(count($usercontextids), count($contextids->get_contextids()));
// There should be no difference between the contexts.
$this->assertEmpty(array_diff($usercontextids, $contextids->get_contextids()));
// Check that the module context for the assignment without user override event is not included.
$this->assertNotContains($assign1context->id, $contextids->get_contextids());
}
/**
@@ -155,12 +150,12 @@ class core_calendar_privacy_testcase extends provider_testcase {
$event1 = $this->create_test_standard_calendar_event('user', $user->id, time(), '');
// Add Category Calendar Events for Category.
$event2 = $this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
$event2 = $this->create_test_standard_calendar_event('category', $user->id, time(), '', $category->id);
// Add two Course Calendar Event for Course 1 and set the same time (1 day a head).
$time = strtotime('+1 day', time());
$event3 = $this->create_test_standard_calendar_event('course', 0, $time, 'ABC', 0, $course1->id);
$event4 = $this->create_test_standard_calendar_event('course', 0, $time, 'DEF', 0, $course1->id);
$event3 = $this->create_test_standard_calendar_event('course', $user->id, $time, 'ABC', 0, $course1->id);
$event4 = $this->create_test_standard_calendar_event('course', $user->id, $time, 'DEF', 0, $course1->id);
// Add a Course Assignment Action Calendar Event for Course 2.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
@@ -174,7 +169,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Add a Calendar Subscription and Group Calendar Event to Course 3.
$subscription1 = $this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user->id, 0, $course3->id);
$event6 = $this->create_test_standard_calendar_event('group', 0, time(), '', 0, $course3->id, $course3group->id);
$event6 = $this->create_test_standard_calendar_event('group', $user->id, time(), '', 0, $course3->id, $course3group->id);
// Retrieve the user's context ids.
$contextlist = provider::get_contexts_for_userid($user->id);
@@ -325,16 +320,16 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Add a Course Calendar Event by User 1 for Course 1 and Course 2.
$this->setUser($user1);
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course2->id);
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course2->id);
// Add a Calendar Subscription by User 1 for Course 1.
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user1->id, 0, $course1->id);
// Add a Course Calendar Event by User 2 for Course 1 and Course 2.
$this->setUser($user2);
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course2->id);
$this->create_test_standard_calendar_event('course', $user2->id, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('course', $user2->id, time(), '', 0, $course2->id);
// Add a Calendar Subscription by User 2 for Course 2.
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user2->id, 0, $course2->id);
@@ -347,23 +342,23 @@ class core_calendar_privacy_testcase extends provider_testcase {
$cm = get_coursemodule_from_instance('assign', $instance->id);
$modulecontext = context_module::instance($cm->id);
$assign = new assign($modulecontext, $cm, $course2);
$this->create_test_action_calendar_event('duedate', $course2->id, $instance->id, 'assign', 0, time());
$this->create_test_action_calendar_event('duedate', $course2->id, $instance->id, 'assign', $user2->id, time());
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $instance->id, 'assign', $user2->id, time());
// Delete all Calendar Events for all Users by Context for Course 1.
provider::delete_data_for_all_users_in_context($course1context);
// The course1 events should still exist, since it belongs to the course and not to the user who created them.
// Verify all Calendar Events for Course 1 were deleted.
$events = $DB->get_records('event', array('courseid' => $course1->id));
$this->assertCount(2, $events);
// Course1 subscription should still exist, since it belongs to the course and not to the user who created them.
$this->assertCount(0, $events);
// Verify all Calendar Subscriptions for Course 1 were deleted.
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course1->id));
$this->assertCount(1, $subscriptions);
$this->assertCount(0, $subscriptions);
// The course2 events should still exist, since it belongs to the course and not to the user who created them.
// Verify all Calendar Events for Course 2 exists still.
$events = $DB->get_records('event', array('courseid' => $course2->id));
$this->assertCount(4, $events);
// Course2 subscription should still exist, since it belongs to the course and not to the user who created them.
// Verify all Calendar Subscriptions for Course 2 exists still.
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course2->id));
$this->assertCount(1, $subscriptions);
@@ -372,10 +367,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Verify all Calendar Events for Course 2 context were deleted.
$events = $DB->get_records('event', array('courseid' => $course2->id, 'modulename' => '0'));
$this->assertCount(2, $events);
$this->assertCount(0, $events);
// Verify all Calendar Subscriptions for Course 2 were deleted.
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course2->id));
$this->assertCount(1, $subscriptions);
$this->assertCount(0, $subscriptions);
// Verify all Calendar Events for the assignment exists still.
$events = $DB->get_records('event', array('modulename' => 'assign'));
@@ -384,9 +379,9 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Delete all Calendar Events for all Users by Context for the assignment.
provider::delete_data_for_all_users_in_context($modulecontext);
// Verify that the action event still exists since it is not a user override.
// Verify all Calendar Events for the assignment context were deleted.
$events = $DB->get_records('event', array('modulename' => 'assign'));
$this->assertCount(1, $events);
$this->assertCount(0, $events);
}
/**
@@ -406,28 +401,25 @@ class core_calendar_privacy_testcase extends provider_testcase {
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
// Add 6 events for user 1 for various contexts.
// Add 5 Calendar Events for User 1 for various contexts.
$this->setUser($user1);
$this->create_test_standard_calendar_event('user', $user1->id, time(), '');
$this->create_test_standard_calendar_event('user', $user1->id, time(), '');
$this->create_test_standard_calendar_event('site', 0, time(), '', 0, 1);
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course2->id);
$this->create_test_standard_calendar_event('site', $user1->id, time(), '', 0, 1);
$this->create_test_standard_calendar_event('category', $user1->id, time(), '', $category->id);
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course2->id);
// Add two subscription for user 1 at course and user contexts.
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', 0, 0, $course2->id);
$this->create_test_calendar_subscription('user', 'https://calendar.google.com/', $user1->id);
// Add 1 Calendar Subscription for User 1 at course context.
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user1->id, 0, $course2->id);
// Add four events for user 2 for various contexts.
// Add 3 Calendar Events for User 2 for various contexts.
$this->setUser($user2);
$this->create_test_standard_calendar_event('user', $user2->id, time(), '');
$this->create_test_standard_calendar_event('user', $user2->id, time(), '');
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
$this->create_test_standard_calendar_event('category', $user2->id, time(), '', $category->id);
$this->create_test_standard_calendar_event('course', $user2->id, time(), '', 0, $course1->id);
// Add a calendar Subscription for User 2 at course context.
$this->create_test_calendar_subscription('user', 'https://calendar.google.com/', $user2->id, 0, $course2->id);
// Add 1 Calendar Subscription for User 2 at course context.
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user2->id, 0, $course2->id);
// Retrieve the user's context ids.
$contextlist = provider::get_contexts_for_userid($user1->id);
@@ -436,31 +428,17 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Delete all Calendar data for User 1.
provider::delete_data_for_user($approvedcontextlist);
// All user events created by user1 should have been removed.
// Test all Calendar Events and Subscriptions for User 1 equals zero.
$events = $DB->get_records('event', ['userid' => $user1->id]);
$this->assertCount(0, $events);
// The subscription created by user1 should have been deleted.
$eventsubscriptions = $DB->get_records('event_subscriptions', ['userid' => $user1->id]);
$this->assertCount(0, $eventsubscriptions);
// Test all calendar events and subscriptions for user2 still exists and matches the same number created.
// Test all Calendar Events and Subscriptions for User 2 still exists and matches the same number created.
$events = $DB->get_records('event', ['userid' => $user2->id]);
$this->assertCount(2, $events);
// The subscription created by user2 should have not been deleted.
$this->assertCount(3, $events);
$eventsubscriptions = $DB->get_records('event_subscriptions', ['userid' => $user2->id]);
$this->assertCount(1, $eventsubscriptions);
// There should be only six "shared" events created (userid equal zero).
// One site, one category and two course events that were created by user1.
// One category and one course event created by user2.
$events = $DB->get_records('event', ['userid' => 0]);
$this->assertCount(6, $events);
// There should be eight event created in total.
// One site, one category and two course events that were created by user1.
// One category, one course and two user events created by user2.
$events = $DB->get_records('event');
$this->assertCount(8, $events);
}
/**
@@ -496,10 +474,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Add Category Calendar Events for Category.
$this->setUser($user1);
$this->create_test_standard_calendar_event('category', 0, time(), '',
$this->create_test_standard_calendar_event('category', $user1->id, time(), '',
$category->id);
$this->setUser($user2);
$this->create_test_standard_calendar_event('category', 0, time(), '',
$this->create_test_standard_calendar_event('category', $user2->id, time(), '',
$category->id);
// Add User Calendar Events for user1 and user2.
@@ -515,10 +493,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Add a Course Calendar Events for Course 1.
$this->setUser($user1);
$this->create_test_standard_calendar_event('course', 0, time(), '',
$this->create_test_standard_calendar_event('course', $user1->id, time(), '',
0, $course1->id);
$this->setUser($user2);
$this->create_test_standard_calendar_event('course', 0, time(), '',
$this->create_test_standard_calendar_event('course', $user2->id, time(), '',
0, $course1->id);
// Add a Course Assignment Action Calendar Event for Course 2.
@@ -536,7 +514,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
'assign', $user2->id, time());
// Add a Calendar Subscription and Group Calendar Event to Course 3.
$this->create_test_standard_calendar_event('group', 0, time(), '', 0,
$this->create_test_standard_calendar_event('group', $user2->id, time(), '', 0,
$course3->id, $course3group->id);
$this->setUser($user3);
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user3->id,
@@ -552,22 +530,28 @@ class core_calendar_privacy_testcase extends provider_testcase {
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
$this->assertTrue(in_array($user2->id, $userlist2->get_userids()));
// The user list for course1context should not return any users.
// The user list for course1context should return user1 and user2.
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
$this->assertCount(2, $userlist3);
$this->assertTrue(in_array($user1->id, $userlist3->get_userids()));
$this->assertTrue(in_array($user2->id, $userlist3->get_userids()));
// The user list for course2context should not return any users.
$userlist4 = new \core_privacy\local\request\userlist($course2context, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(0, $userlist4);
// The user list for course3context should not return any users.
// The user list for course3context should return user2 and user3.
$userlist5 = new \core_privacy\local\request\userlist($course3context, $component);
provider::get_users_in_context($userlist5);
$this->assertCount(0, $userlist5);
// The user list for categorycontext should not return any users.
$this->assertCount(2, $userlist5);
$this->assertTrue(in_array($user2->id, $userlist5->get_userids()));
$this->assertTrue(in_array($user3->id, $userlist5->get_userids()));
// The user list for categorycontext should return user1 and user2.
$userlist6 = new \core_privacy\local\request\userlist($categorycontext, $component);
provider::get_users_in_context($userlist6);
$this->assertCount(0, $userlist6);
$this->assertCount(2, $userlist6);
$this->assertTrue(in_array($user1->id, $userlist6->get_userids()));
$this->assertTrue(in_array($user2->id, $userlist6->get_userids()));
// The user list for modulecontext should return user2.
$userlist7 = new \core_privacy\local\request\userlist($modulecontext, $component);
provider::get_users_in_context($userlist7);
@@ -612,10 +596,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Add Category Calendar Events for Category.
$this->setUser($user1);
$this->create_test_standard_calendar_event('category', 0, time(), '',
$this->create_test_standard_calendar_event('category', $user1->id, time(), '',
$category->id);
$this->setUser($user2);
$this->create_test_standard_calendar_event('category', 0, time(), '',
$this->create_test_standard_calendar_event('category', $user2->id, time(), '',
$category->id);
// Add User Calendar Events for user1 and user2.
@@ -631,10 +615,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
// Add a Course Calendar Events for Course 1.
$this->setUser($user1);
$this->create_test_standard_calendar_event('course', 0, time(), '',
$this->create_test_standard_calendar_event('course', $user1->id, time(), '',
0, $course1->id);
$this->setUser($user2);
$this->create_test_standard_calendar_event('course', 0, time(), '',
$this->create_test_standard_calendar_event('course', $user2->id, time(), '',
0, $course1->id);
// Add a Course Assignment Action Calendar Event for Course 2.
@@ -652,7 +636,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
'assign', $user2->id, time());
// Add a Calendar Subscription and Group Calendar Event to Course 3.
$this->create_test_standard_calendar_event('group', 0, time(), '', 0,
$this->create_test_standard_calendar_event('group', $user2->id, time(), '', 0,
$course3->id, $course3group->id);
$this->setUser($user3);
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user3->id,
@@ -666,22 +650,22 @@ class core_calendar_privacy_testcase extends provider_testcase {
$userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
// The user list for course1context should not return any users.
// The user list for course1context should return user1 and user2.
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
$this->assertCount(2, $userlist3);
// The user list for course2context should not return any users.
$userlist4 = new \core_privacy\local\request\userlist($course2context, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(0, $userlist4);
// The user list for course3context should not return any users.
// The user list for course3context should return user2 and user3.
$userlist5 = new \core_privacy\local\request\userlist($course3context, $component);
provider::get_users_in_context($userlist5);
$this->assertCount(0, $userlist5);
// The user list for categorycontext should not return any users.
$this->assertCount(2, $userlist5);
// The user list for categorycontext should return user1 and user2.
$userlist6 = new \core_privacy\local\request\userlist($categorycontext, $component);
provider::get_users_in_context($userlist6);
$this->assertCount(0, $userlist6);
$this->assertCount(2, $userlist6);
// The user list for modulecontext should return user2.
$userlist7 = new \core_privacy\local\request\userlist($modulecontext, $component);
provider::get_users_in_context($userlist7);
@@ -709,25 +693,28 @@ class core_calendar_privacy_testcase extends provider_testcase {
$approvedlist2 = new approved_userlist($course1context, $component, []);
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist2);
// The user list for course1context should not return any users.
// The user list for course1context should return user1 and user2.
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
$this->assertCount(2, $userlist3);
$this->assertTrue(in_array($user1->id, $userlist3->get_userids()));
$this->assertTrue(in_array($user2->id, $userlist3->get_userids()));
// Convert $userlist3 into an approved_contextlist.
// Pass the ID of user1 as a value for the approved user list.
$approvedlist2 = new approved_userlist($course1context, $component, [$user1->id]);
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist2);
// The user list for course1context should not return any users.
// The user list for course1context should return user2.
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
$this->assertCount(1, $userlist3);
$this->assertTrue(in_array($user2->id, $userlist3->get_userids()));
// The user list for course3context should not return any users.
// The user list for course3context should still return user2 and user3.
$userlist5 = new \core_privacy\local\request\userlist($course3context, $component);
provider::get_users_in_context($userlist5);
$this->assertCount(0, $userlist5);
$this->assertCount(2, $userlist5);
// Convert $userlist6 into an approved_contextlist.
$approvedlist3 = new approved_userlist($categorycontext, $component, $userlist6->get_userids());
@@ -775,7 +762,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
'categoryid' => $categoryid,
'courseid' => $courseid,
'groupid' => $groupid,
'userid' => ($eventtype == 'user') ? $userid : 0,
'userid' => $userid,
'modulename' => 0,
'instance' => 0,
'eventtype' => $eventtype,
@@ -835,7 +822,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
'categoryid' => $categoryid,
'courseid' => $courseid,
'groupid' => $groupid,
'userid' => ($eventtype == 'user') ? $userid : 0,
'userid' => $userid,
'eventtype' => $eventtype
];
@@ -36,12 +36,10 @@ Feature: Restrict activity availability through date conditions
| x[year] | 2037 |
And I press "Save and return to course"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
When I am on the "Course 1" course page logged in as student1
Then I should see "Available from 31 December 2037"
And "Test assignment 1" "link" should not exist in the "page" "region"
And "Test assignment 1" activity should be dimmed
And "Test assignment 1" "link" should not exist
And I log out
@javascript
Scenario: Show activity hidden to students when available until date is in past
@@ -60,6 +58,5 @@ Feature: Restrict activity availability through date conditions
And I click on ".availability-item .availability-eye img" "css_element"
And I press "Save and return to course"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
Then I should not see "Test assignment 2"
When I am on the "Course 1" course page logged in as student1
Then I should not see "Test assignment 2" in the "page" "region"
@@ -17,13 +17,16 @@ Feature: Restrict activity availability through grade conditions
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And I log in as "teacher1"
And the following "activity" exists:
| course | C1 |
| activity | assign |
| idnumber | Grade assignment |
| name | Grade assignment |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| submissiondrafts | 0 |
And I am on the "Course 1" course page logged in as teacher1
And I am on "Course 1" course homepage with editing mode on
And I add a "Assignment" to section "1" and I fill the form with:
| Assignment name | Grade assignment |
| Description | Grade this assignment to revoke restriction on restricted assignment |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
# Adding the page like this because id_availableform_enabled needs to be clicked to trigger the action.
And I add a "Page" to section "2"
And I expand all fieldsets
@@ -31,15 +34,15 @@ Feature: Restrict activity availability through grade conditions
And I click on "Grade" "button" in the "Add restriction..." "dialogue"
And I click on "min" "checkbox"
And I set the following fields to these values:
| Name | Test page name |
| Description | Restricted page, till grades in Grade assignment is at least 20% |
| Page content | Test page contents |
| id | Grade assignment |
| minval | 20 |
| Name | Test page name |
| Description | Restricted page, till grades in Grade assignment is at least 20% |
| Page content | Test page contents |
| id | Grade assignment |
| minval | 20 |
And I press "Save and return to course"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
When I am on the "Course 1" course page logged in as student1
Then I should see "Not available unless: You achieve a required score in Grade assignment"
And "Test page name" activity should be dimmed
And "Test page name" "link" should not exist
@@ -50,9 +53,8 @@ Feature: Restrict activity availability through grade conditions
And I press "Save changes"
And I should see "Submitted for grading"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Grade assignment"
And I am on the "Grade assignment" "assign activity" page logged in as teacher1
And I navigate to "View all submissions" in current page administration
And I click on "Grade" "link" in the "Student First" "table_row"
And I set the following fields to these values:
@@ -61,7 +63,7 @@ Feature: Restrict activity availability through grade conditions
And I press "OK"
And I follow "Edit settings"
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I am on the "Course 1" course page logged in as student1
And "Test page name" activity should be visible
And I should not see "Not available unless: You achieve a required score in Grade assignment"
@@ -76,16 +76,14 @@ Feature: Restrict sections availability through completion or grade conditions
And I am on "Course 1" course homepage
Then I should see "Not available unless: You achieve a required score in Grade assignment"
And "Test page name" activity should be hidden
And I follow "Grade assignment"
And I am on the "Grade assignment" "assign activity" page
And I press "Add submission"
And I set the following fields to these values:
| Online text | I'm the student submission |
And I press "Save changes"
And I should see "Submitted for grading"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Grade assignment"
And I am on the "Grade assignment" "assign activity" page logged in as teacher1
And I navigate to "View all submissions" in current page administration
And I click on "Grade" "link" in the "Student First" "table_row"
And I set the following fields to these values:
@@ -94,7 +92,6 @@ Feature: Restrict sections availability through completion or grade conditions
And I press "OK"
And I follow "Edit settings"
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I am on the "Course 1" Course page logged in as student1
And "Test page name" activity should be visible
And I should not see "Not available unless: You achieve a required score in Grade assignment"
@@ -44,4 +44,4 @@ Feature: Manage H5P content from the content bank
And I expand "Site pages" node
And I click on "Content bank" "link"
When I follow "filltheblanks.h5p"
Then "region-main-settings-menu" "region" should not exist
Then "Rename" "link" should not exist in the "region-main-settings-menu" "region"
@@ -58,7 +58,7 @@ Feature: Delete H5P file from the content bank
And I click on "Content bank" "link" in the "Navigation" "block"
And I should see "filltheblanks.h5p"
And I follow "filltheblanks.h5p"
Then ".header-actions-container" "css_element" should not exist
Then "Delete" "link" should not exist in the "region-main-settings-menu" "region"
And I click on "Content bank" "link"
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
@@ -159,3 +159,37 @@ Feature: Content bank use editor feature
And I click on "Content bank" "link"
And I follow "filltheblanks.h5p"
Then "Edit" "link" should not exist in the "region-main" "region"
Scenario: Teachers keep their content authoring in copied courses
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "contentbank content" exist:
| contextlevel | reference | contenttype | user | contentname | filepath |
| Course | C1 | contenttype_h5p | admin | filltheblanks.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
| Course | C1 | contenttype_h5p | teacher1 | ipsums.h5p | /h5p/tests/fixtures/ipsums.h5p |
And I am on "Course 1" course homepage
And I navigate to "Copy course" in current page administration
And I set the following fields to these values:
| Course full name | Copy |
| Course short name | Copy |
| Teacher | 1 |
When I press "Copy and view"
And I trigger cron
And I am on homepage
And I log out
And I log in as "teacher1"
And I am on "Copy" course homepage
And I expand "Site pages" node
And I click on "Content bank" "link"
And I follow "ipsums.h5p"
Then "Edit" "link" should exist in the "region-main" "region"
And I click on "Content bank" "link"
And I follow "filltheblanks.h5p"
Then "Edit" "link" should not exist in the "region-main" "region"
+11 -5
View File
@@ -24,16 +24,18 @@ Feature: Search content in the content bank
And I expand "Site pages" node
And I click on "Content bank" "link"
And I should see "santjordi.h5p"
And "Clear search input" "button" should not exist
And "Clear search input" "button" should not be visible
And I should not see "items found"
When I set the field "Search" to "book"
Then "Clear search input" "button" should exist
# Waiting for the animation to show the button to finish.
And I wait "1" seconds
Then "Clear search input" "button" should be visible
And I should see "3 items found"
And I should see "SantJordi_book"
And I should see "mathsbook.h5p"
And I should see "historybook.h5p"
And I set the field "Search" to "sant"
And "Clear search input" "button" should exist
And "Clear search input" "button" should be visible
And I should see "5 items found"
And I set the field "Search" to "santjordi"
And I should see "4 items found"
@@ -42,10 +44,14 @@ Feature: Search content in the content bank
And I should see "SantJordi_book"
And I should see "Dragon_santjordi.h5p"
And I click on "Clear search input" "button"
And "Clear search input" "button" should not exist
# Waiting for the animation to hide the button to finish.
And I wait "1" seconds
And "Clear search input" "button" should not be visible
And I should not see "items found"
And I set the field "Search" to ".h5p"
And "Clear search input" "button" should exist
# Waiting for the animation to show the button to finish.
And I wait "1" seconds
And "Clear search input" "button" should be visible
And I should see "7 items found"
And I set the field "Search" to "friend"
And I should see "0 items found"
@@ -103,6 +103,10 @@ class no_recent_accesses extends course_enrolments {
return get_string('nocoursestudents', 'course');
}
if (!$fortraining && !$course->get_course_data()->visible) {
return get_string('hiddenfromstudents');
}
if ($course->get_end() && $course->get_end() < $course->get_start()) {
return get_string('errorendbeforestart', 'course');
}
+24 -25
View File
@@ -57,8 +57,6 @@ M.course_dndupload = {
// The selector identifying the list of modules within a section (note changing this may require
// changes to the get_mods_element function)
modslistselector: 'ul.section',
// Original onbeforeunload event.
originalUnloadEvent: null,
/**
* Initalise the drag and drop upload interface
@@ -765,7 +763,10 @@ M.course_dndupload = {
xhr.onreadystatechange = function() {
if (xhr.readyState == 1) {
this.originalUnloadEvent = window.onbeforeunload;
self.reportUploadDirtyState(true);
// Trigger form upload start events.
require(['core_form/events'], function(FormEvent) {
FormEvent.triggerUploadStarted(section.get('id'));
});
}
if (xhr.readyState == 4) {
if (xhr.status == 200) {
@@ -793,7 +794,10 @@ M.course_dndupload = {
} else {
new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
}
self.reportUploadDirtyState(false);
// Trigger form upload complete events.
require(['core_form/events'], function(FormEvent) {
FormEvent.triggerUploadCompleted(section.get('id'));
});
}
};
@@ -1025,7 +1029,10 @@ M.course_dndupload = {
xhr.onreadystatechange = function() {
if (xhr.readyState == 1) {
this.originalUnloadEvent = window.onbeforeunload;
self.reportUploadDirtyState(true);
// Trigger form upload start events.
require(['core_form/events'], function(FormEvent) {
FormEvent.triggerUploadStarted(section.get('id'));
});
}
if (xhr.readyState == 4) {
if (xhr.status == 200) {
@@ -1043,13 +1050,24 @@ M.course_dndupload = {
} else {
// Error - remove the dummy element
resel.parent.removeChild(resel.li);
// Trigger form upload complete events.
require(['core_form/events'], function(FormEvent) {
FormEvent.triggerUploadCompleted(section.get('id'));
});
new M.core.alert({message: result.error});
}
}
} else {
// Trigger form upload complete events.
require(['core_form/events'], function(FormEvent) {
FormEvent.triggerUploadCompleted(section.get('id'));
});
new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
}
self.reportUploadDirtyState(false);
// Trigger form upload complete events.
require(['core_form/events'], function(FormEvent) {
FormEvent.triggerUploadCompleted(section.get('id'));
});
}
};
@@ -1083,24 +1101,5 @@ M.course_dndupload = {
if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
M.core.actionmenu.newDOMNode(node);
}
},
/**
* Set the event to prevent user navigate away when upload progress still running.
*
* @param {bool} enable true if upload progress is running, false otherwise
*/
reportUploadDirtyState: function(enable) {
if (!enable) {
window.onbeforeunload = this.originalUnloadEvent;
} else {
window.onbeforeunload = function(e) {
var warningMessage = M.util.get_string('changesmadereallygoaway', 'moodle');
if (e) {
e.returnValue = warningMessage;
}
return warningMessage;
};
}
}
};
+24 -6
View File
@@ -4720,21 +4720,39 @@ 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', 'visible');
$sort = trim($sort);
if (empty($sort)) {
$sort = 'timeaccess DESC';
} else {
// The SQL string for sorting can define sorting by multiple columns.
$rawsorts = explode(',', $sort);
$sorts = array();
// Validate and trim the sort parameters in the SQL string for sorting.
foreach ($rawsorts as $rawsort) {
$rawsort = trim($rawsort);
$sorts[] = trim($rawsort);
$sort = trim($rawsort);
$sortparams = explode(' ', $sort);
// A valid sort statement can not have more than 2 params (ex. 'summary desc' or 'timeaccess').
if (count($sortparams) > 2) {
throw new invalid_parameter_exception(
'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].');
}
$sortfield = trim($sortparams[0]);
// Validate the value which defines the field to sort by.
if (!in_array($sortfield, $basefields)) {
throw new invalid_parameter_exception('Invalid field in the sort parameter, allowed fields: ' .
implode(', ', $basefields) . '.');
}
$sortdirection = isset($sortparams[1]) ? trim($sortparams[1]) : '';
// Validate the value which defines the sort direction (if present).
$allowedsortdirections = ['asc', 'desc'];
if (!empty($sortdirection) && !in_array(strtolower($sortdirection), $allowedsortdirections)) {
throw new invalid_parameter_exception('Invalid sort direction in the sort parameter, allowed values: ' .
implode(', ', $allowedsortdirections) . '.');
}
$sorts[] = $sort;
}
$sort = implode(',', $sorts);
}
$orderby = "ORDER BY $sort";
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
$coursefields = 'c.' .join(',', $basefields);
@@ -4767,7 +4785,7 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
AND ue.timestart < :now1
AND (ue.timeend = 0 OR ue.timeend > :now2)
))
$orderby";
ORDER BY $sort";
$now = round(time(), -2); // Improves db caching.
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_COURSE, 'visible' => 1, 'status' => ENROL_USER_ACTIVE,
+16 -3
View File
@@ -621,10 +621,23 @@ abstract class moodleform_mod extends moodleform {
if (!empty($CFG->enableavailability)) {
// Add special button to end of previous section if groups/groupings
// are enabled.
if ($this->_features->groups || $this->_features->groupings) {
$availabilityplugins = \core\plugininfo\availability::get_enabled_plugins();
$groupavailability = $this->_features->groups && array_key_exists('group', $availabilityplugins);
$groupingavailability = $this->_features->groupings && array_key_exists('grouping', $availabilityplugins);
if ($groupavailability || $groupingavailability) {
// When creating the button, we need to set type=button to prevent it behaving as a submit.
$mform->addElement('static', 'restrictgroupbutton', '',
html_writer::tag('button', get_string('restrictbygroup', 'availability'),
array('id' => 'restrictbygroup', 'disabled' => 'disabled', 'class' => 'btn btn-secondary')));
html_writer::tag('button', get_string('restrictbygroup', 'availability'), [
'id' => 'restrictbygroup',
'type' => 'button',
'disabled' => 'disabled',
'class' => 'btn btn-secondary',
'data-groupavailability' => $groupavailability,
'data-groupingavailability' => $groupingavailability
])
);
}
// Availability field. This is just a textarea; the user interface
@@ -8,32 +8,26 @@ Feature: Edit completion settings of an activity
Given the following "courses" exist:
| fullname | shortname | enablecompletion |
| Course 1 | C1 | 1 |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I add a "Page" to section "1" and I fill the form with:
| Name | TestPage |
| Description | x |
| Page content | x |
| Completion tracking | 2 |
| Require view | 1 |
And I am on "Course 1" course homepage
And the following "activities" exist:
| activity | course | idnumber | intro | name | completion | completionview |
| page | C1 | p1 | x | TestPage | 2 | 1 |
Scenario: Completion is not locked when the activity has not yet been viewed
Given I click on "Edit settings" "link" in the "TestPage" activity
Given I am on the TestPage "Page Activity editing" page logged in as admin
When I expand all fieldsets
Then I should see "Completion tracking"
And I should not see "Completion options locked"
Scenario: Completion is locked after the activity has been viewed
Given I follow "TestPage"
When I follow "Edit settings"
Given I am on the TestPage "Page Activity" page logged in as admin
When I am on the TestPage "Page Activity editing" page
And I expand all fieldsets
Then I should see "Completion options locked"
@javascript
Scenario: Pressing the unlock button allows the user to edit completion settings
Given I follow "TestPage"
When I navigate to "Edit settings" in current page administration
Given I am on the TestPage "Page Activity" page logged in as admin
When I am on the TestPage "Page Activity editing" page
And I expand all fieldsets
And I press "Unlock completion options"
Then I should see "Completion options unlocked"
@@ -45,8 +39,8 @@ Feature: Edit completion settings of an activity
@javascript
Scenario: Even when completion is locked, the user can still set the date
Given I follow "TestPage"
When I navigate to "Edit settings" in current page administration
Given I am on the TestPage "Page Activity" page logged in as admin
And I am on the TestPage "Page Activity editing" page
And I expand all fieldsets
When I click on "id_completionexpected_enabled" "checkbox"
And I set the field "id_completionexpected_year" to "2013"
+9 -7
View File
@@ -51,8 +51,8 @@ Feature: Display and choose from the available activities in course
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "help" "core_course > Activity chooser screen"
And I should see "Back" in the "help" "core_course > Activity chooser screen"
When I click on "Back" "button" in the "help" "core_course > Activity chooser screen"
Then "modules" "core_course > Activity chooser screen" should exist
And "help" "core_course > Activity chooser screen" should not exist
Then "modules" "core_course > Activity chooser screen" should be visible
And "help" "core_course > Activity chooser screen" should not be visible
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue"
@@ -123,24 +123,26 @@ Feature: Display and choose from the available activities in course
And I should see "Lesson" in the "Add an activity or resource" "dialogue"
When I set the field "search" to ""
And I should not see "1 results found" in the "Add an activity or resource" "dialogue"
Then ".searchresultscontainer" "css_element" should not exist
Then ".searchresultscontainer" "css_element" should not be visible
And ".optionscontainer" "css_element" should exist
Scenario: Teacher can not see a "clear" button if a search query is not entered in the activity chooser search bar
When I click on "Add an activity or resource" "button" in the "Topic 1" "section"
Then "Clear search input" "button" should not exist
Then "Clear search input" "button" should not be visible
Scenario: Teacher can see a "clear" button after entering a search query in the activity chooser search bar
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
When I set the field "search" to "Search query"
Then "Clear search input" "button" should not exist
Then "Clear search input" "button" should not be visible
Scenario: Teacher can not see a "clear" button if the search query is removed in the activity chooser search bar
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
And I set the field "search" to "Search query"
And "Clear search input" "button" should exist
When I set the field "search" to ""
Then "Clear search input" "button" should not exist
# Waiting for the animation to hide the button to finish.
And I wait "1" seconds
Then "Clear search input" "button" should not be visible
Scenario: Teacher can instantly remove the search query from the activity search bar by clicking on the "clear" button
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
@@ -148,7 +150,7 @@ Feature: Display and choose from the available activities in course
And I should see "results found" in the "Add an activity or resource" "dialogue"
When I click on "Clear search input" "button"
Then I should not see "Search query"
And ".searchresultscontainer" "css_element" should not exist
And ".searchresultscontainer" "css_element" should not be visible
And ".optionscontainer" "css_element" should exist
Scenario: Teacher gets the base case for the Activity Chooser tab mode
@@ -45,9 +45,7 @@ Feature: Activity navigation involving activities with access restrictions
@javascript
Scenario: Activity navigation involving activities with access restrictions
Given I log in as "student1"
And I am on "Course 1" course homepage
When I follow "Page 1"
Given I am on the "Page 1" "page activity" page logged in as student1
Then I should see "Page 2" in the "#next-activity-link" "css_element"
# Activity that has access restriction should not show up in the dropdown.
And the "Jump to..." select box should not contain "Page 3"
@@ -56,7 +54,7 @@ Feature: Activity navigation involving activities with access restrictions
And I should see "Page 2" in the "#prev-activity-link" "css_element"
And the "Jump to..." select box should not contain "Page 3"
# Navigate to Page 2.
And I follow "Page 2"
And I click on "Page 2" "link" in the "page-content" "region"
# Since Page 2 has now been viewed and deemed completed, Page 3 can now be accessed.
And I should see "Page 3" in the "#next-activity-link" "css_element"
And the "Jump to..." select box should contain "Page 3"
-38
View File
@@ -1346,44 +1346,6 @@ class behat_course extends behat_base {
return $this->getSession()->getPage()->findButton(get_string('turneditingoff')) ? true : false;
}
/**
* Returns the id of the category with the given idnumber.
*
* Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
*
* @param string $idnumber
* @return string
* @throws ExpectationException
*/
protected function get_category_id($idnumber) {
global $DB;
try {
return $DB->get_field('course_categories', 'id', array('idnumber' => $idnumber), MUST_EXIST);
} catch (dml_missing_record_exception $ex) {
throw new ExpectationException(sprintf("There is no category in the database with the idnumber '%s'", $idnumber),
$this->getSession());
}
}
/**
* Returns the id of the course with the given idnumber.
*
* Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
*
* @param string $idnumber
* @return string
* @throws ExpectationException
*/
protected function get_course_id($idnumber) {
global $DB;
try {
return $DB->get_field('course', 'id', array('idnumber' => $idnumber), MUST_EXIST);
} catch (dml_missing_record_exception $ex) {
throw new ExpectationException(sprintf("There is no course in the database with the idnumber '%s'", $idnumber),
$this->getSession());
}
}
/**
* Returns the category node from within the listing on the management page.
*
+2 -5
View File
@@ -20,15 +20,12 @@ Feature: Managers can create courses
When I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add the "Latest announcements" block
Then "Latest announcements" "block" should exist
And I follow "Announcements"
And I am on the Announcements "forum activity" page
And "Add a new topic" "link" should exist
And "Subscription mode > Forced subscription" "link" should not exist in current page administration
And "Subscription mode > Forced subscription" "text" should exist in current page administration
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Announcements"
And I am on the Announcements "forum activity" page logged in as student1
And "Add a new topic" "link" should not exist
And "Forced subscription" "text" should exist in current page administration
@@ -20,8 +20,7 @@ Feature: View subfolders in a course in-line
| Name | Test folder |
| Display folder contents | On a separate page |
| Show subfolders expanded | |
And I should see "Test folder"
And I follow "Test folder"
And I am on the "Test folder" "folder activity" page
And I press "Edit"
And I press "Create folder"
And I set the field "New folder name" to "Test subfolder 1"
@@ -32,7 +31,7 @@ Feature: View subfolders in a course in-line
Scenario: Add a folder with two subfolders - view on separate page
Given I am on "Course 1" course homepage
And I should not see "Test subfolder 1"
And I follow "Test folder"
And I am on the "Test folder" "folder activity" page
And I should see "Test subfolder 1"
And I press "Edit"
And I press "Create folder"
@@ -41,13 +40,13 @@ Feature: View subfolders in a course in-line
And I press "Save changes"
When I am on "Course 1" course homepage
Then I should not see "Test subfolder 2"
And I follow "Test folder"
And I am on the "Test folder" "folder activity" page
And I should see "Test subfolder 2"
Given I navigate to "Edit settings" in current page administration
And I am on the "Test folder" "folder activity editing" page
And I set the field "Show subfolders expanded" to "1"
When I am on "Course 1" course homepage
Then I should not see "Test subfolder 2"
And I follow "Test folder"
And I am on the "Test folder" "folder activity" page
And I should see "Test subfolder 2"
@javascript
@@ -63,8 +62,7 @@ Feature: View subfolders in a course in-line
And I press "Save and return to course"
Then I should see "Test subfolder 1"
And I should not see "Test sub subfolder"
Given I open "Test folder" actions menu
When I click on "Edit settings" "link" in the "Test folder" activity
And I am on the "Test folder" "folder activity editing" page
And I set the field "Show subfolders expanded" to "1"
And I press "Save and return to course"
Then I should see "Test subfolder 1"
+69 -10
View File
@@ -4550,7 +4550,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 0,
'limit' => 0,
'offset' => 0,
'expecteddbqueries' => 1,
'expecteddbqueries' => 4,
'expectedresult' => $buildexpectedresult(0, 0)
],
'less than query limit' => [
@@ -4558,7 +4558,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 2,
'limit' => 0,
'offset' => 0,
'expecteddbqueries' => 1,
'expecteddbqueries' => 2,
'expectedresult' => $buildexpectedresult(2, 0)
],
'more than query limit' => [
@@ -4566,7 +4566,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 7,
'limit' => 0,
'offset' => 0,
'expecteddbqueries' => 3,
'expecteddbqueries' => 4,
'expectedresult' => $buildexpectedresult(7, 0)
],
'limit less than query limit' => [
@@ -4574,7 +4574,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 7,
'limit' => 2,
'offset' => 0,
'expecteddbqueries' => 1,
'expecteddbqueries' => 2,
'expectedresult' => $buildexpectedresult(2, 0)
],
'limit less than query limit with offset' => [
@@ -4582,7 +4582,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 7,
'limit' => 2,
'offset' => 2,
'expecteddbqueries' => 1,
'expecteddbqueries' => 2,
'expectedresult' => $buildexpectedresult(2, 2)
],
'limit less than total' => [
@@ -4590,7 +4590,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 9,
'limit' => 6,
'offset' => 0,
'expecteddbqueries' => 2,
'expecteddbqueries' => 3,
'expectedresult' => $buildexpectedresult(6, 0)
],
'less results than limit' => [
@@ -4598,7 +4598,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 9,
'limit' => 20,
'offset' => 0,
'expecteddbqueries' => 3,
'expecteddbqueries' => 4,
'expectedresult' => $buildexpectedresult(9, 0)
],
'less results than limit exact divisible' => [
@@ -4606,7 +4606,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 9,
'limit' => 20,
'offset' => 0,
'expecteddbqueries' => 4,
'expecteddbqueries' => 5,
'expectedresult' => $buildexpectedresult(9, 0)
],
'less results than limit with offset' => [
@@ -4614,7 +4614,7 @@ class core_course_courselib_testcase extends advanced_testcase {
'totalcourses' => 9,
'limit' => 10,
'offset' => 5,
'expecteddbqueries' => 2,
'expecteddbqueries' => 3,
'expectedresult' => $buildexpectedresult(4, 5)
],
];
@@ -5442,7 +5442,16 @@ class core_course_courselib_testcase extends advanced_testcase {
// Every course accessed, order by shortname DESC. The last create course ($course[2]) should have the greater shortname.
$result = course_get_recent_courses($student->id, 0, 0, 'shortname DESC');
$this->assertCount(3, $result);
$this->assertEquals($courses[2]->id, array_shift($result)->id);
$this->assertEquals($courses[2]->id, array_values($result)[0]->id);
$this->assertEquals($courses[1]->id, array_values($result)[1]->id);
$this->assertEquals($courses[0]->id, array_values($result)[2]->id);
// Every course accessed, order by shortname ASC.
$result = course_get_recent_courses($student->id, 0, 0, 'shortname ASC');
$this->assertCount(3, $result);
$this->assertEquals($courses[0]->id, array_values($result)[0]->id);
$this->assertEquals($courses[1]->id, array_values($result)[1]->id);
$this->assertEquals($courses[2]->id, array_values($result)[2]->id);
$guestcourse = $generator->create_course(
(object)array('shortname' => 'guestcourse',
@@ -5464,6 +5473,56 @@ class core_course_courselib_testcase extends advanced_testcase {
$this->assertArrayNotHasKey($courses[0]->id, $result);
}
/**
* Test the validation of the sort value in course_get_recent_courses().
*
* @dataProvider course_get_recent_courses_sort_validation_provider
* @param string $sort The sort value
* @param string $expectedexceptionmsg The expected exception message
*/
public function test_course_get_recent_courses_sort_validation(string $sort, string $expectedexceptionmsg) {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
if (!empty($expectedexceptionmsg)) {
$this->expectException('invalid_parameter_exception');
$this->expectExceptionMessage($expectedexceptionmsg);
}
course_get_recent_courses($user->id, 0, 0, $sort);
}
/**
* Data provider for test_course_get_recent_courses_sort_validation().
*
* @return array
*/
function course_get_recent_courses_sort_validation_provider() {
return [
'Invalid sort format (SQL injection attempt)' =>
[
'shortname DESC LIMIT 1--',
'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].',
],
'Sort uses \'sort by\' field that does not exist' =>
[
'shortname DESC, xyz ASC',
'Invalid field in the sort parameter, allowed fields: id, idnumber, summary, summaryformat, ' .
'startdate, enddate, category, shortname, fullname, timeaccess, component, visible.',
],
'Sort uses invalid value for the sorting direction' =>
[
'shortname xyz, lastaccess',
'Invalid sort direction in the sort parameter, allowed values: asc, desc.',
],
'Valid sort format' =>
[
'shortname asc, timeaccess',
''
]
];
}
/**
* Test the course_get_recent_courses function.
*/
+175 -4
View File
@@ -2765,7 +2765,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
/**
* Test cases for the get_enrolled_courses_by_timeline_classification test.
*/
public function get_get_enrolled_courses_by_timeline_classification_test_cases() {
public function get_get_enrolled_courses_by_timeline_classification_test_cases():array {
$now = time();
$day = 86400;
@@ -2864,6 +2864,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'future',
'limit' => 2,
'offset' => 0,
'sort' => 'shortname ASC',
'expectedcourses' => [],
'expectednextoffset' => 0
],
@@ -2873,6 +2874,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'future',
'limit' => 0,
'offset' => 0,
'sort' => 'shortname ASC',
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
'expectednextoffset' => 15
],
@@ -2881,6 +2883,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'future',
'limit' => 2,
'offset' => 0,
'sort' => 'shortname ASC',
'expectedcourses' => ['afuture', 'bfuture'],
'expectednextoffset' => 4
],
@@ -2889,6 +2892,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'future',
'limit' => 2,
'offset' => 2,
'sort' => 'shortname ASC',
'expectedcourses' => ['bfuture', 'cfuture'],
'expectednextoffset' => 7
],
@@ -2897,6 +2901,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'future',
'limit' => 5,
'offset' => 0,
'sort' => 'shortname ASC',
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
'expectednextoffset' => 13
],
@@ -2905,6 +2910,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'future',
'limit' => 10,
'offset' => 0,
'sort' => 'shortname ASC',
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
'expectednextoffset' => 15
],
@@ -2913,6 +2919,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'future',
'limit' => 10,
'offset' => 5,
'sort' => 'shortname ASC',
'expectedcourses' => ['cfuture', 'dfuture', 'efuture'],
'expectednextoffset' => 15
],
@@ -2921,6 +2928,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'all',
'limit' => 0,
'offset' => 0,
'sort' => 'shortname ASC',
'expectedcourses' => [
'afuture',
'ainprogress',
@@ -2945,6 +2953,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'all',
'limit' => 5,
'offset' => 0,
'sort' => 'shortname ASC',
'expectedcourses' => [
'afuture',
'ainprogress',
@@ -2959,6 +2968,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => 'shortname ASC',
'expectedcourses' => [
'bpast',
'cfuture',
@@ -2973,9 +2983,161 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
'classification' => 'all',
'limit' => 5,
'offset' => 50,
'sort' => 'shortname ASC',
'expectedcourses' => [],
'expectednextoffset' => 50
],
'all limit and offset with sort ul.timeaccess desc' => [
'coursedata' => $coursedata,
'classification' => 'inprogress',
'limit' => 0,
'offset' => 0,
'sort' => 'ul.timeaccess desc',
'expectedcourses' => [
'ainprogress',
'binprogress',
'cinprogress',
'dinprogress',
'einprogress'
],
'expectednextoffset' => 15
],
'all limit and offset with sort sql injection for sort or 1==1' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => 'ul.timeaccess desc or 1==1',
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with sql injection of sort a custom one' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "ul.timeaccess LIMIT 1--",
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong sort direction' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "ul.timeaccess abcdasc",
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong sort direction' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "ul.timeaccess.foo ascd",
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong sort param' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "foobar",
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong field name' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "ul.foobar",
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong field separator' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "ul.timeaccess.foo",
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong field separator #' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "ul#timeaccess",
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong field separator $' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => 'ul$timeaccess',
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with wrong field name' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => 'timeaccess123',
'expectedcourses' => [],
'expectednextoffset' => 0,
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
],
'all limit and offset with no sort direction for ul' => [
'coursedata' => $coursedata,
'classification' => 'inprogress',
'limit' => 0,
'offset' => 0,
'sort' => "ul.timeaccess",
'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'],
'expectednextoffset' => 15,
],
'all limit and offset with valid field name and no prefix, test for ul' => [
'coursedata' => $coursedata,
'classification' => 'inprogress',
'limit' => 0,
'offset' => 0,
'sort' => "timeaccess",
'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'],
'expectednextoffset' => 15,
],
'all limit and offset with valid field name and no prefix' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "fullname",
'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'],
'expectednextoffset' => 10,
],
'all limit and offset with valid field name and no prefix and with sort direction' => [
'coursedata' => $coursedata,
'classification' => 'all',
'limit' => 5,
'offset' => 5,
'sort' => "fullname desc",
'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'],
'expectednextoffset' => 10,
],
];
}
@@ -2987,16 +3149,20 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
* @param string $classification Timeline classification
* @param int $limit Maximum number of results
* @param int $offset Offset the unfiltered courses result set by this amount
* @param string $sort sort the courses
* @param array $expectedcourses Expected courses in result
* @param int $expectednextoffset Expected next offset value in result
* @param string|null $expectedexception Expected exception string
*/
public function test_get_enrolled_courses_by_timeline_classification(
$coursedata,
$classification,
$limit,
$offset,
$sort,
$expectedcourses,
$expectednextoffset
$expectednextoffset,
$expectedexception = null
) {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
@@ -3013,6 +3179,11 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$this->setUser($student);
if (isset($expectedexception)) {
$this->expectException('coding_exception');
$this->expectExceptionMessage($expectedexception);
}
// NOTE: The offset applies to the unfiltered full set of courses before the classification
// filtering is done.
// E.g. In our example if an offset of 2 is given then it would mean the first
@@ -3021,7 +3192,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$classification,
$limit,
$offset,
'shortname ASC'
$sort
);
$result = external_api::clean_returnvalue(
core_course_external::get_enrolled_courses_by_timeline_classification_returns(),
@@ -3032,7 +3203,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
return $course['shortname'];
}, $result['courses']);
$this->assertEquals($expectedcourses, $actual);
$this->assertEqualsCanonicalizing($expectedcourses, $actual);
$this->assertEquals($expectednextoffset, $result['nextoffset']);
}
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core_customfield/form",["jquery","core/str","core/notification","core/ajax","core/templates","core/sortable_list","core/inplace_editable"],function(a,b,c,d,e,f){var g=function(f,g,h,i,j){b.get_strings([{key:"confirm"},{key:"confirmdelete"+g,component:"core_customfield"},{key:"yes"},{key:"no"}]).done(function(b){c.confirm(b[0],b[1],b[2],b[3],function(){var b="field"===g?"core_customfield_delete_field":"core_customfield_delete_category";d.call([{methodname:b,args:{id:f}},{methodname:"core_customfield_reload_template",args:{component:h,area:i,itemid:j}}])[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);return null}).fail(c.exception)})}).fail(c.exception)},h=function(b,f,g){var h=d.call([{methodname:"core_customfield_create_category",args:{component:b,area:f,itemid:g}},{methodname:"core_customfield_reload_template",args:{component:b,area:f,itemid:g}}]),i;h[0].then(function(a){i=a;return null}).fail(c.exception);h[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);window.location.href="#category-"+i;return null}).fail(c.exception)};return{init:function init(){var e=a("#customfield_catlist"),i=e.attr("data-component"),j=e.attr("data-area"),k=e.attr("data-itemid");a("[data-role=deletefield]").on("click",function(b){g(a(this).attr("data-id"),"field",i,j,k);b.preventDefault()});a("[data-role=deletecategory]").on("click",function(b){g(a(this).attr("data-id"),"category",i,j,k);b.preventDefault()});a("[data-role=addnewcategory]").on("click",function(){h(i,j,k)});var l=function(a){return a.closest("[data-category-id]").find("[data-inplaceeditable][data-itemtype=category][data-component=core_customfield]").attr("data-value")},m=new f(a("#customfield_catlist .categorieslist"),{moveHandlerSelector:".movecategory [data-drag-type=move]"});m.getElementName=function(b){return a.Deferred().resolve(l(b))};a("[data-category-id]").on("sortablelist-drop",function(a,b){if(b.positionChanged){var e=d.call([{methodname:"core_customfield_move_category",args:{id:b.element.data("category-id"),beforeid:b.targetNextElement.data("category-id")}}]);e[0].fail(c.exception)}a.stopPropagation()});var n=new f(a("#customfield_catlist .fieldslist tbody"),{moveHandlerSelector:".movefield [data-drag-type=move]"});n.getDestinationName=function(c,d){if(!d.length){return b.get_string("totopofcategory","customfield",l(c))}else if(d.attr("data-field-name")){return b.get_string("afterfield","customfield",d.attr("data-field-name"))}else{return a.Deferred().resolve("")}};a("[data-field-name]").on("sortablelist-drop",function(a,b){a.stopPropagation();if(b.positionChanged){var e=d.call([{methodname:"core_customfield_move_field",args:{id:b.element.data("field-id"),beforeid:b.targetNextElement.data("field-id"),categoryid:+b.targetList.closest("[data-category-id]").attr("data-category-id")}}]);e[0].fail(c.exception)}}).on("sortablelist-drag",function(d){d.stopPropagation();b.get_string("therearenofields","core_customfield").then(function(b){a("#customfield_catlist .categorieslist").children().each(function(){var c=a(this).find(a(".field")),d=a(this).find(a(".nofields"));if(!c.length&&!d.length){a(this).find("tbody").append("<tr class=\"nofields\"><td colspan=\"5\">"+b+"</td></tr>")}if(c.length&&d.length){d.remove()}});return null}).fail(c.exception)});a("[data-category-id], [data-field-name]").on("sortablelist-dragstart",function(b,c){setTimeout(function(){a(".sortable-list-is-dragged").width(c.element.width())},501)})}}});
define ("core_customfield/form",["jquery","core/str","core/notification","core/ajax","core/templates","core/sortable_list","core/pending","core/inplace_editable"],function(a,b,c,d,e,f,g){var h=function(f,h,i,j,k){var l=new g("core_customfield/form:confirmDelete");b.get_strings([{key:"confirm"},{key:"confirmdelete"+h,component:"core_customfield"},{key:"yes"},{key:"no"}]).then(function(b){c.confirm(b[0],b[1],b[2],b[3],function(){var b=new g("core_customfield/form:confirmDelete"),l="field"===h?"core_customfield_delete_field":"core_customfield_delete_category";d.call([{methodname:l,args:{id:f}},{methodname:"core_customfield_reload_template",args:{component:i,area:j,itemid:k}}])[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);return null}).then(b.resolve).catch(c.exception)})}).then(l.resolve).catch(c.exception)},i=function(b,f,h){var i=new g("core_customfield/form:confirmDelete"),j=d.call([{methodname:"core_customfield_create_category",args:{component:b,area:f,itemid:h}},{methodname:"core_customfield_reload_template",args:{component:b,area:f,itemid:h}}]),k;j[0].then(function(a){k=a;return null}).catch(c.exception);j[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);window.location.href="#category-"+k;return null}).catch(c.exception);Promise.all(j).then(i.resolve).catch()};return{init:function init(){var e=a("#customfield_catlist"),j=e.attr("data-component"),k=e.attr("data-area"),l=e.attr("data-itemid");a("[data-role=deletefield]").on("click",function(b){h(a(this).attr("data-id"),"field",j,k,l);b.preventDefault()});a("[data-role=deletecategory]").on("click",function(b){h(a(this).attr("data-id"),"category",j,k,l);b.preventDefault()});a("[data-role=addnewcategory]").on("click",function(){i(j,k,l)});var m=function(a){return a.closest("[data-category-id]").find("[data-inplaceeditable][data-itemtype=category][data-component=core_customfield]").attr("data-value")},n=new f(a("#customfield_catlist .categorieslist"),{moveHandlerSelector:".movecategory [data-drag-type=move]"});n.getElementName=function(b){return a.Deferred().resolve(m(b))};a("[data-category-id]").on("sortablelist-drop",function(a,b){if(b.positionChanged){var e=new g("core_customfield/form:categoryid:on:sortablelist-drop");d.call([{methodname:"core_customfield_move_category",args:{id:b.element.data("category-id"),beforeid:b.targetNextElement.data("category-id")}}])[0].then(e.resolve).catch(c.exception)}a.stopPropagation()});var o=new f(a("#customfield_catlist .fieldslist tbody"),{moveHandlerSelector:".movefield [data-drag-type=move]"});o.getDestinationName=function(c,d){if(!d.length){return b.get_string("totopofcategory","customfield",m(c))}else if(d.attr("data-field-name")){return b.get_string("afterfield","customfield",d.attr("data-field-name"))}else{return a.Deferred().resolve("")}};a("[data-field-name]").on("sortablelist-drop",function(a,b){a.stopPropagation();if(b.positionChanged){var e=new g("core_customfield/form:fieldname:on:sortablelist-drop");d.call([{methodname:"core_customfield_move_field",args:{id:b.element.data("field-id"),beforeid:b.targetNextElement.data("field-id"),categoryid:+b.targetList.closest("[data-category-id]").attr("data-category-id")}}])[0].then(e.resolve).catch(c.exception)}});a("[data-field-name]").on("sortablelist-drag",function(d){var e=new g("core_customfield/form:fieldname:on:sortablelist-drag");d.stopPropagation();b.get_string("therearenofields","core_customfield").then(function(b){a("#customfield_catlist .categorieslist").children().each(function(){var c=a(this).find(a(".field")),d=a(this).find(a(".nofields"));if(!c.length&&!d.length){a(this).find("tbody").append("<tr class=\"nofields\"><td colspan=\"5\">"+b+"</td></tr>")}if(c.length&&d.length){d.remove()}});return null}).then(e.resolve).catch(c.exception)});a("[data-category-id], [data-field-name]").on("sortablelist-dragstart",function(b,c){setTimeout(function(){a(".sortable-list-is-dragged").width(c.element.width())},501)})}}});
//# sourceMappingURL=form.min.js.map
File diff suppressed because one or more lines are too long
+66 -27
View File
@@ -21,9 +21,16 @@
* @copyright 2018 Toni Barbera
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates', 'core/sortable_list', 'core/inplace_editable'],
function(
$, Str, Notification, Ajax, Templates, SortableList) {
define([
'jquery',
'core/str',
'core/notification',
'core/ajax',
'core/templates',
'core/sortable_list',
'core/pending',
'core/inplace_editable',
], function($, Str, Notification, Ajax, Templates, SortableList, Pending) {
/**
* Display confirmation dialogue
@@ -35,25 +42,36 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
* @param {Number} itemid
*/
var confirmDelete = function(id, type, component, area, itemid) {
var pendingPromise = new Pending('core_customfield/form:confirmDelete');
Str.get_strings([
{'key': 'confirm'},
{'key': 'confirmdelete' + type, component: 'core_customfield'},
{'key': 'yes'},
{'key': 'no'},
]).done(function(s) {
])
.then(function(s) {
Notification.confirm(s[0], s[1], s[2], s[3], function() {
var pendingDeletePromise = new Pending('core_customfield/form:confirmDelete');
var func = (type === 'field') ? 'core_customfield_delete_field' : 'core_customfield_delete_category';
Ajax.call([
{methodname: func, args: {id: id}},
{methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
])[1].then(function(response) {
])[1]
.then(function(response) {
return Templates.render('core_customfield/list', response);
}).then(function(html, js) {
})
.then(function(html, js) {
Templates.replaceNode($('[data-region="list-page"]'), html, js);
return null;
}).fail(Notification.exception);
})
.then(pendingDeletePromise.resolve)
.catch(Notification.exception);
});
}).fail(Notification.exception);
return;
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
};
/**
@@ -64,24 +82,31 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
* @param {Number} itemid
*/
var createNewCategory = function(component, area, itemid) {
var pendingPromise = new Pending('core_customfield/form:confirmDelete');
var promises = Ajax.call([
{methodname: 'core_customfield_create_category', args: {component: component, area: area, itemid: itemid}},
{methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
]),
categoryid;
{methodname: 'core_customfield_create_category', args: {component: component, area: area, itemid: itemid}},
{methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
]);
var categoryid;
promises[0].then(function(response) {
categoryid = response;
return null;
}).fail(Notification.exception);
}).catch(Notification.exception);
promises[1].then(function(response) {
return Templates.render('core_customfield/list', response);
}).then(function(html, js) {
})
.then(function(html, js) {
Templates.replaceNode($('[data-region="list-page"]'), html, js);
window.location.href = '#category-' + categoryid;
return null;
}).fail(Notification.exception);
})
.catch(Notification.exception);
Promise.all(promises)
.then(pendingPromise.resolve)
.catch();
};
return {
@@ -89,18 +114,21 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
* Initialise the custom fields manager
*/
init: function() {
var mainlist = $('#customfield_catlist'),
component = mainlist.attr('data-component'),
area = mainlist.attr('data-area'),
itemid = mainlist.attr('data-itemid');
var mainlist = $('#customfield_catlist');
var component = mainlist.attr('data-component');
var area = mainlist.attr('data-area');
var itemid = mainlist.attr('data-itemid');
$("[data-role=deletefield]").on('click', function(e) {
confirmDelete($(this).attr('data-id'), 'field', component, area, itemid);
e.preventDefault();
});
$("[data-role=deletecategory]").on('click', function(e) {
confirmDelete($(this).attr('data-id'), 'category', component, area, itemid);
e.preventDefault();
});
$('[data-role=addnewcategory]').on('click', function() {
createNewCategory(component, area, itemid);
});
@@ -124,7 +152,8 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
$('[data-category-id]').on('sortablelist-drop', function(evt, info) {
if (info.positionChanged) {
var promises = Ajax.call([
var pendingPromise = new Pending('core_customfield/form:categoryid:on:sortablelist-drop');
Ajax.call([
{
methodname: 'core_customfield_move_category',
args: {
@@ -133,8 +162,9 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
}
},
]);
promises[0].fail(Notification.exception);
])[0]
.then(pendingPromise.resolve)
.catch(Notification.exception);
}
evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
});
@@ -158,7 +188,8 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
$('[data-field-name]').on('sortablelist-drop', function(evt, info) {
evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
if (info.positionChanged) {
var promises = Ajax.call([
var pendingPromise = new Pending('core_customfield/form:fieldname:on:sortablelist-drop');
Ajax.call([
{
methodname: 'core_customfield_move_field',
args: {
@@ -167,11 +198,17 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
categoryid: Number(info.targetList.closest('[data-category-id]').attr('data-category-id'))
},
},
]);
promises[0].fail(Notification.exception);
])[0]
.then(pendingPromise.resolve)
.catch(Notification.exception);
}
}).on('sortablelist-drag', function(evt) {
});
$('[data-field-name]').on('sortablelist-drag', function(evt) {
var pendingPromise = new Pending('core_customfield/form:fieldname:on:sortablelist-drag');
evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
// Refreshing fields tables.
Str.get_string('therearenofields', 'core_customfield').then(function(s) {
$('#customfield_catlist .categorieslist').children().each(function() {
@@ -187,7 +224,9 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
}
});
return null;
}).fail(Notification.exception);
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
});
$('[data-category-id], [data-field-name]').on('sortablelist-dragstart',
@@ -55,12 +55,16 @@ class field_controller extends \core_customfield\field_controller {
$mform->addElement('text', 'configdata[displaysize]', get_string('displaysize', 'customfield_text'), ['size' => 6]);
$mform->setType('configdata[displaysize]', PARAM_INT);
$mform->setDefault('configdata[displaysize]', 50);
if (!$this->get_configdata_property('displaysize')) {
$mform->setDefault('configdata[displaysize]', 50);
}
$mform->addRule('configdata[displaysize]', null, 'numeric', null, 'client');
$mform->addElement('text', 'configdata[maxlength]', get_string('maxlength', 'customfield_text'), ['size' => 6]);
$mform->setType('configdata[maxlength]', PARAM_INT);
$mform->setDefault('configdata[maxlength]', 1333);
if (!$this->get_configdata_property('maxlength')) {
$mform->setDefault('configdata[maxlength]', 1333);
}
$mform->addRule('configdata[maxlength]', null, 'numeric', null, 'client');
$mform->addElement('selectyesno', 'configdata[ispassword]', get_string('ispassword', 'customfield_text'));
@@ -113,7 +117,7 @@ class field_controller extends \core_customfield\field_controller {
$errors['configdata[link]'] = get_string('errorconfiglinkplaceholder', 'customfield_text');
} else if (!validateUrlSyntax(str_replace('$$', 'XYZ', $link), 's+H?S?F-E-u-P-a?I?p?f?q?r?')) {
// This validation is more strict than PARAM_URL - it requires the protocol and it must be either http or https.
$errors['configdata[link]'] = get_string('errorconfigdisplaysize', 'customfield_text');
$errors['configdata[link]'] = get_string('errorconfiglinksyntax', 'customfield_text');
}
}
}
+7
View File
@@ -1,6 +1,13 @@
This files describes API changes in /dataformat/ download system,
information provided here is intended especially for developers.
=== 3.9.8 ===
* Optional $callback for the following \core\dataformat methods now receive a second argument to define
whether the current format writer supports HTML:
- download_data()
- write_data()
- write_data_to_filearea()
=== 3.9 ===
* The following methods have been added to the base dataformat class to allow instances to export to a local
file. They can be overridden in extending classes to define how files should be created:
+6 -2
View File
@@ -188,7 +188,9 @@ $removeenabled = $canunenrol ? '' : 'disabled="disabled"';
</td>
<td id="buttonscell">
<div id="addcontrols">
<input name="add" <?php echo $addenabled; ?> id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
<input class="btn btn-secondary" name="add" <?php echo $addenabled; ?> id="add" type="submit"
value="<?php echo $OUTPUT->larrow() . '&nbsp;' . get_string('add'); ?>"
title="<?php print_string('add'); ?>" /><br />
<div class="enroloptions">
@@ -205,7 +207,9 @@ $removeenabled = $canunenrol ? '' : 'disabled="disabled"';
</div>
<div id="removecontrols">
<input name="remove" id="remove" <?php echo $removeenabled; ?> type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
<input class="btn btn-secondary" name="remove" id="remove" <?php echo $removeenabled; ?> type="submit"
value="<?php echo get_string('remove') . '&nbsp;' . $OUTPUT->rarrow(); ?>"
title="<?php print_string('remove'); ?>" />
</div>
</td>
<td id="potentialcell">
+2 -1
View File
@@ -1055,7 +1055,8 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
$this->assertArrayHasKey('customint2', $errors);
// Test with valid data.
$validgroup = reset($groups);
reset($groups);
$validgroup = key($groups);
$data = [
'customint1' => [$metacourse2->id],
'customint2' => $validgroup
@@ -98,6 +98,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
And I log in as "student1"
And I am on "Course 1" course homepage
And I press "Enrol me"
And I should see "You are enrolled in the course"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
@@ -118,6 +119,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
And I log in as "student1"
And I am on "Course 1" course homepage
And I press "Enrol me"
And I should see "You are enrolled in the course"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
+126
View File
@@ -1036,6 +1036,59 @@ class core_enrollib_testcase extends advanced_testcase {
$this->assertEquals([$course1->id, $course2->id, $course3->id, $course4->id], array_keys($courses));
}
/**
* Data provider for {@see test_enrol_get_my_courses_by_time}
*
* @return array
*/
public function enrol_get_my_courses_by_time_provider(): array {
return [
'No start or end time' =>
[null, null, true],
'Start time now, no end time' =>
[0, null, true],
'Start time now, end time in the future' =>
[0, MINSECS, true],
'Start time in the past, no end time' =>
[-MINSECS, null, true],
'Start time in the past, end time in the future' =>
[-MINSECS, MINSECS, true],
'Start time in the past, end time in the past' =>
[-DAYSECS, -HOURSECS, false],
'Start time in the future' =>
[MINSECS, null, false],
];
}
/**
* Test that expected course enrolments are returned when they have timestart / timeend specified
*
* @param int|null $timestartoffset Null for 0, otherwise offset from current time
* @param int|null $timeendoffset Null for 0, otherwise offset from current time
* @param bool $expectreturn
*
* @dataProvider enrol_get_my_courses_by_time_provider
*/
public function test_enrol_get_my_courses_by_time(?int $timestartoffset, ?int $timeendoffset, bool $expectreturn): void {
$this->resetAfterTest();
$time = time();
$timestart = $timestartoffset === null ? 0 : $time + $timestartoffset;
$timeend = $timeendoffset === null ? 0 : $time + $timeendoffset;
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', $timestart, $timeend);
$this->setUser($user);
$courses = enrol_get_my_courses();
if ($expectreturn) {
$this->assertCount(1, $courses);
$this->assertEquals($course->id, reset($courses)->id);
} else {
$this->assertEmpty($courses);
}
}
/**
* test_course_users
*
@@ -1362,4 +1415,77 @@ class core_enrollib_testcase extends advanced_testcase {
$durationinday = $duration / DAYSECS;
$this->assertEquals(9, $durationinday);
}
/**
* Test get_enrolled_with_capabilities_join cannotmatchanyrows attribute.
*
* @dataProvider get_enrolled_with_capabilities_join_cannotmatchanyrows_data()
* @param string $capability the tested capability
* @param bool $useprohibit if the capability must be assigned to prohibit
* @param int $expectedmatch expected cannotmatchanyrows value
* @param int $expectedcount expceted count value
*/
public function test_get_enrolled_with_capabilities_join_cannotmatchanyrows(
string $capability,
bool $useprohibit,
int $expectedmatch,
int $expectedcount
) {
global $DB, $CFG;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
$roleid = $CFG->defaultuserroleid;
// Override capability if necessary.
if ($useprohibit && $capability) {
assign_capability($capability, CAP_PROHIBIT, $roleid, $context);
}
// Check if we must enrol or not.
$this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$join = get_enrolled_with_capabilities_join($context, '', $capability);
// Execute query.
$sql = "SELECT COUNT(DISTINCT u.id)
FROM {user} u {$join->joins}
WHERE {$join->wheres}";
$countrecords = $DB->count_records_sql($sql, $join->params);
// Validate cannotmatchanyrows.
$this->assertEquals($expectedmatch, $join->cannotmatchanyrows);
$this->assertEquals($expectedcount, $countrecords);
}
/**
* Data provider for test_get_enrolled_with_capabilities_join_cannotmatchanyrows
*
* @return @array of testing scenarios
*/
public function get_enrolled_with_capabilities_join_cannotmatchanyrows_data() {
return [
'no prohibits, no capability' => [
'capability' => '',
'useprohibit' => false,
'expectedmatch' => 0,
'expectedcount' => 1,
],
'no prohibits with capability' => [
'capability' => 'moodle/course:manageactivities',
'useprohibit' => false,
'expectedmatch' => 0,
'expectedcount' => 1,
],
'prohibits with capability' => [
'capability' => 'moodle/course:manageactivities',
'useprohibit' => true,
'expectedmatch' => 1,
'expectedcount' => 0,
],
];
}
}
@@ -24,10 +24,7 @@ Feature: Render H5P content using filters
@javascript @external
Scenario: Render an external H5P content URL.
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "PageName1"
And I navigate to "Edit settings" in current page administration
Given I am on the PageName1 "page activity editing" page logged in as teacher1
And I set the field "Page content" to "<div>Go for it</div>https://moodle.h5p.com/content/1290772960722742119/embed"
When I click on "Save and display" "button"
And I wait until the page is ready
@@ -36,10 +33,7 @@ Feature: Render H5P content using filters
@javascript
Scenario: Add an external H5P content URL in a link with the URL. Should be rendered.
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "PageName1"
And I navigate to "Edit settings" in current page administration
Given I am on the PageName1 "page activity editing" page logged in as teacher1
# This content won't be displayed, so this scenario shouldn't be labeled as external.
And I set the field "Page content" to "<a href='https://moodle.h5p.com/content/1290772960722742119/embed'>https://moodle.h5p.com/content/1290772960722742119/embed</a>"
When I click on "Save and display" "button"
@@ -48,10 +42,7 @@ Feature: Render H5P content using filters
Then I should see "Lorum ipsum"
Scenario: Add an external H5P content URL in a link with text. Shouldn't be rendered.
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "PageName1"
And I navigate to "Edit settings" in current page administration
Given I am on the PageName1 "page activity editing" page logged in as teacher1
# This content won't be displayed, so this scenario shouldn't be labeled as external.
And I set the field "Page content" to "<a href='https://moodle.h5p.com/content/1290772960722742119/embed'>Here you are the content</a>"
When I click on "Save and display" "button"
@@ -67,8 +58,7 @@ Feature: Render H5P content using filters
| Name | ipsumFile |
And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
And I press "Save and return to course"
And I follow "PageName1"
And I navigate to "Edit settings" in current page administration
And I am on the PageName1 "page activity editing" page
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
And I click on "Server files" "link" in the ".fp-repo-area" "css_element"
@@ -85,9 +75,7 @@ Feature: Render H5P content using filters
Then I should see "Lorum ipsum"
And I switch to the main frame
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I follow "PageName1"
And I am on the PageName1 "page activity" page logged in as student1
# Switch to iframe created by filter
And I switch to "h5p-iframe" class iframe
# Switch to iframe created by embed.php page
@@ -104,8 +92,7 @@ Feature: Render H5P content using filters
| Name | ipsumFile |
And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
And I press "Save and return to course"
And I follow "PageName1"
And I navigate to "Edit settings" in current page administration
And I am on the PageName1 "page activity editing" page
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
And I click on "Server files" "link" in the ".fp-repo-area" "css_element"
@@ -129,8 +116,7 @@ Feature: Render H5P content using filters
| Name | ipsumFileTeacher |
And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
And I press "Save and return to course"
And I follow "PageName1"
And I navigate to "Edit settings" in current page administration
And I am on the PageName1 "page activity editing" page
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
And I click on "Server files" "link" in the ".fp-repo-area" "css_element"
@@ -153,8 +139,7 @@ Feature: Render H5P content using filters
| Name | ipsumFile |
And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Select files" filemanager
And I press "Save and return to course"
And I follow "PageName2"
And I navigate to "Edit settings" in current page administration
And I am on the PageName2 "page activity editing" page
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
And I click on "Server files" "link" in the ".fp-repo-area" "css_element"
@@ -172,9 +157,7 @@ Feature: Render H5P content using filters
And I should see "Lorum ipsum"
And I switch to the main frame
And I log out
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "PageName1"
And I am on the PageName1 "page activity" page logged in as teacher1
# Switch to iframe created by filter
And I switch to "h5p-iframe" class iframe
# Switch to iframe created by embed.php page
+14 -2
View File
@@ -23,6 +23,16 @@ class grade_export_xml extends grade_export {
public $plugin = 'xml';
public $updatedgradesonly = false; // default to export ALL grades
/**
* Ensure we produce correctly formed XML content by encoding idnumbers appropriately
*
* @param string $idnumber
* @return string
*/
private static function xml_export_idnumber(string $idnumber): string {
return htmlspecialchars($idnumber, ENT_QUOTES | ENT_XML1);
}
/**
* To be implemented by child classes
* @param boolean $feedback
@@ -84,9 +94,11 @@ class grade_export_xml extends grade_export {
}
// only need id number
fwrite($handle, "\t\t<assignment>{$grade_item->idnumber}</assignment>\n");
$gradeitemidnumber = self::xml_export_idnumber($grade_item->idnumber);
fwrite($handle, "\t\t<assignment>{$gradeitemidnumber}</assignment>\n");
// this column should be customizable to use either student id, idnumber, uesrname or email.
fwrite($handle, "\t\t<student>{$user->idnumber}</student>\n");
$useridnumber = self::xml_export_idnumber($user->idnumber);
fwrite($handle, "\t\t<student>{$useridnumber}</student>\n");
// Format and display the grade in the selected display type (real, letter, percentage).
if (is_array($this->displaytype)) {
// Grades display type came from the return of export_bulk_export_data() on grade publishing.
+13 -5
View File
@@ -12,10 +12,12 @@ Feature: I need to export grades as xml
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
| student1 | Student | 1 | student1@example.com | s1 |
| student2 | Student | 2 | student2@example.com | 'Bill'&"Ben"<tag>Hello</tag> |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name | intro |
| assign | C1 | a1 | Test assignment name | Submit something! |
@@ -24,15 +26,21 @@ Feature: I need to export grades as xml
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name"
And I give the grade "42.00" to the user "Student 2" for the grade item "Test assignment name"
And I press "Save changes"
@javascript
Scenario: Export grades as text
Scenario: Export grades as XML
When I navigate to "Export > XML file" in the course gradebook
And I expand all fieldsets
And I set the field "Grade export decimal places" to "1"
And I press "Download"
Then I should see "s1"
And I should see "a1"
And I should see "80.0"
And I should not see "80.00"
Then I should see "s1" in the "//results//result[1]//student" "xpath_element"
And I should see "a1" in the "//results//result[1]//assignment" "xpath_element"
And I should see "80.0" in the "//results//result[1]//score" "xpath_element"
And I should not see "80.00" in the "//results//result[1]//score" "xpath_element"
# Ensure we have the encoded ID number of student 2.
And I should see "'Bill'&\"Ben\"<tag>Hello</tag>" in the "//results//result[2]//student" "xpath_element"
And I should see "a1" in the "//results//result[2]//assignment" "xpath_element"
And I should see "42.0" in the "//results//result[2]//score" "xpath_element"
And I should not see "42.00" in the "//results//result[2]//score" "xpath_element"

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