Compare commits

..

342 Commits

Author SHA1 Message Date
Eloy Lafuente (stronk7) 68aa19ba73 Moodle release 3.9.8 2021-07-10 18:55:57 +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
359 changed files with 7737 additions and 1424 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 -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
@@ -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>
@@ -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);
+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
@@ -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"
@@ -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"
+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
];
@@ -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
+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
+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,
],
];
}
}
+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"
@@ -156,6 +156,9 @@ class fetch extends external_api {
$hasgrade = $gradeitem->user_has_grade($gradeduser);
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
$instance = $gradeitem->get_advanced_grading_instance($USER, $grade);
if (!$instance) {
throw new moodle_exception('error:gradingunavailable', 'grading');
}
$controller = $instance->get_controller();
$definition = $controller->get_definition();
$fillings = $instance->get_guide_filling();
@@ -149,6 +149,10 @@ class fetch extends external_api {
$hasgrade = $gradeitem->user_has_grade($gradeduser);
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
$instance = $gradeitem->get_advanced_grading_instance($USER, $grade);
if (!$instance) {
throw new moodle_exception('error:gradingunavailable', 'grading');
}
$controller = $instance->get_controller();
$definition = $controller->get_definition();
$fillings = $instance->get_rubric_filling();
+1 -1
View File
@@ -231,7 +231,7 @@ if (!empty($method)) {
} else {
$tag = html_writer::tag('span', get_string('statusdraft', 'core_grading'), array('class' => 'status draft'));
}
echo $output->heading(s($definition->name) . ' ' . $tag, 3, 'definition-name');
echo $output->heading(format_string($definition->name) . ' ' . $tag, 3, 'definition-name');
echo $output->box($controller->render_preview($PAGE), 'definition-preview');
}
}
+4
View File
@@ -240,6 +240,10 @@ class gradeimport_csv_load_data {
$select = "{$field} = :{$field}";
}
// Validate if the user id value is numerical.
if ($field === 'id' && !is_numeric($value)) {
$errorkey = 'usermappingerror';
}
// Make sure the record exists and that there's only one matching record found.
$user = $DB->get_record_select('user', $select, array($userfields['field'] => $value), '*', MUST_EXIST);
} catch (dml_missing_record_exception $missingex) {
@@ -87,7 +87,7 @@ class provider implements
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preferences = get_user_preferences();
$preferences = get_user_preferences(null, null, $userid);
foreach ($preferences as $name => $value) {
$prefname = null;
$prefdescription = null;
+6 -2
View File
@@ -63,14 +63,18 @@ class gradereport_grader_privacy_testcase extends \core_privacy\tests\provider_t
* These preferences can be set on each course, but the value is shared in the whole site.
*/
public function test_export_user_preferences_single() {
// Add some user preferences.
// Create test user, add some preferences.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
set_user_preference('grade_report_showcalculations', 1, $user);
set_user_preference('grade_report_meanselection', GRADE_REPORT_MEAN_GRADED, $user);
set_user_preference('grade_report_studentsperpage', 50, $user);
// Validate exported data.
// Switch to admin user (so we can validate preferences of our test user are still exported).
$this->setAdminUser();
// Validate exported data for our test user.
provider::export_user_preferences($user->id);
$context = context_user::instance($user->id);
$writer = writer::with_context($context);
@@ -397,7 +397,7 @@ Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.cor
'<div class="{{CSS.FULLNAME}}">' +
'<label for="{{checkboxId}}">{{fullname}}</label>' +
'</div>' +
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{extrafields}}</div>' +
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{{extrafields}}}</div>' +
'</div>' +
'</div>'
);
File diff suppressed because one or more lines are too long
@@ -397,7 +397,7 @@ Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.cor
'<div class="{{CSS.FULLNAME}}">' +
'<label for="{{checkboxId}}">{{fullname}}</label>' +
'</div>' +
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{extrafields}}</div>' +
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{{extrafields}}}</div>' +
'</div>' +
'</div>'
);
@@ -395,7 +395,7 @@ Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.cor
'<div class="{{CSS.FULLNAME}}">' +
'<label for="{{checkboxId}}">{{fullname}}</label>' +
'</div>' +
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{extrafields}}</div>' +
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{{extrafields}}}</div>' +
'</div>' +
'</div>'
);
@@ -13,4 +13,4 @@ Feature: We can use the user report
And I am on "Course 1" course homepage
And I navigate to "View > User report" in the course gradebook
And I select "All users (0)" from the "Select all or one user" singleselect
Then I should see "No students enrolled in this course yet"
Then I should see "There are no students enrolled in this course."
+1 -1
View File
@@ -232,7 +232,7 @@ if ($editform->is_cancelled()) {
$newgroup = new stdClass();
$newgroup->courseid = $data->courseid;
$newgroup->name = $group['name'];
$newgroup->enablemessaging = $data->enablemessaging;
$newgroup->enablemessaging = $data->enablemessaging ?? 0;
$groupid = groups_create_group($newgroup);
$createdgroups[] = $groupid;
foreach($group['members'] as $user) {
+3
View File
@@ -44,6 +44,9 @@ $action = required_param('action', PARAM_ALPHA);
$factory = new factory();
$editor = $factory->get_editor();
// Set context to default system context.
$PAGE->set_context(null);
switch ($action) {
// Load list of libraries or details for library.
case 'libraries':
+16 -1
View File
@@ -265,7 +265,22 @@ class editor_framework implements H5peditorStorage {
* minorVersion as properties.
*/
public function alterLibraryFiles(&$files, $libraries): void {
// This is to be implemented when the renderer is used.
global $PAGE;
// Refactor dependency list.
$librarylist = [];
foreach ($libraries as $dependency) {
$librarylist[$dependency['machineName']] = [
'majorVersion' => $dependency['majorVersion'],
'minorVersion' => $dependency['minorVersion']
];
}
$renderer = $PAGE->get_renderer('core_h5p');
$embedtype = 'editor';
$renderer->h5p_alter_scripts($files['scripts'], $librarylist, $embedtype);
$renderer->h5p_alter_styles($files['styles'], $librarylist, $embedtype);
}
/**
+3 -13
View File
@@ -1093,20 +1093,10 @@ class framework implements \H5PFrameworkInterface {
* @param int $minorversion The library's minor version
*/
public function alterLibrarySemantics(&$semantics, $name, $majorversion, $minorversion) {
global $DB;
global $PAGE;
$library = $DB->get_record('h5p_libraries',
array(
'machinename' => $name,
'majorversion' => $majorversion,
'minorversion' => $minorversion,
)
);
if ($library) {
$library->semantics = json_encode($semantics);
$DB->update_record('h5p_libraries', $library);
}
$renderer = $PAGE->get_renderer('core_h5p');
$renderer->h5p_alter_semantics($semantics, $name, $majorversion, $minorversion);
}
/**
+2
View File
@@ -421,6 +421,8 @@ class helper {
* @return array The JS array converted to PHP array.
*/
public static function parse_js_array(string $jscontent): array {
// Convert all line-endings to UNIX format first.
$jscontent = str_replace(array("\r\n", "\r"), "\n", $jscontent);
$jsarray = preg_split('/,\n\s+/', substr($jscontent, 0, -1));
$jsarray = preg_replace('~{?\\n~', '', $jsarray);
+3 -1
View File
@@ -79,7 +79,9 @@ abstract class handler {
* Register the H5P autoloader.
*/
public static function register(): void {
spl_autoload_register([static::class, 'autoload']);
// Prepend H5P libraries in order to guarantee they are loaded first. Plugins using same libraries will need to use a
// different namespace if they want to use a different version.
spl_autoload_register([static::class, 'autoload'], true, true);
}
/**
+46 -1
View File
@@ -37,4 +37,49 @@ use plugin_renderer_base;
*/
class renderer extends plugin_renderer_base {
}
/**
* Alter which stylesheets are loaded for H5P.
* This is useful for adding custom styles or replacing existing ones.
*
* @param array|object $scripts List of stylesheets that will be loaded
* @param array $libraries Array of libraries indexed by the library's machineName
* @param string $embedtype Possible values: div, iframe, external, editor
*/
public function h5p_alter_styles(&$scripts, array $libraries, string $embedtype) {
}
/**
* Alter which scripts are loaded for H5P.
* This is useful for adding custom scripts or replacing existing ones.
*
* @param array|object $scripts List of JavaScripts that will be loaded
* @param array $libraries Array of libraries indexed by the library's machineName
* @param string $embedtype Possible values: div, iframe, external, editor
*/
public function h5p_alter_scripts(&$scripts, array $libraries, string $embedtype) {
}
/**
* Alter semantics before they are processed. This is useful for changing
* how the editor looks and how content parameters are filtered.
*
* @param object|object $semantics Semantics as object
* @param string $name Machine name of library
* @param int $majorversion Major version of library
* @param int $minorversion Minor version of library
*/
public function h5p_alter_semantics(&$semantics, $name, $majorversion, $minorversion) {
}
/**
* Alter parameters of H5P content after it has been filtered through semantics.
* This is useful for adapting the content to the current context.
*
* @param array|object $parameters The content parameters for the library
* @param string $name The machine readable name of the library
* @param int $majorversion Major version of the library
* @param int $minorversion Minor version of the library
*/
public function h5p_alter_filtered_parameters(&$parameters, string $name, int $majorversion, int $minorversion) {
}
}
+30 -1
View File
@@ -361,7 +361,7 @@ class player {
$cid = $this->get_cid();
// The filterParameters function should be called before getting the dependencyfiles because it rebuild content
// dependency cache and export file.
$settings['contents'][$cid]['jsonContent'] = $this->core->filterParameters($this->content);
$settings['contents'][$cid]['jsonContent'] = $this->get_filtered_parameters();
$files = $this->get_dependency_files();
if ($this->embedtype === 'div') {
@@ -402,15 +402,44 @@ class player {
return $settings;
}
/**
* Get filtered parameters, modifying them by the renderer if the theme implements the h5p_alter_filtered_parameters function.
*
* @return string Filtered parameters.
*/
private function get_filtered_parameters(): string {
global $PAGE;
$safeparams = $this->core->filterParameters($this->content);
$decodedparams = json_decode($safeparams);
$h5poutput = $PAGE->get_renderer('core_h5p');
$h5poutput->h5p_alter_filtered_parameters(
$decodedparams,
$this->content['library']['name'],
$this->content['library']['majorVersion'],
$this->content['library']['minorVersion']
);
$safeparams = json_encode($decodedparams);
return $safeparams;
}
/**
* Finds library dependencies of view
*
* @return array Files that the view has dependencies to
*/
private function get_dependency_files(): array {
global $PAGE;
$preloadeddeps = $this->core->loadContentDependencies($this->h5pid, 'preloaded');
$files = $this->core->getDependenciesFiles($preloadeddeps);
// Add additional asset files if required.
$h5poutput = $PAGE->get_renderer('core_h5p');
$h5poutput->h5p_alter_scripts($files['scripts'], $preloadeddeps, $this->embedtype);
$h5poutput->h5p_alter_styles($files['styles'], $preloadeddeps, $this->embedtype);
return $files;
}
Binary file not shown.
@@ -5,52 +5,52 @@
<json>
<![CDATA[
{
"fontFamily": "h5p-core-21",
"fontFamily": "h5p-core",
"description": "Font generated by IcoMoon.",
"majorVersion": 1,
"minorVersion": 1,
"version": "Version 1.1",
"fontId": "h5p-core-21",
"psName": "h5p-core-21",
"fontId": "h5p-core",
"psName": "h5p-core",
"subFamily": "Regular",
"fullName": "h5p-core-21"
"fullName": "h5p-core"
}
]]>
</json>
</metadata>
<defs>
<font id="h5p-core-21" horiz-adv-x="1024">
<font id="h5p-core" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe565;" glyph-name="arrow-down" data-tags="arrow-down" d="M234 389.669h556l-278-278z" />
<glyph unicode="&#xe566;" glyph-name="arrow-left" data-tags="arrow-left" d="M381-11.331v524l262-262z" />
<glyph unicode="&#xe58e;" glyph-name="colapse" data-tags="colapse" d="M512 447.336l256-256-60-60-196 196-196-196-60 60z" />
<glyph unicode="&#xe58f;" glyph-name="expand" data-tags="expand" d="M708 423.336l60-60-256-256-256 256 60 60 196-196z" />
<glyph unicode="&#xe600;" glyph-name="move" data-tags="move" d="M386.662 725.063h71.27v-71.27h-71.27v71.27zM566.067 725.063h71.27v-71.27h-71.27v71.27zM386.662 568.8h71.27v-71.27h-71.27v71.27zM566.067 568.8h71.27v-71.27h-71.27v71.27zM386.662 412.435h71.27v-71.27h-71.27v71.27zM566.067 412.435h71.27v-71.27h-71.27v71.27zM386.662 256.173h71.27v-71.27h-71.27v71.27zM566.067 256.173h71.27v-71.27h-71.27v71.27zM386.662 99.808h71.27v-71.27h-71.27v71.27zM566.067 99.808h71.27v-71.27h-71.27v71.27zM386.662-56.454h71.27v-71.27h-71.27v71.27zM566.067-56.454h71.27v-71.27h-71.27v71.27z" />
<glyph unicode="&#xe601;" glyph-name="check-mark" data-tags="check-mark" d="M454.299 245.924l-116.917 116.917-84.781-84.707 201.696-201.697 317.097 317.097-84.781 84.706z" />
<glyph unicode="&#xe888;" glyph-name="arrow-up-circle" data-tags="arrow-up-circle" d="M512 606.057c-148.616 0-264.722-120.75-260.077-269.367 0-125.395 88.241-232.212 208.991-255.434v213.636h-92.885c-13.933 0-13.933 9.288-9.288 18.577l139.327 171.838c4.645 9.288 13.933 9.288 23.221 4.645 0 0 4.645-4.645 4.645-4.645l139.327-171.838c9.288-9.288 4.645-18.577-9.288-18.577h-92.885v-213.636c143.972 32.51 232.212 171.838 199.703 315.808-23.221 120.75-130.039 204.347-250.789 208.991z" />
<glyph unicode="&#xe889;" glyph-name="info-circle" data-tags="info-circle" d="M512 601.601c-144.077 0-260.266-116.191-260.266-260.266s116.191-260.266 260.266-260.266 260.266 116.191 260.266 260.266v0c0 139.429-116.191 255.619-260.266 260.266zM470.171 550.478h88.305v-69.714h-88.305v69.714zM600.305 160.078h-181.257v51.123h51.123v162.666h-51.123v51.123h139.429v-218.438h46.477l-4.648-46.477z" />
<glyph unicode="&#xe88a;" glyph-name="search" data-tags="search" d="M772.098 125.51l-110.68 110.68c71.943 99.612 49.806 243.494-49.806 315.437s-243.494 44.27-315.437-55.339c-71.943-99.612-49.806-243.494 49.806-315.437 77.475-55.339 182.623-55.339 260.098 0l110.68-110.68c5.533-5.533 11.068-5.533 16.601 0 0 0 0 0 0 0l33.205 33.205c11.068 5.533 11.068 16.601 5.533 22.137 0 0 0 0 0 0zM478.795 202.985c-88.544 0-160.486 71.943-160.486 160.486s71.943 160.486 160.486 160.486 160.486-71.943 160.486-160.486-71.943-160.486-160.486-160.486v0z" />
<glyph unicode="&#xe88c;" glyph-name="fullscreen" data-tags="fullscreen" d="M368.55 490.521c5.737 5.737 0 5.737-5.737 5.737l-103.284 11.476c-5.737 5.737-11.476 0-11.476-5.737l11.476-109.021c0-5.737 5.737-5.737 5.737-5.737l103.284 103.284zM293.959 427.403l63.118-63.118c5.737-5.737 11.476-5.737 17.213 0l22.953 22.953c5.737 5.737 5.737 11.476 0 17.213l-63.118 57.379-40.166-34.429zM787.42 387.237c5.737-5.737 5.737 0 5.737 5.737l11.476 109.021c0 5.737-5.737 11.476-11.476 11.476l-109.021-11.476c-5.737 0-5.737-5.737-5.737-5.737l109.021-109.021zM724.305 461.832l-63.118-63.118c-5.737-5.737-5.737-11.476 0-17.213l22.953-22.953c5.737-5.737 11.476-5.737 17.213 0l63.118 63.118-40.166 40.166zM689.876 180.672c-5.737-5.737 0-5.737 5.737-5.737l109.021-11.476c5.737 0 11.476 5.737 11.476 11.476l-17.213 103.284c0 5.737-5.737 5.737-5.737 5.737l-103.284-103.284zM758.731 249.527l-63.118 63.118c-5.737 5.737-11.476 5.737-17.213 0l-22.953-22.953c-5.737-5.737-5.737-11.476 0-17.213l63.118-63.118 40.166 40.166zM265.269 283.956c-5.737 5.737-5.737 0-5.737-5.737l-11.476-109.021c0-5.737 5.737-11.476 11.476-11.476l109.021 11.476c5.737 0 5.737 5.737 5.737 5.737l-109.021 109.021zM334.124 209.362l63.118 63.118c5.737 5.737 5.737 11.476 0 17.213l-22.953 22.953c-5.737 5.737-11.476 5.737-17.213 0l-63.118-63.118 40.166-40.166zM161.985 593.805v-499.201h722.979v499.201h-722.979zM844.799 134.77h-636.911v413.13h636.911v-413.13z" />
<glyph unicode="&#xe88e;" glyph-name="h5p" data-tags="h5p" d="M934.072 489.192c-22.319 16.738-50.216 27.897-89.273 27.897h-139.487v-66.954h-156.225l-11.159-55.795c11.159 5.579 27.897 11.159 39.057 11.159s22.319 0 33.476 0c33.476 0 66.954-11.159 89.273-33.476s33.476-50.216 33.476-83.692c0-22.319-5.579-44.635-16.738-66.954s-27.897-39.057-50.216-50.216c-5.579-5.579-16.738 0-22.319-11.159h117.17v133.908h66.954c44.635 0 78.113 11.159 100.43 27.897 22.319 22.319 33.476 50.216 33.476 83.692 0 39.057-11.159 66.954-27.897 83.692v0zM839.221 377.603c-11.159-5.579-22.319-11.159-44.635-11.159h-33.476v83.692h39.057c22.319 0 33.476-5.579 44.635-11.159 5.579-5.579 11.159-16.738 11.159-27.897 0-16.738-5.579-27.897-16.738-33.476v0zM565.826 338.546c-16.738 0-33.476-11.159-44.635-27.897l-94.851 16.738 44.635 195.281h-94.851v-150.646h-117.17v150.646h-111.589v-362.667h111.589v133.908h117.17v-133.908h139.487c-16.738 11.159-33.476 11.159-44.635 22.319s-22.319 22.319-27.897 33.476c-5.579 11.159-11.159 22.319-16.738 39.057l94.851 16.738c5.579-16.738 22.319-27.897 44.635-27.897 27.897 0 50.216 22.319 50.216 50.216 0 22.319-22.319 44.635-50.216 44.635v0z" />
<glyph unicode="&#xe88f;" glyph-name="rights-of-use" data-tags="rights-of-use" d="M899.611 329.519c0-5.907 0-5.907 0-5.907-23.631-23.631-47.261-35.448-76.799-41.355-11.813 0-23.631-5.907-35.448-5.907s-17.724 0-29.537 0c0 0-5.907 0-5.907 5.907-64.985 59.079-135.877 118.153-200.863 183.139 0 0-5.907 0-5.907 0-23.631-5.907-47.261-11.813-70.892-17.724s-53.168 0-76.799 11.813c-17.724 11.813-23.631 17.724-29.537 35.448-5.907 5.907 0 23.631 11.813 23.631 41.355 11.813 88.616 29.537 129.971 47.261 11.813 5.907 29.537 5.907 41.355 5.907 5.907 0 11.813-5.907 11.813-5.907 41.355-17.724 82.709-29.537 124.060-47.261 0 0 5.907 0 5.907 0 29.537 5.907 64.985 17.724 94.523 23.631 5.907 0 5.907 0 5.907 0l106.34-212.676zM291.12 335.429c17.724 11.813 35.448 5.907 53.168-11.813 11.813-11.813 11.813-29.537 5.907-47.261 17.724 5.907 35.448-5.907 41.355-17.724 11.813-17.724 5.907-35.448-5.907-47.261 5.907 0 11.813 0 17.724 0 11.813-5.907 23.631-11.813 29.537-29.537s0-29.537-5.907-35.448c-5.907-5.907-11.813-11.813-17.724-17.724s-11.813-11.813-17.724-17.724-35.448-17.724-53.168 0c-29.537 29.537-53.168 64.985-82.709 94.523-17.724 23.631-35.448 41.355-47.261 64.985-5.907 11.813-11.813 17.724-11.813 29.537 0 5.907 0 17.724 5.907 23.631 11.813 11.813 17.724 17.724 29.537 29.537 17.724 17.724 47.261 11.813 64.985-5.907-5.907 0-5.907-5.907-5.907-11.813v0zM438.811 128.66l29.537-29.537c17.724-17.724 47.261-11.813 59.079 5.907l-5.907 5.907c-23.631 23.631-47.261 47.261-70.892 70.892-5.907 5.907-5.907 5.907-5.907 11.813s5.907 5.907 11.813 11.813c5.907 0 11.813 0 11.813-5.907 11.813-11.813 29.537-29.537 47.261-47.261 11.813-11.813 29.537-29.537 47.261-47.261 5.907-11.813 17.724-11.813 29.537-11.813 11.813 5.907 23.631 11.813 29.537 23.631 0 5.907 0 5.907 0 5.907-41.355 41.355-88.616 82.709-129.971 129.971-5.907 5.907-5.907 5.907-5.907 11.813 0 11.813 11.813 11.813 23.631 5.907 0 0 5.907 0 5.907-5.907 41.355-41.355 88.616-88.616 129.971-129.971 5.907-5.907 5.907-5.907 5.907-5.907 17.724 0 35.448 17.724 35.448 35.448 0 5.907 0 5.907 0 5.907-47.261 47.261-100.429 100.429-147.691 147.691-5.907 5.907-5.907 5.907-5.907 11.813s5.907 11.813 5.907 11.813c5.907 0 11.813 0 11.813-5.907 5.907-5.907 5.907-5.907 11.813-11.813 35.448-35.448 70.892-70.892 106.34-106.34 11.813-11.813 23.631-23.631 29.537-29.537 0 0 5.907-5.907 5.907 0 23.631 5.907 35.448 29.537 29.537 53.168h35.448c0 0 0 0 0 0 0-5.907 0-17.724 0-23.631-5.907-29.537-23.631-47.261-53.168-59.079 0 0-5.907 0-5.907-5.907-11.813-29.537-35.448-53.168-64.985-53.168-5.907 0-5.907 0-5.907-5.907-17.724-35.448-59.079-47.261-88.616-35.448-5.907 0-11.813 5.907-11.813 5.907-5.907-5.907-11.813-11.813-23.631-17.724-29.537-11.813-59.079-5.907-76.799 11.813-11.813 11.813-17.724 17.724-29.537 29.537 17.724 23.631 23.631 29.537 29.537 41.355v0 0zM273.396 642.628c29.537-11.813 64.985-23.631 94.523-29.537 35.448-11.813 64.985-23.631 100.429-29.537 0 0 0 0 5.907 0-17.724-5.907-35.448-11.813-47.261-17.724 0 0-5.907 0-5.907 0-47.261 11.813-94.523 23.631-135.877 41.355-5.907 0-5.907 0-5.907 0l-76.799-183.139c0-11.813 5.907-17.724 11.813-23.631s5.907-5.907 5.907-11.813c-5.907-5.907-11.813-17.724-23.631-23.631-17.724 17.724-29.537 35.448-29.537 64.985l88.616 212.676c-5.907-11.813 5.907 5.907 17.724 0v0z" />
<glyph unicode="&#xe890;" glyph-name="delete-circle" data-tags="delete-circle" d="M512 601.601c-147.107 0-260.266-118.817-260.266-260.266s118.817-260.266 260.266-260.266 260.266 118.817 260.266 260.266-113.158 260.266-260.266 260.266zM653.449 262.123c5.659-5.659 5.659-16.973 0-28.29l-33.949-33.949c-5.659-5.659-16.973-5.659-28.29 0l-79.212 79.212-79.212-79.212c-5.659-5.659-16.973-5.659-28.29 0l-33.949 33.949c-5.659 5.659-5.659 16.973 0 28.29l84.871 79.212-79.212 79.212c-5.659 5.659-5.659 16.973 0 28.29l33.949 33.949c5.659 5.659 16.973 5.659 28.29 0l73.554-84.871 79.212 79.212c5.659 5.659 16.973 5.659 28.29 0l33.949-33.949c5.659-5.659 5.659-16.973 0-28.29l-79.212-73.554 79.212-79.212z" />
<glyph unicode="&#xe891;" glyph-name="window" data-tags="window" d="M203.936 461.136c-5.704-5.704 0-5.704 5.704-5.704l108.394-11.41c5.704 0 11.41 5.704 11.41 11.41l-17.114 102.687c0 5.704-5.704 5.704-5.704 5.704l-102.687-102.687zM272.395 523.891l-62.752 62.752c-5.704 5.704-11.41 5.704-17.114 0l-17.114-22.821c-5.704-5.704-5.704-11.41 0-17.114l62.752-62.752 34.228 39.935zM751.605 558.119c-5.704 5.704-5.704 0-5.704-5.704l-11.41-108.394c0-5.704 5.704-11.41 11.41-11.41l108.394 11.41c5.704 0 5.704 5.704 5.704 5.704l-108.394 108.394zM814.357 483.957l62.752 62.752c5.704 5.704 5.704 11.41 0 17.114l-22.821 22.821c-5.704 5.704-11.41 5.704-17.114 0l-62.752-62.752 39.935-39.935zM848.588 221.534c5.704 5.704 0 5.704-5.704 5.704l-102.687 17.114c-5.704 0-11.41-5.704-11.41-11.41l11.41-108.394c0-5.704 5.704-5.704 5.704-5.704l102.687 102.687zM780.129 158.779l62.752-62.752c5.704-5.704 11.41-5.704 17.114 0l22.821 22.821c5.704 5.704 5.704 11.41 0 17.114l-62.752 62.752-39.935-39.935zM300.919 124.551c5.704-5.704 5.704 0 5.704 5.704l11.41 108.394c0 5.704-5.704 11.41-11.41 11.41l-108.394-11.41c-5.704 0-5.704-5.704-5.704-5.704l108.394-108.394zM238.167 193.010l-62.752-62.752c-5.704-5.704-5.704-11.41 0-17.114l22.821-22.821c5.704-5.704 11.41-5.704 17.114 0l62.752 62.752-39.935 39.935zM352.264 466.843v-239.605h347.998v239.605h-347.998zM654.622 267.172h-262.424v154.032h262.424v-154.032z" />
<glyph unicode="&#xe892;" glyph-name="code" data-tags="code" d="M449.641 235.325c6.235-6.235 6.235-12.472 6.235-18.707v-62.359c0-6.235-6.235-6.235-6.235-6.235l-230.728 155.897c-6.235 6.235-6.235 12.472-6.235 18.707v49.886c0 6.235 6.235 12.472 6.235 18.707l230.728 155.897c6.235 6.235 6.235 0 6.235-6.235v-62.359c0-6.235-6.235-12.472-6.235-18.707l-162.134-112.245c-6.235-6.235-6.235-6.235 0-12.472l162.134-99.776zM736.493 341.335c6.235 6.235 6.235 6.235 0 12.472l-155.897 112.245c-6.235 6.235-6.235 12.472-6.235 18.707v62.359c0 6.235 6.235 6.235 6.235 6.235l230.728-155.897c6.235-6.235 6.235-12.472 6.235-18.707v-49.886c0-6.235-6.235-12.472-6.235-18.707l-230.728-155.897c-6.235-6.235-6.235 0-6.235 6.235v62.359c0 6.235 6.235 12.472 6.235 18.707l155.897 99.776z" />
<glyph unicode="&#xe893;" glyph-name="download" data-tags="download" d="M358.941 435.525c-11.773 0-17.66-5.887-5.887-17.66l153.059-188.382c5.887-11.773 23.547-11.773 29.433 0l153.059 188.382c5.887 11.773 5.887 17.66-5.887 17.66h-323.782zM576.756 423.751v135.399c0 11.773-11.773 23.547-23.547 23.547h-70.643c-11.773 0-23.547-11.773-23.547-23.547v-141.286h117.739zM653.286 288.352c-5.887 0-17.66-5.887-23.547-11.773l-76.53-94.19c-5.887-5.887-17.66-17.66-23.547-23.547 0 0-5.887-5.887-11.773-5.887s-17.66 11.773-17.66 11.773c-5.887 5.887-17.66 17.66-23.547 23.547l-76.53 94.19c-5.887 5.887-17.66 11.773-23.547 11.773h-123.626c-5.887 0-17.66-5.887-17.66-17.66v-141.286c0-5.887 5.887-17.66 17.66-17.66h529.824c5.887 0 17.66 5.887 17.66 17.66v141.286c0 5.887-5.887 17.66-17.66 17.66l-129.513-5.887zM305.958 176.502c-17.66 0-29.433 11.773-29.433 29.433s11.773 29.433 29.433 29.433c17.66 0 29.433-11.773 29.433-29.433s-11.773-29.433-29.433-29.433v0z" />
<glyph unicode="&#xe894;" glyph-name="delete" data-tags="delete" d="M620.266 341.335l134.045 134.045c10.311 10.311 10.311 30.934 0 41.245l-61.866 61.866c-10.311 10.311-30.934 10.311-41.245 0l-134.045-134.045-134.045 134.045c-10.311 10.311-30.934 10.311-41.245 0l-61.866-61.866c-10.311-10.311-10.311-30.934 0-41.245l134.045-134.045-134.045-134.045c-10.311-10.311-10.311-30.934 0-41.245l61.866-61.866c10.311-10.311 30.934-10.311 41.245 0l134.045 134.045 134.045-134.045c10.311-10.311 30.934-10.311 41.245 0l61.866 61.866c10.311 10.311 10.311 30.934 0 41.245l-134.045 134.045z" />
<glyph unicode="&#xe900;" glyph-name="edit-image" data-tags="edit-image" d="M300.237 621.639c69.018 23.142 133.325 14.234 189.133-33.28 56.627-48.128 77.619-110.592 63.181-183.808-2.355-12.186 0.307-19.456 8.704-27.853 93.901-93.389 156.774-156.467 250.47-250.163 5.427-5.427 10.957-10.854 15.667-16.896 39.424-50.278 16.794-124.006-44.237-142.029-36.966-10.957-68.403 0-95.334 27.034-95.642 96.051-160.973 160.973-256.614 257.024-6.963 6.963-12.8 8.909-22.63 6.758-117.76-26.317-229.171 60.826-231.731 181.453-0.614 26.419 3.584 52.326 15.974 77.926 34.816-34.816 68.506-67.789 101.274-101.786 10.445-10.752 20.992-15.36 36.045-15.36 14.643 0 25.19 3.891 34.816 14.848 10.752 12.39 23.040 23.347 34.611 35.021 14.336 14.438 14.336 46.080-0.205 60.518-35.123 35.226-70.349 70.349-106.598 106.496 3.891 2.253 5.632 3.482 7.475 4.096zM703.386 63.559c-0.41-24.269 20.685-45.466 44.851-45.158 23.757 0.41 44.032 20.992 43.93 44.544-0.102 23.757-20.275 44.032-44.237 44.134-23.859 0.307-44.237-19.661-44.544-43.52z" />
<glyph unicode="&#xe901;" glyph-name="hourglass" data-tags="hourglass" d="M733.286-10.579c-147.763 0-295.526 0-443.29 0 0 2.048 0.102 4.096 0 6.144-0.307 13.824-1.024 32.666-0.922 46.49 0.41 39.731 6.861 78.131 19.046 115.2 17.203 52.224 43.725 96.256 81.306 130.355 4.506 4.096 9.216 7.885 13.722 11.776-0.205 0.717-0.307 1.126-0.41 1.229-1.331 1.229-2.765 2.355-4.198 3.584-28.058 22.63-50.688 51.405-68.403 85.606-30.618 59.085-43.52 123.597-41.165 192.614 0.205 7.168 0.614 18.33 0.922 25.498 147.763 0 295.526 0 443.29 0 0.205-1.331 0.512-2.662 0.614-3.994 2.662-36.966 1.229-77.722-5.939-113.869-14.336-72.909-44.544-133.837-95.027-179.405-4.096-3.686-8.294-7.066-12.39-10.547 0.205-0.717 0.307-1.126 0.512-1.331 0.819-0.717 1.638-1.434 2.458-2.15 42.189-33.894 71.68-79.872 90.931-135.782 11.776-34.202 18.637-69.837 20.070-106.701 0.819-19.763-0.614-44.851-1.126-64.717zM687.309 32.634c0 6.554 0.205 12.493 0 18.432-1.331 37.581-7.27 74.138-19.866 108.749-17.92 49.562-45.568 88.269-88.678 108.646-2.458 1.126-2.97 3.072-2.97 5.837 0.102 16.691 0.102 33.485 0 50.176 0 3.994 1.331 5.427 4.096 6.963 9.114 5.325 18.432 10.24 26.829 16.896 29.696 23.552 49.152 56.934 62.362 95.744 10.342 30.413 15.77 62.259 17.818 94.822 0.614 9.114 0.102 18.227 0.102 27.546-116.634 0-233.574 0-351.027 0 0.307-8.704 0.614-16.998 1.024-25.395 1.946-37.274 8.499-73.216 21.504-107.213 18.125-47.104 45.261-83.558 86.528-103.117 2.253-1.024 2.867-2.662 2.867-5.427-0.102-17.203-0.102-34.509 0-51.712 0-3.072-1.024-4.506-3.277-5.632-5.632-2.867-11.366-5.734-16.691-9.216-34.304-22.733-56.832-57.754-71.987-100.147-12.493-35.123-18.125-71.987-19.661-109.875-0.205-5.325 0-10.752 0-16.282 117.35 0.205 234.189 0.205 351.027 0.205zM410.214 451.962c68.096 0 135.373 0 203.674 0-3.789-6.554-7.168-12.595-10.752-18.227-10.957-16.998-24.269-30.618-39.731-41.472s-27.75-25.293-32.768-46.592c-1.638-6.963-2.765-14.336-2.867-21.606-0.307-17.203-0.205-34.406 0.307-51.712 0.717-28.058 12.493-48.947 32.154-62.566 43.008-30.003 65.843-75.878 75.776-132.506 0.307-1.638 0.307-3.379 0.614-5.53-83.149 0-166.093 0-249.242 0 2.662 20.685 7.885 40.346 15.462 59.085 13.312 32.973 32.768 59.187 59.494 77.722 16.589 11.469 28.365 27.955 32.358 50.995 0.819 4.813 1.331 9.83 1.434 14.746 0.102 18.637 0.614 37.274-0.205 55.808-1.126 24.678-11.981 43.213-28.57 57.139-9.216 7.782-18.944 14.746-27.648 23.142-11.981 11.162-21.197 25.395-29.491 41.574z" />
<glyph unicode="&#xe902;" glyph-name="plus-icon" data-tags="plus-icon" d="M768 285.015c0-19.323-15.664-34.987-34.987-34.987h-151.040v-151.467c0-19.323-15.664-34.987-34.987-34.987h-69.547c-19.323 0-34.987 15.664-34.987 34.987v151.467h-151.467c-19.323 0-34.987 15.664-34.987 34.987v69.547c0 19.323 15.664 34.987 34.987 34.987h151.467v151.467c0 19.323 15.664 34.987 34.987 34.987h69.547c19.323 0 34.987-15.664 34.987-34.987v-151.467h151.467c19.323 0 34.987-15.664 34.987-34.987z" />
<glyph unicode="&#xe903;" glyph-name="video-upload-icon" data-tags="video-upload-icon" d="M384 328.535v-128c0-21.333 21.333-42.667 42.667-42.667h128c21.333 0 42.667 17.067 42.667 42.667v128c0 21.333-17.067 42.667-42.667 42.667h-128c-21.333 0-42.667-17.067-42.667-42.667zM785.067 499.201l-102.4 106.667c-12.8 12.8-38.4 21.333-55.467 21.333h-140.8l21.333-42.667h89.6v-136.533c0-17.067 12.8-29.867 29.867-29.867h140.8v-341.333h-426.667v328.533h-42.667v-341.333c0-17.067 12.8-29.867 29.867-29.867h448c17.067 0 29.867 12.8 29.867 29.867v384c4.267 17.067-8.533 38.4-21.333 51.2zM640 456.535v123.733c4.267 0 12.8-4.267 12.8-8.533l102.4-102.4c4.267-4.267 4.267-8.533 8.533-12.8h-123.733zM725.333 166.401v196.267c0 4.267-4.267 8.533-8.533 8.533s-8.533 0-12.8-4.267l-89.6-89.6v-29.867l89.6-89.6c8.533-4.267 8.533 0 12.8 0 4.267 4.267 8.533 4.267 8.533 8.533zM349.867 473.601v136.533l59.733-59.733c8.533-8.533 17.067-4.267 25.6 0l12.8 12.8c8.533 8.533 8.533 17.067 0 25.6l-115.2 106.667c-8.533 8.533-17.067 8.533-21.333 0l-115.2-110.933c-4.267-8.533-4.267-17.067 0-25.6l12.8-12.8c8.533-8.533 17.067-8.533 21.333 0l59.733 59.733v-136.533c0-8.533 8.533-17.067 17.067-17.067h25.6c0 0 17.067 8.533 17.067 21.333z" />
<glyph unicode="&#xe904;" glyph-name="play-icon" data-tags="play-icon" d="M392.533 597.335c81.067 46.933 187.733 46.933 273.067 0 42.667-25.6 72.533-55.467 98.133-98.133 72.533-128 29.867-294.4-98.133-371.2-128-72.533-294.4-29.867-371.2 98.133-46.933 81.067-46.933 187.733 0 273.067 21.333 42.667 55.467 72.533 98.133 98.133zM661.333 345.601c12.8 8.533 12.8 29.867 0 38.4l-192 110.933c-8.533 4.267-12.8 4.267-21.333 0s-12.8-12.8-12.8-21.333v-226.133c0-8.533 4.267-17.067 12.8-21.333s17.067-4.267 21.333 0l192 119.467z" />
<glyph unicode="&#xe905;" glyph-name="copy" data-tags="copy" d="M722.133 561.201h-247.733c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v288.533c-0.267 65.867-53.467 119.2-119.333 119.2zM778.533 156.534c0-31.333-25.067-56.4-56.4-56.4h-247.867c-31.333 0-56.4 25.067-56.4 56.4v285.467c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-285.467zM245.2 326.001v288.533c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-18.8h62.667v18.8c0 65.867-53.333 119.2-119.2 119.2h-247.467c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h18.8v62.667h-18.8c-31.467-3.067-56.533 22-56.533 56.533zM681.2 360.534h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 266.401h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 172.268h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333z" />
<glyph unicode="&#xe906;" glyph-name="examples-icon" data-tags="examples-icon" d="M213.333 166.402c89.6 38.4 183.467 68.267 273.067 17.067v281.6c-68.267 46.933-157.867 55.467-234.667 12.8l-38.4-311.467zM810.667 166.402l-42.667 315.733c-72.533 38.4-166.4 34.133-234.667-17.067v-285.867c93.867 51.2 187.733 21.333 277.333-12.8zM832 520.535c-51.2 29.867-110.933 46.933-170.667 55.467-51.2 0-102.4-8.533-149.333-29.867-46.933 21.333-98.133 29.867-149.333 29.867-59.733-4.267-119.467-25.6-170.667-55.467l-64-452.267c0 0 29.867-17.067 110.933 21.333 46.933 25.6 102.4 34.133 157.867 25.6 42.667-4.267 85.333-21.333 115.2-55.467v0c29.867 34.133 72.533 51.2 115.2 55.467 55.467 4.267 106.667-4.267 157.867-29.867 81.067-38.4 110.933-21.333 110.933-21.333l-64 456.533zM793.6 115.202c-42.667 21.333-89.6 34.133-140.8 34.133-8.533 0-21.333 0-29.867 0-42.667-4.267-81.067-17.067-115.2-42.667-34.133 25.6-72.533 38.4-115.2 42.667-12.8 0-21.333 0-34.133 0-46.933 0-93.867-8.533-136.533-29.867-21.333-12.8-46.933-21.333-72.533-25.6l64 413.867c46.933 25.6 98.133 38.4 149.333 42.667 46.933 0 93.867-8.533 136.533-25.6l12.8-8.533 12.8 4.267c42.667 17.067 89.6 25.6 136.533 25.6 51.2-4.267 102.4-17.067 149.333-42.667l59.733-418.133c-29.867 8.533-51.2 17.067-76.8 29.867z" />
<glyph unicode="&#xe907;" glyph-name="tutorials-icon" data-tags="tutorials-icon" d="M887.467 430.935l-375.467-110.933h-4.267l-217.6 68.267c-21.333-25.6-34.133-59.733-34.133-98.133 21.333-12.8 25.6-38.4 12.8-59.733-4.267-4.267-8.533-8.533-12.8-12.8l17.067-145.067c0-4.267 0-4.267-4.267-8.533 0 0 0 0-4.267 0h-64c-4.267 0-4.267 0-8.533 4.267 0 4.267-4.267 4.267-4.267 8.533l17.067 145.067c-12.8 8.533-17.067 21.333-17.067 34.133 0 17.067 8.533 29.867 21.333 38.4 0 38.4 12.8 76.8 34.133 110.933l-106.667 29.867c-8.533 4.267-8.533 8.533-8.533 17.067 0 4.267 4.267 4.267 4.267 4.267l375.467 119.467h4.267l375.467-123.733c4.267 0 8.533-4.267 8.533-8.533s-4.267-8.533-8.533-12.8zM725.333 234.669c4.267-46.933-93.867-85.333-213.333-85.333s-213.333 38.4-213.333 85.333l4.267 106.667 192-64c4.267 0 12.8 0 17.067 0s12.8 0 17.067 4.267l192 59.733 4.267-106.667z" />
<glyph unicode="&#xe908;" glyph-name="info-important-description" data-tags="info-important-description" d="M512 697.368c-188.5 0-341.3-152.8-341.3-341.3s152.8-341.4 341.3-341.4 341.3 152.8 341.3 341.3-152.8 341.4-341.3 341.4v0zM512 43.268c-172.7 0-312.7 140-312.7 312.7s140 312.7 312.7 312.7c172.7 0 312.7-140 312.7-312.7-0.2-172.6-140.1-312.5-312.7-312.7v0zM512 605.568c-137.9 0-249.6-111.8-249.6-249.6s111.7-249.6 249.6-249.6 249.6 111.8 249.6 249.6-111.8 249.6-249.6 249.6v0z" />
<glyph unicode="&#xe909;" glyph-name="icon-info" data-tags="icon-info" d="M467.2 459.522h87.467c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v70.385c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-87.462c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-69.959c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.715-4.373 9.2-7.159 15.338-7.245zM597.333 156.589h-22.187v209.92c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-130.128c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h22.182v-139.947h-22.187c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h174.075c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v46.065c0.043 0.527 0.067 1.141 0.067 1.761 0 5.302-1.791 10.185-4.8 14.079-3.742 4.424-9.36 7.247-15.636 7.247-0.489 0-0.975-0.017-1.456-0.051z" />
<glyph unicode="&#xe90a;" glyph-name="paste" data-tags="paste" d="M394.402 702.4h-75.333c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-56.4 37.6-100.4 87.867-116v69.067c-15.733 9.467-25.067 25.067-25.067 47.067v288.4c0 31.333 25.067 56.4 56.4 56.4h131.733c0 0 0 0 3.2 3.2v0c0 31.333-28.267 59.6-59.6 59.6zM704.802 592.533c0 0-28.267 0-40.8 0-12.533 34.533-43.867 59.6-84.667 59.6s-69.067-25.067-81.6-59.6c-12.533 0-40.8 0-40.8 0-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v285.467c3.2 65.867-53.2 122.267-119.2 122.267zM582.535 605.2c22 0 40.8-18.8 40.8-40.8s-18.8-40.8-40.8-40.8c-22 0-40.8 18.8-40.8 40.8s15.733 40.8 40.8 40.8zM764.402 181.733c0-31.333-25.067-56.4-56.4-56.4h-250.933c-31.333 0-56.4 25.067-56.4 56.4v288.533c0 18.8 9.467 37.6 25.067 47.067v0c0-43.867 34.533-78.4 78.4-78.4h160c43.867 0 78.4 34.533 78.4 78.4v0c12.533-9.467 22-28.267 22-47.067v-288.533z" />
<glyph unicode="&#xe565;" glyph-name="arrow-down" data-tags="arrow-down" d="M234 565.667h556l-278-278z" />
<glyph unicode="&#xe566;" glyph-name="arrow-left" data-tags="arrow-left" d="M381 164.667v524l262-262z" />
<glyph unicode="&#xe58e;" glyph-name="colapse" data-tags="colapse" d="M512 584.667l256-256-60-60-196 196-196-196-60 60z" />
<glyph unicode="&#xe58f;" glyph-name="expand" data-tags="expand" d="M708 584.667l60-60-256-256-256 256 60 60 196-196z" />
<glyph unicode="&#xe600;" glyph-name="move" data-tags="move" d="M386.662 853.061h71.27v-71.27h-71.27v71.27zM566.067 853.061h71.27v-71.27h-71.27v71.27zM386.662 696.798h71.27v-71.27h-71.27v71.27zM566.067 696.798h71.27v-71.27h-71.27v71.27zM386.662 540.433h71.27v-71.27h-71.27v71.27zM566.067 540.433h71.27v-71.27h-71.27v71.27zM386.662 384.171h71.27v-71.27h-71.27v71.27zM566.067 384.171h71.27v-71.27h-71.27v71.27zM386.662 227.806h71.27v-71.27h-71.27v71.27zM566.067 227.806h71.27v-71.27h-71.27v71.27zM386.662 71.544h71.27v-71.27h-71.27v71.27zM566.067 71.544h71.27v-71.27h-71.27v71.27z" />
<glyph unicode="&#xe601;" glyph-name="check-mark" data-tags="check-mark" d="M454.301 395.253l-116.917 116.917-84.781-84.707 201.696-201.697 317.097 317.097-84.781 84.706z" />
<glyph unicode="&#xe888;" glyph-name="arrow-up-circle" data-tags="arrow-up-circle" d="M513.344 689.068c-148.616 0-264.722-120.75-260.077-269.367 0-125.395 88.241-232.212 208.991-255.434v213.636h-92.885c-13.933 0-13.933 9.288-9.288 18.577l139.327 171.838c4.645 9.288 13.933 9.288 23.221 4.645 0 0 4.645-4.645 4.645-4.645l139.327-171.838c9.288-9.288 4.645-18.577-9.288-18.577h-92.885v-213.636c143.972 32.51 232.212 171.838 199.703 315.808-23.221 120.75-130.039 204.347-250.789 208.991z" />
<glyph unicode="&#xe889;" glyph-name="info-circle" data-tags="info-circle" d="M512 686.933c-144.077 0-260.266-116.191-260.266-260.266s116.191-260.266 260.266-260.266 260.266 116.191 260.266 260.266v0c0 139.429-116.191 255.619-260.266 260.266zM470.171 635.81h88.305v-69.714h-88.305v69.714zM600.305 245.41h-181.257v51.123h51.123v162.666h-51.123v51.123h139.429v-218.438h46.477l-4.648-46.477z" />
<glyph unicode="&#xe88a;" glyph-name="search" data-tags="search" d="M769.263 223.163l-110.68 110.68c71.943 99.612 49.806 243.494-49.806 315.437s-243.494 44.27-315.437-55.339c-71.943-99.612-49.806-243.494 49.806-315.437 77.475-55.339 182.623-55.339 260.098 0l110.68-110.68c5.533-5.533 11.068-5.533 16.601 0 0 0 0 0 0 0l33.205 33.205c11.068 5.533 11.068 16.601 5.533 22.137 0 0 0 0 0 0zM475.96 300.638c-88.544 0-160.486 71.943-160.486 160.486s71.943 160.486 160.486 160.486 160.486-71.943 160.486-160.486-71.943-160.486-160.486-160.486v0z" />
<glyph unicode="&#xe88c;" glyph-name="fullscreen" data-tags="fullscreen" d="M357.075 572.983c5.737 5.737 0 5.737-5.737 5.737l-103.284 11.476c-5.737 5.737-11.476 0-11.476-5.737l11.476-109.021c0-5.737 5.737-5.737 5.737-5.737l103.284 103.284zM282.484 509.865l63.118-63.118c5.737-5.737 11.476-5.737 17.213 0l22.953 22.953c5.737 5.737 5.737 11.476 0 17.213l-63.118 57.379-40.166-34.429zM775.945 469.699c5.737-5.737 5.737 0 5.737 5.737l11.476 109.021c0 5.737-5.737 11.476-11.476 11.476l-109.021-11.476c-5.737 0-5.737-5.737-5.737-5.737l109.021-109.021zM712.83 544.294l-63.118-63.118c-5.737-5.737-5.737-11.476 0-17.213l22.953-22.953c5.737-5.737 11.476-5.737 17.213 0l63.118 63.118-40.166 40.166zM678.401 263.134c-5.737-5.737 0-5.737 5.737-5.737l109.021-11.476c5.737 0 11.476 5.737 11.476 11.476l-17.213 103.284c0 5.737-5.737 5.737-5.737 5.737l-103.284-103.284zM747.256 331.989l-63.118 63.118c-5.737 5.737-11.476 5.737-17.213 0l-22.953-22.953c-5.737-5.737-5.737-11.476 0-17.213l63.118-63.118 40.166 40.166zM253.794 366.418c-5.737 5.737-5.737 0-5.737-5.737l-11.476-109.021c0-5.737 5.737-11.476 11.476-11.476l109.021 11.476c5.737 0 5.737 5.737 5.737 5.737l-109.021 109.021zM322.649 291.824l63.118 63.118c5.737 5.737 5.737 11.476 0 17.213l-22.953 22.953c-5.737 5.737-11.476 5.737-17.213 0l-63.118-63.118 40.166-40.166zM150.51 676.267v-499.201h722.979v499.201h-722.979zM833.324 217.232h-636.911v413.13h636.911v-413.13z" />
<glyph unicode="&#xe88e;" glyph-name="h5p" data-tags="h5p" d="M891.406 574.524c-22.319 16.738-50.216 27.897-89.273 27.897h-139.487v-66.954h-156.225l-11.159-55.795c11.159 5.579 27.897 11.159 39.057 11.159s22.319 0 33.476 0c33.476 0 66.954-11.159 89.273-33.476s33.476-50.216 33.476-83.692c0-22.319-5.579-44.635-16.738-66.954s-27.897-39.057-50.216-50.216c-5.579-5.579-16.738 0-22.319-11.159h117.17v133.908h66.954c44.635 0 78.113 11.159 100.43 27.897 22.319 22.319 33.476 50.216 33.476 83.692 0 39.057-11.159 66.954-27.897 83.692v0zM796.555 462.935c-11.159-5.579-22.319-11.159-44.635-11.159h-33.476v83.692h39.057c22.319 0 33.476-5.579 44.635-11.159 5.579-5.579 11.159-16.738 11.159-27.897 0-16.738-5.579-27.897-16.738-33.476v0zM523.16 423.878c-16.738 0-33.476-11.159-44.635-27.897l-94.851 16.738 44.635 195.281h-94.851v-150.646h-117.17v150.646h-111.589v-362.667h111.589v133.908h117.17v-133.908h139.487c-16.738 11.159-33.476 11.159-44.635 22.319s-22.319 22.319-27.897 33.476c-5.579 11.159-11.159 22.319-16.738 39.057l94.851 16.738c5.579-16.738 22.319-27.897 44.635-27.897 27.897 0 50.216 22.319 50.216 50.216 0 22.319-22.319 44.635-50.216 44.635v0z" />
<glyph unicode="&#xe88f;" glyph-name="rights-of-use" data-tags="rights-of-use" d="M878.277 414.35c0-5.907 0-5.907 0-5.907-23.631-23.631-47.261-35.448-76.799-41.355-11.813 0-23.631-5.907-35.448-5.907s-17.724 0-29.537 0c0 0-5.907 0-5.907 5.907-64.985 59.079-135.877 118.153-200.863 183.139 0 0-5.907 0-5.907 0-23.631-5.907-47.261-11.813-70.892-17.724s-53.168 0-76.799 11.813c-17.724 11.813-23.631 17.724-29.537 35.448-5.907 5.907 0 23.631 11.813 23.631 41.355 11.813 88.616 29.537 129.971 47.261 11.813 5.907 29.537 5.907 41.355 5.907 5.907 0 11.813-5.907 11.813-5.907 41.355-17.724 82.709-29.537 124.060-47.261 0 0 5.907 0 5.907 0 29.537 5.907 64.985 17.724 94.523 23.631 5.907 0 5.907 0 5.907 0l106.34-212.676zM269.786 420.26c17.724 11.813 35.448 5.907 53.168-11.813 11.813-11.813 11.813-29.537 5.907-47.261 17.724 5.907 35.448-5.907 41.355-17.724 11.813-17.724 5.907-35.448-5.907-47.261 5.907 0 11.813 0 17.724 0 11.813-5.907 23.631-11.813 29.537-29.537s0-29.537-5.907-35.448c-5.907-5.907-11.813-11.813-17.724-17.724s-11.813-11.813-17.724-17.724-35.448-17.724-53.168 0c-29.537 29.537-53.168 64.985-82.709 94.523-17.724 23.631-35.448 41.355-47.261 64.985-5.907 11.813-11.813 17.724-11.813 29.537 0 5.907 0 17.724 5.907 23.631 11.813 11.813 17.724 17.724 29.537 29.537 17.724 17.724 47.261 11.813 64.985-5.907-5.907 0-5.907-5.907-5.907-11.813v0zM417.477 213.491l29.537-29.537c17.724-17.724 47.261-11.813 59.079 5.907l-5.907 5.907c-23.631 23.631-47.261 47.261-70.892 70.892-5.907 5.907-5.907 5.907-5.907 11.813s5.907 5.907 11.813 11.813c5.907 0 11.813 0 11.813-5.907 11.813-11.813 29.537-29.537 47.261-47.261 11.813-11.813 29.537-29.537 47.261-47.261 5.907-11.813 17.724-11.813 29.537-11.813 11.813 5.907 23.631 11.813 29.537 23.631 0 5.907 0 5.907 0 5.907-41.355 41.355-88.616 82.709-129.971 129.971-5.907 5.907-5.907 5.907-5.907 11.813 0 11.813 11.813 11.813 23.631 5.907 0 0 5.907 0 5.907-5.907 41.355-41.355 88.616-88.616 129.971-129.971 5.907-5.907 5.907-5.907 5.907-5.907 17.724 0 35.448 17.724 35.448 35.448 0 5.907 0 5.907 0 5.907-47.261 47.261-100.429 100.429-147.691 147.691-5.907 5.907-5.907 5.907-5.907 11.813s5.907 11.813 5.907 11.813c5.907 0 11.813 0 11.813-5.907 5.907-5.907 5.907-5.907 11.813-11.813 35.448-35.448 70.892-70.892 106.34-106.34 11.813-11.813 23.631-23.631 29.537-29.537 0 0 5.907-5.907 5.907 0 23.631 5.907 35.448 29.537 29.537 53.168h35.448c0 0 0 0 0 0 0-5.907 0-17.724 0-23.631-5.907-29.537-23.631-47.261-53.168-59.079 0 0-5.907 0-5.907-5.907-11.813-29.537-35.448-53.168-64.985-53.168-5.907 0-5.907 0-5.907-5.907-17.724-35.448-59.079-47.261-88.616-35.448-5.907 0-11.813 5.907-11.813 5.907-5.907-5.907-11.813-11.813-23.631-17.724-29.537-11.813-59.079-5.907-76.799 11.813-11.813 11.813-17.724 17.724-29.537 29.537 17.724 23.631 23.631 29.537 29.537 41.355v0 0zM252.062 727.459c29.537-11.813 64.985-23.631 94.523-29.537 35.448-11.813 64.985-23.631 100.429-29.537 0 0 0 0 5.907 0-17.724-5.907-35.448-11.813-47.261-17.724 0 0-5.907 0-5.907 0-47.261 11.813-94.523 23.631-135.877 41.355-5.907 0-5.907 0-5.907 0l-76.799-183.139c0-11.813 5.907-17.724 11.813-23.631s5.907-5.907 5.907-11.813c-5.907-5.907-11.813-17.724-23.631-23.631-17.724 17.724-29.537 35.448-29.537 64.985l88.616 212.676c-5.907-11.813 5.907 5.907 17.724 0v0z" />
<glyph unicode="&#xe890;" glyph-name="delete-circle" data-tags="delete-circle" d="M512 686.933c-147.107 0-260.266-118.817-260.266-260.266s118.817-260.266 260.266-260.266 260.266 118.817 260.266 260.266-113.158 260.266-260.266 260.266zM653.449 347.455c5.659-5.659 5.659-16.973 0-28.29l-33.949-33.949c-5.659-5.659-16.973-5.659-28.29 0l-79.212 79.212-79.212-79.212c-5.659-5.659-16.973-5.659-28.29 0l-33.949 33.949c-5.659 5.659-5.659 16.973 0 28.29l84.871 79.212-79.212 79.212c-5.659 5.659-5.659 16.973 0 28.29l33.949 33.949c5.659 5.659 16.973 5.659 28.29 0l73.554-84.871 79.212 79.212c5.659 5.659 16.973 5.659 28.29 0l33.949-33.949c5.659-5.659 5.659-16.973 0-28.29l-79.212-73.554 79.212-79.212z" />
<glyph unicode="&#xe891;" glyph-name="window" data-tags="window" d="M186.821 549.319c-5.704-5.704 0-5.704 5.704-5.704l108.394-11.41c5.704 0 11.41 5.704 11.41 11.41l-17.114 102.687c0 5.704-5.704 5.704-5.704 5.704l-102.687-102.687zM255.28 612.074l-62.752 62.752c-5.704 5.704-11.41 5.704-17.114 0l-17.114-22.821c-5.704-5.704-5.704-11.41 0-17.114l62.752-62.752 34.228 39.935zM734.49 646.302c-5.704 5.704-5.704 0-5.704-5.704l-11.41-108.394c0-5.704 5.704-11.41 11.41-11.41l108.394 11.41c5.704 0 5.704 5.704 5.704 5.704l-108.394 108.394zM797.242 572.14l62.752 62.752c5.704 5.704 5.704 11.41 0 17.114l-22.821 22.821c-5.704 5.704-11.41 5.704-17.114 0l-62.752-62.752 39.935-39.935zM831.473 309.717c5.704 5.704 0 5.704-5.704 5.704l-102.687 17.114c-5.704 0-11.41-5.704-11.41-11.41l11.41-108.394c0-5.704 5.704-5.704 5.704-5.704l102.687 102.687zM763.014 246.962l62.752-62.752c5.704-5.704 11.41-5.704 17.114 0l22.821 22.821c5.704 5.704 5.704 11.41 0 17.114l-62.752 62.752-39.935-39.935zM283.804 212.734c5.704-5.704 5.704 0 5.704 5.704l11.41 108.394c0 5.704-5.704 11.41-11.41 11.41l-108.394-11.41c-5.704 0-5.704-5.704-5.704-5.704l108.394-108.394zM221.052 281.193l-62.752-62.752c-5.704-5.704-5.704-11.41 0-17.114l22.821-22.821c5.704-5.704 11.41-5.704 17.114 0l62.752 62.752-39.935 39.935zM335.149 555.026v-239.605h347.998v239.605h-347.998zM637.507 355.355h-262.424v154.032h262.424v-154.032z" />
<glyph unicode="&#xe892;" glyph-name="code" data-tags="code" d="M446.523 311.303c6.235-6.235 6.235-12.472 6.235-18.707v-62.359c0-6.235-6.235-6.235-6.235-6.235l-230.728 155.897c-6.235 6.235-6.235 12.472-6.235 18.707v49.886c0 6.235 6.235 12.472 6.235 18.707l230.728 155.897c6.235 6.235 6.235 0 6.235-6.235v-62.359c0-6.235-6.235-12.472-6.235-18.707l-162.134-112.245c-6.235-6.235-6.235-6.235 0-12.472l162.134-99.776zM733.375 417.313c6.235 6.235 6.235 6.235 0 12.472l-155.897 112.245c-6.235 6.235-6.235 12.472-6.235 18.707v62.359c0 6.235 6.235 6.235 6.235 6.235l230.728-155.897c6.235-6.235 6.235-12.472 6.235-18.707v-49.886c0-6.235-6.235-12.472-6.235-18.707l-230.728-155.897c-6.235-6.235-6.235 0-6.235 6.235v62.359c0 6.235 6.235 12.472 6.235 18.707l155.897 99.776z" />
<glyph unicode="&#xe893;" glyph-name="download" data-tags="download" d="M353.050 512.027c-11.773 0-17.66-5.887-5.887-17.66l153.059-188.382c5.887-11.773 23.547-11.773 29.433 0l153.059 188.382c5.887 11.773 5.887 17.66-5.887 17.66h-323.782zM570.865 500.253v135.399c0 11.773-11.773 23.547-23.547 23.547h-70.643c-11.773 0-23.547-11.773-23.547-23.547v-141.286h117.739zM647.395 364.854c-5.887 0-17.66-5.887-23.547-11.773l-76.53-94.19c-5.887-5.887-17.66-17.66-23.547-23.547 0 0-5.887-5.887-11.773-5.887s-17.66 11.773-17.66 11.773c-5.887 5.887-17.66 17.66-23.547 23.547l-76.53 94.19c-5.887 5.887-17.66 11.773-23.547 11.773h-123.626c-5.887 0-17.66-5.887-17.66-17.66v-141.286c0-5.887 5.887-17.66 17.66-17.66h529.824c5.887 0 17.66 5.887 17.66 17.66v141.286c0 5.887-5.887 17.66-17.66 17.66l-129.513-5.887zM300.067 253.004c-17.66 0-29.433 11.773-29.433 29.433s11.773 29.433 29.433 29.433c17.66 0 29.433-11.773 29.433-29.433s-11.773-29.433-29.433-29.433v0z" />
<glyph unicode="&#xe894;" glyph-name="delete" data-tags="delete" d="M615.111 426.667l134.045 134.045c10.311 10.311 10.311 30.934 0 41.245l-61.866 61.866c-10.311 10.311-30.934 10.311-41.245 0l-134.045-134.045-134.045 134.045c-10.311 10.311-30.934 10.311-41.245 0l-61.866-61.866c-10.311-10.311-10.311-30.934 0-41.245l134.045-134.045-134.045-134.045c-10.311-10.311-10.311-30.934 0-41.245l61.866-61.866c10.311-10.311 30.934-10.311 41.245 0l134.045 134.045 134.045-134.045c10.311-10.311 30.934-10.311 41.245 0l61.866 61.866c10.311 10.311 10.311 30.934 0 41.245l-134.045 134.045z" />
<glyph unicode="&#xe900;" glyph-name="edit-image" data-tags="edit-image" d="M300.336 749.664c69.018 23.142 133.325 14.234 189.133-33.28 56.627-48.128 77.619-110.592 63.181-183.808-2.355-12.186 0.307-19.456 8.704-27.853 93.901-93.389 156.774-156.467 250.47-250.163 5.427-5.427 10.957-10.854 15.667-16.896 39.424-50.278 16.794-124.006-44.237-142.029-36.966-10.957-68.403 0-95.334 27.034-95.642 96.051-160.973 160.973-256.614 257.024-6.963 6.963-12.8 8.909-22.63 6.758-117.76-26.317-229.171 60.826-231.731 181.453-0.614 26.419 3.584 52.326 15.974 77.926 34.816-34.816 68.506-67.789 101.274-101.786 10.445-10.752 20.992-15.36 36.045-15.36 14.643 0 25.19 3.891 34.816 14.848 10.752 12.39 23.040 23.347 34.611 35.021 14.336 14.438 14.336 46.080-0.205 60.518-35.123 35.226-70.349 70.349-106.598 106.496 3.891 2.253 5.632 3.482 7.475 4.096zM703.485 191.584c-0.41-24.269 20.685-45.466 44.851-45.158 23.757 0.41 44.032 20.992 43.93 44.544-0.102 23.757-20.275 44.032-44.237 44.134-23.859 0.307-44.237-19.661-44.544-43.52z" />
<glyph unicode="&#xe901;" glyph-name="hourglass" data-tags="hourglass" d="M733.414 117.419c-147.763 0-295.526 0-443.29 0 0 2.048 0.102 4.096 0 6.144-0.307 13.824-1.024 32.666-0.922 46.49 0.41 39.731 6.861 78.131 19.046 115.2 17.203 52.224 43.725 96.256 81.306 130.355 4.506 4.096 9.216 7.885 13.722 11.776-0.205 0.717-0.307 1.126-0.41 1.229-1.331 1.229-2.765 2.355-4.198 3.584-28.058 22.63-50.688 51.405-68.403 85.606-30.618 59.085-43.52 123.597-41.165 192.614 0.205 7.168 0.614 18.33 0.922 25.498 147.763 0 295.526 0 443.29 0 0.205-1.331 0.512-2.662 0.614-3.994 2.662-36.966 1.229-77.722-5.939-113.869-14.336-72.909-44.544-133.837-95.027-179.405-4.096-3.686-8.294-7.066-12.39-10.547 0.205-0.717 0.307-1.126 0.512-1.331 0.819-0.717 1.638-1.434 2.458-2.15 42.189-33.894 71.68-79.872 90.931-135.782 11.776-34.202 18.637-69.837 20.070-106.701 0.819-19.763-0.614-44.851-1.126-64.717zM687.437 160.632c0 6.554 0.205 12.493 0 18.432-1.331 37.581-7.27 74.138-19.866 108.749-17.92 49.562-45.568 88.269-88.678 108.646-2.458 1.126-2.97 3.072-2.97 5.837 0.102 16.691 0.102 33.485 0 50.176 0 3.994 1.331 5.427 4.096 6.963 9.114 5.325 18.432 10.24 26.829 16.896 29.696 23.552 49.152 56.934 62.362 95.744 10.342 30.413 15.77 62.259 17.818 94.822 0.614 9.114 0.102 18.227 0.102 27.546-116.634 0-233.574 0-351.027 0 0.307-8.704 0.614-16.998 1.024-25.395 1.946-37.274 8.499-73.216 21.504-107.213 18.125-47.104 45.261-83.558 86.528-103.117 2.253-1.024 2.867-2.662 2.867-5.427-0.102-17.203-0.102-34.509 0-51.712 0-3.072-1.024-4.506-3.277-5.632-5.632-2.867-11.366-5.734-16.691-9.216-34.304-22.733-56.832-57.754-71.987-100.147-12.493-35.123-18.125-71.987-19.661-109.875-0.205-5.325 0-10.752 0-16.282 117.35 0.205 234.189 0.205 351.027 0.205zM410.342 579.96c68.096 0 135.373 0 203.674 0-3.789-6.554-7.168-12.595-10.752-18.227-10.957-16.998-24.269-30.618-39.731-41.472s-27.75-25.293-32.768-46.592c-1.638-6.963-2.765-14.336-2.867-21.606-0.307-17.203-0.205-34.406 0.307-51.712 0.717-28.058 12.493-48.947 32.154-62.566 43.008-30.003 65.843-75.878 75.776-132.506 0.307-1.638 0.307-3.379 0.614-5.53-83.149 0-166.093 0-249.242 0 2.662 20.685 7.885 40.346 15.462 59.085 13.312 32.973 32.768 59.187 59.494 77.722 16.589 11.469 28.365 27.955 32.358 50.995 0.819 4.813 1.331 9.83 1.434 14.746 0.102 18.637 0.614 37.274-0.205 55.808-1.126 24.678-11.981 43.213-28.57 57.139-9.216 7.782-18.944 14.746-27.648 23.142-11.981 11.162-21.197 25.395-29.491 41.574z" />
<glyph unicode="&#xe902;" glyph-name="plus-icon" data-tags="plus-icon" d="M767.787 391.893c0-19.323-15.664-34.987-34.987-34.987h-151.040v-151.467c0-19.323-15.664-34.987-34.987-34.987h-69.547c-19.323 0-34.987 15.664-34.987 34.987v151.467h-151.467c-19.323 0-34.987 15.664-34.987 34.987v69.547c0 19.323 15.664 34.987 34.987 34.987h151.467v151.467c0 19.323 15.664 34.987 34.987 34.987h69.547c19.323 0 34.987-15.664 34.987-34.987v-151.467h151.467c19.323 0 34.987-15.664 34.987-34.987z" />
<glyph unicode="&#xe903;" glyph-name="video-upload-icon" data-tags="video-upload-icon" d="M395.848 387.201v-128c0-21.333 21.333-42.667 42.667-42.667h128c21.333 0 42.667 17.067 42.667 42.667v128c0 21.333-17.067 42.667-42.667 42.667h-128c-21.333 0-42.667-17.067-42.667-42.667zM796.915 557.867l-102.4 106.667c-12.8 12.8-38.4 21.333-55.467 21.333h-140.8l21.333-42.667h89.6v-136.533c0-17.067 12.8-29.867 29.867-29.867h140.8v-341.333h-426.667v328.533h-42.667v-341.333c0-17.067 12.8-29.867 29.867-29.867h448c17.067 0 29.867 12.8 29.867 29.867v384c4.267 17.067-8.533 38.4-21.333 51.2zM651.848 515.201v123.733c4.267 0 12.8-4.267 12.8-8.533l102.4-102.4c4.267-4.267 4.267-8.533 8.533-12.8h-123.733zM737.181 225.067v196.267c0 4.267-4.267 8.533-8.533 8.533s-8.533 0-12.8-4.267l-89.6-89.6v-29.867l89.6-89.6c8.533-4.267 8.533 0 12.8 0 4.267 4.267 8.533 4.267 8.533 8.533zM361.715 532.267v136.533l59.733-59.733c8.533-8.533 17.067-4.267 25.6 0l12.8 12.8c8.533 8.533 8.533 17.067 0 25.6l-115.2 106.667c-8.533 8.533-17.067 8.533-21.333 0l-115.2-110.933c-4.267-8.533-4.267-17.067 0-25.6l12.8-12.8c8.533-8.533 17.067-8.533 21.333 0l59.733 59.733v-136.533c0-8.533 8.533-17.067 17.067-17.067h25.6c0 0 17.067 8.533 17.067 21.333z" />
<glyph unicode="&#xe904;" glyph-name="play-icon" data-tags="play-icon" d="M375.444 661.357c81.067 46.933 187.733 46.933 273.067 0 42.667-25.6 72.533-55.467 98.133-98.133 72.533-128 29.867-294.4-98.133-371.2-128-72.533-294.4-29.867-371.2 98.133-46.933 81.067-46.933 187.733 0 273.067 21.333 42.667 55.467 72.533 98.133 98.133zM644.244 409.623c12.8 8.533 12.8 29.867 0 38.4l-192 110.933c-8.533 4.267-12.8 4.267-21.333 0s-12.8-12.8-12.8-21.333v-226.133c0-8.533 4.267-17.067 12.8-21.333s17.067-4.267 21.333 0l192 119.467z" />
<glyph unicode="&#xe905;" glyph-name="copy" data-tags="copy" d="M722.133 603.867h-247.733c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v288.533c-0.267 65.867-53.467 119.2-119.333 119.2zM778.533 199.2c0-31.333-25.067-56.4-56.4-56.4h-247.867c-31.333 0-56.4 25.067-56.4 56.4v285.467c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-285.467zM245.2 368.667v288.533c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-18.8h62.667v18.8c0 65.867-53.333 119.2-119.2 119.2h-247.467c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h18.8v62.667h-18.8c-31.467-3.067-56.533 22-56.533 56.533zM681.2 403.2h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 309.067h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 214.934h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333z" />
<glyph unicode="&#xe906;" glyph-name="examples-icon" data-tags="examples-icon" d="M213.333 275.201c89.6 38.4 183.467 68.267 273.067 17.067v281.6c-68.267 46.933-157.867 55.467-234.667 12.8l-38.4-311.467zM810.667 275.201l-42.667 315.733c-72.533 38.4-166.4 34.133-234.667-17.067v-285.867c93.867 51.2 187.733 21.333 277.333-12.8zM832 629.334c-51.2 29.867-110.933 46.933-170.667 55.467-51.2 0-102.4-8.533-149.333-29.867-46.933 21.333-98.133 29.867-149.333 29.867-59.733-4.267-119.467-25.6-170.667-55.467l-64-452.267c0 0 29.867-17.067 110.933 21.333 46.933 25.6 102.4 34.133 157.867 25.6 42.667-4.267 85.333-21.333 115.2-55.467v0c29.867 34.133 72.533 51.2 115.2 55.467 55.467 4.267 106.667-4.267 157.867-29.867 81.067-38.4 110.933-21.333 110.933-21.333l-64 456.533zM793.6 224.001c-42.667 21.333-89.6 34.133-140.8 34.133-8.533 0-21.333 0-29.867 0-42.667-4.267-81.067-17.067-115.2-42.667-34.133 25.6-72.533 38.4-115.2 42.667-12.8 0-21.333 0-34.133 0-46.933 0-93.867-8.533-136.533-29.867-21.333-12.8-46.933-21.333-72.533-25.6l64 413.867c46.933 25.6 98.133 38.4 149.333 42.667 46.933 0 93.867-8.533 136.533-25.6l12.8-8.533 12.8 4.267c42.667 17.067 89.6 25.6 136.533 25.6 51.2-4.267 102.4-17.067 149.333-42.667l59.733-418.133c-29.867 8.533-51.2 17.067-76.8 29.867z" />
<glyph unicode="&#xe907;" glyph-name="tutorials-icon" data-tags="tutorials-icon" d="M887.468 537.598l-375.467-110.933h-4.267l-217.6 68.267c-21.333-25.6-34.133-59.733-34.133-98.133 21.333-12.8 25.6-38.4 12.8-59.733-4.267-4.267-8.533-8.533-12.8-12.8l17.067-145.067c0-4.267 0-4.267-4.267-8.533 0 0 0 0-4.267 0h-64c-4.267 0-4.267 0-8.533 4.267 0 4.267-4.267 4.267-4.267 8.533l17.067 145.067c-12.8 8.533-17.067 21.333-17.067 34.133 0 17.067 8.533 29.867 21.333 38.4 0 38.4 12.8 76.8 34.133 110.933l-106.667 29.867c-8.533 4.267-8.533 8.533-8.533 17.067 0 4.267 4.267 4.267 4.267 4.267l375.467 119.467h4.267l375.467-123.733c4.267 0 8.533-4.267 8.533-8.533s-4.267-8.533-8.533-12.8zM725.334 341.332c4.267-46.933-93.867-85.333-213.333-85.333s-213.333 38.4-213.333 85.333l4.267 106.667 192-64c4.267 0 12.8 0 17.067 0s12.8 0 17.067 4.267l192 59.733 4.267-106.667z" />
<glyph unicode="&#xe908;" glyph-name="info-important-description" data-tags="info-important-description" d="M512 768.017c-188.5 0-341.3-152.8-341.3-341.3s152.8-341.4 341.3-341.4 341.3 152.8 341.3 341.3-152.8 341.4-341.3 341.4v0zM512 113.917c-172.7 0-312.7 140-312.7 312.7s140 312.7 312.7 312.7c172.7 0 312.7-140 312.7-312.7-0.2-172.6-140.1-312.5-312.7-312.7v0zM512 676.217c-137.9 0-249.6-111.8-249.6-249.6s111.7-249.6 249.6-249.6 249.6 111.8 249.6 249.6-111.8 249.6-249.6 249.6v0z" />
<glyph unicode="&#xe909;" glyph-name="icon-info" data-tags="icon-info" d="M468.875 565.974h87.467c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v70.385c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-87.462c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-69.959c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.715-4.373 9.2-7.159 15.338-7.245zM599.008 263.041h-22.187v209.92c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-130.128c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h22.182v-139.947h-22.187c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h174.075c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v46.065c0.043 0.527 0.067 1.141 0.067 1.761 0 5.302-1.791 10.185-4.8 14.079-3.742 4.424-9.36 7.247-15.636 7.247-0.489 0-0.975-0.017-1.456-0.051z" />
<glyph unicode="&#xe90a;" glyph-name="paste" data-tags="paste" d="M394.402 745.067h-75.333c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-56.4 37.6-100.4 87.867-116v69.067c-15.733 9.467-25.067 25.067-25.067 47.067v288.4c0 31.333 25.067 56.4 56.4 56.4h131.733c0 0 0 0 3.2 3.2v0c0 31.333-28.267 59.6-59.6 59.6zM704.802 635.2c0 0-28.267 0-40.8 0-12.533 34.533-43.867 59.6-84.667 59.6s-69.067-25.067-81.6-59.6c-12.533 0-40.8 0-40.8 0-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v285.467c3.2 65.867-53.2 122.267-119.2 122.267zM582.535 647.867c22 0 40.8-18.8 40.8-40.8s-18.8-40.8-40.8-40.8c-22 0-40.8 18.8-40.8 40.8s15.733 40.8 40.8 40.8zM764.402 224.4c0-31.333-25.067-56.4-56.4-56.4h-250.933c-31.333 0-56.4 25.067-56.4 56.4v288.533c0 18.8 9.467 37.6 25.067 47.067v0c0-43.867 34.533-78.4 78.4-78.4h160c43.867 0 78.4 34.533 78.4 78.4v0c12.533-9.467 22-28.267 22-47.067v-288.533z" />
<glyph unicode="&#xe90b;" glyph-name="reuse" data-tags="reuse" d="M734.974 624.751c-54.605 61.619-134.123 100.573-222.977 100.573-164.936 0-298.661-133.724-298.661-298.661h74.667c0 123.721 100.272 223.993 223.993 223.993 68.214 0 128.747-30.96 169.766-79.119l-70.213-70.213h199.109v199.109l-75.689-75.689zM511.999 202.671c-68.214 0-128.747 30.96-169.766 79.119l70.213 70.213h-199.109v-199.109l75.689 75.689c54.605-61.619 134.123-100.573 222.977-100.573 164.936 0 298.661 133.724 298.661 298.661h-74.667c0-123.721-100.272-223.993-223.993-223.993z" />
<glyph unicode="&#xe90c;" glyph-name="info-outlined" data-tags="info-outlined" d="M467.199 181.336h89.599v268.8h-89.599v-268.8zM512 853.335c-247.296 0-448.001-200.705-448.001-448.001s200.705-448.001 448.001-448.001 448.001 200.705 448.001 448.001-200.705 448.001-448.001 448.001zM512 46.936c-197.568 0-358.398 160.83-358.398 358.398s160.83 358.398 358.398 358.398 358.398-160.83 358.398-358.398-160.83-358.398-358.398-358.398zM467.199 539.734h89.599v89.599h-89.599v-89.599z" />
<glyph unicode="&#xe90d;" glyph-name="spinner" data-tags="spinner" d="M600 808.001c0-48.602-39.398-88-88-88s-88 39.398-88 88 39.398 88 88 88 88-39.398 88-88zM512 133.333c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.398 88-88 88zM893.334 514.667c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.398 88-88 88zM218.666 426.667c0 48.602-39.398 88-88 88s-88-39.398-88-88 39.398-88 88-88 88 39.398 88 88zM242.357 245.024c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88c0 48.6-39.4 88-88 88zM781.643 245.024c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88c0 48.6-39.398 88-88 88zM242.357 784.31c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.4 88-88 88z" />

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

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