Compare commits

...

142 Commits

Author SHA1 Message Date
Jun Pataleta 1724362afc Moodle release 4.3rc1 2023-10-04 07:58:44 +08:00
Jun Pataleta c3ec88f12d Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2023-10-04 07:58:41 +08:00
Ferran Recio 3f985bf3cf MDL-79458 output: fix subpanels in classic 2023-10-03 13:32:27 +02:00
Jun Pataleta b896c303f3 Merge branch 'MDL-79557-master' of https://github.com/jleyva/moodle 2023-10-03 12:18:13 +08:00
Jake Dallimore d7c348cfe1 Merge branch 'MDL-79056-master' of https://github.com/junpataleta/moodle 2023-10-03 12:02:52 +08:00
Andrew Nicols 213fd0cdb3 MDL-78580 mod_quiz: Do not concatenate lang strings 2023-10-03 12:02:29 +08:00
Andrew Nicols be8093e2c7 Merge branch 'MDL-78580_master' of https://github.com/marxjohnson/moodle 2023-10-03 11:05:42 +08:00
Huong Nguyen 6d6ed6822f Merge branch 'MDL-79247' of https://github.com/stronk7/moodle 2023-10-03 09:56:55 +07:00
Huong Nguyen 96b03bc501 Merge branch 'MDL-79555-master' of https://github.com/ilyatregubov/moodle 2023-10-03 09:44:04 +07:00
Huong Nguyen 4a7259f236 Merge branch 'MDL-79224_master' of https://github.com/marxjohnson/moodle 2023-10-03 09:38:53 +07:00
Jun Pataleta 6ee9522dfa Merge branch 'MDL-79254_master' of https://github.com/marxjohnson/moodle 2023-10-03 09:42:25 +08:00
Ilya Tregubov 9938ee3a04 Merge branch 'MDL-79534' of https://github.com/paulholden/moodle 2023-10-03 09:34:14 +08:00
Jun Pataleta cfc14c45bb Merge branch 'MDL-79535' of https://github.com/paulholden/moodle 2023-10-03 09:21:12 +08:00
Jun Pataleta 88b78ea367 Merge branch 'MDL-79364-master' of https://github.com/ilyatregubov/moodle 2023-10-03 09:16:38 +08:00
Juan Leyva 83461bcf5f MDL-79557 message: Clean subject field content for get_messages WS 2023-10-02 13:10:52 +02:00
Mark Johnson 6401ca4959 MDL-79224 core: Make Binary datafilter redisplay selected value
The Binary datafilter was returning a single value where all parts of
the API expect an array of values. This was working in most places by
fluke as this value was a single-character string, so doing $value[0]
returned the value. However, it was not working when deciding which
option to mark as selected when re-displaying the filter.

This change makes the filter return an array containing a single integer
value to match the rest of the API, then internally selects that single
value for comparison when deciding if an option should be selected.
2023-10-02 11:50:44 +01:00
Ilya Tregubov f591de2b58 MDL-79555 core_grades: Remove copy pasted code.
Seems that data from form was overridden for no reason.
2023-10-02 17:31:43 +08:00
Ilya Tregubov 94a13aeeff MDL-79364 backup: Fix conversion for 1.9 backups.
1.9 backups used current plugin version as oldversion.
But quiz uses some hardcoded version numbers when processing
restore which is lower then current plugin number, so some quiz
logic was ignored.

See define_structure(), process_quiz_question_instance() within
restore_quiz_stepslib.php
2023-10-02 15:26:50 +08:00
Ilya Tregubov c80e366051 Merge branch 'MDL-79458-master' of https://github.com/ferranrecio/moodle 2023-10-02 13:30:07 +08:00
Huong Nguyen 0d6fb2546d MDL-79344 tool_mfa: Use core/log for factor_webauthn 2023-10-02 11:41:53 +07:00
Huong Nguyen 8b395dcfc3 Merge branch 'MDL-79344-master' of https://github.com/stevandoMoodle/moodle 2023-10-02 11:36:59 +07:00
Eloy Lafuente (stronk7) f284cd9eaf MDL-79247 forum: Workaround same-time discussion modified dates bug
Within the forum_get_discussion_neighbours() function of forum
when the discussions timemodified (last post) is the same, there
is a bug that does strange things when calculating the prev and
next discussions.

Note that, in real life, this is really hard to achieve, but in tests,
when multiple discussions and post can be created by generators in the
same second (specially when the test machine is quick), chances of
facing that problem are higher.

By adding 1 second wait, we ensure that the discussions won't have
the same timemodified (last post) and workaround the problem.

No mater of that, the problem deserves an issue to be created
so we guarantee from code that it also works ok when the same
timemodified (last post) situation happens.
2023-10-01 23:23:30 +02:00
Eloy Lafuente (stronk7) 4fcb52cd45 MDL-79247 forum: Make discussion list ordering deterministic
Note that normally this doesn't matter much, but there are situations
when we want the discussion list ordering fully consistent /
deterministic.

Specifically, when discussions (or forum posts )are created in the
same second, or when the discussion titles are repeated, or 2
discussions have the same number of votes... (any criteria in general),
in the context of testing, we don't want the order
to be non-consistent, so we need to provide an extra sorting
criterion to make it fully deterministic.

So, in this case, we are adding a sort by discussion.id <<DIRECTION>>
that is an unique value, primary key... so cheap to calculate and,
that way, when the 1st sorting column has repeated values, the id
will decide.
2023-10-01 18:34:09 +02:00
AMOS bot 2e7514f4b0 Automatically generated installer lang files 2023-09-30 00:10:37 +00:00
Jun Pataleta 43d5aec47e on-demand release 4.3beta+ 2023-09-29 22:21:20 +08:00
Jun Pataleta 36b7d90e5e Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2023-09-29 22:21:17 +08:00
Jun Pataleta 7cf8ef39ad Merge branch 'MDL-79487-do-not-delete-refresh-token-master' of https://github.com/individual-it/moodle 2023-09-29 12:14:25 +08:00
Jun Pataleta 336ec5580a Merge branch 'MDL-79041-master-2' of https://github.com/HuongNV13/moodle 2023-09-29 11:59:39 +08:00
Jun Pataleta 7998d80302 MDL-79056 gradereport_user: Make column heading IDs unique
Associate column headings of user report tables to the user ID that the
report is being generated for. This will prevent duplicate IDs for the
report columns when multiple user report tables are displayed on the
User report page.
2023-09-29 11:03:14 +08:00
Jun Pataleta af22c038b7 MDL-79056 gradereport_user: Set grade category and item names as th
* The table cells mentioned in the `headers` attribute of other table
cells need to be using the table header <th> tag. Otherwise, HTML
validation fails.
2023-09-29 11:03:14 +08:00
Ilya Tregubov bd380a11ec Merge branch 'MDL-79280' of https://github.com/paulholden/moodle 2023-09-29 09:53:35 +08:00
Ilya Tregubov 45031e8205 Merge branch 'MDL-79521-master' of https://github.com/meirzamoodle/moodle 2023-09-29 09:21:40 +08:00
AMOS bot 96aac92ddc Automatically generated installer lang files 2023-09-29 00:08:13 +00:00
Paul Holden a8c8967055 MDL-79535 mod_quiz: use dummy URL in random questions external call.
It's not required by the external method, however its absence causes
debugging warnings about missing page URL in the error logs.
2023-09-28 19:08:47 +01:00
Paul Holden d7db5f08b0 MDL-79534 contenttype_h5p: correct fallback file used for icons. 2023-09-28 17:39:28 +01:00
Sara Arjona 458172696f Merge branch 'MDL-79511-master' of https://github.com/meirzamoodle/moodle 2023-09-28 16:15:47 +02:00
Sara Arjona d5bf67a67a Merge branch 'MDL-79499-master' of https://github.com/mihailges/moodle 2023-09-28 16:05:25 +02:00
Sara Arjona 3839d226ca Merge branch 'MDL-79482' of https://github.com/paulholden/moodle 2023-09-28 13:33:20 +02:00
Artur Neumann d4709d817e MDL-79487 oauth2: do not delete refresh token
when `log_out` is called from `\core\oauth2\client` it will delete the refresh token,
what it actually needs to use it to get a new access token
actually logging out is not needed here, the only thing we need to make sure is,
the invalid access token is removed from the session
that is done by storing `null`
2023-09-28 15:57:12 +05:45
Huong Nguyen 0094121765 Merge branch 'MDL-79274_master' of https://github.com/catalystfd/moodle 2023-09-28 15:48:49 +07:00
Sara Arjona f1721161c3 Merge branch 'MDL-79490' of https://github.com/paulholden/moodle 2023-09-28 10:46:17 +02:00
Ilya Tregubov 4f6240f630 Merge branch 'MDL-79502-master' of https://github.com/roland04/moodle 2023-09-28 16:45:07 +08:00
Ilya Tregubov 12b5c2e952 Merge branch 'MDL-79463' of https://github.com/paulholden/moodle 2023-09-28 16:40:35 +08:00
Sara Arjona 3f8a1b0107 Merge branch 'MDL-79477' of https://github.com/paulholden/moodle 2023-09-28 10:17:33 +02:00
Sara Arjona c571d3a0da Merge branch 'MDL-78918-master' of https://github.com/ssj365/moodle 2023-09-28 09:52:08 +02:00
Stevani Andolo 492fded0ec MDL-79344 tool_mfa: Fixed factor_webauthn request timeout handler 2023-09-28 15:48:34 +08:00
Stevani Andolo 369fee885f MDL-78750 communication_matrix: Fixed available providers 2023-09-28 09:13:22 +02:00
Meirza 3cda11b55c MDL-79521 tool_analytics: Export modal should use the ModalSaveCancel 2023-09-28 12:51:54 +07:00
Stevani Andolo a78921005f MDL-78750 communication_matrix: Dynamic settings 2023-09-28 07:51:35 +02:00
Sara Arjona 89eae3a811 Merge branch 'MDL-79350-master' of https://github.com/daledavies/moodle 2023-09-28 07:47:55 +02:00
Huong Nguyen f61b060182 Merge branch 'MDL-78813-master' of https://github.com/mihailges/moodle 2023-09-28 12:01:25 +07:00
Huong Nguyen 5c131030fd MDL-79041 core: Fix typo for MoodleNet resource URL 2023-09-28 11:33:00 +07:00
Huong Nguyen 202d57eef7 MDL-79041 core: Fix PHPDoc for prepare_file_share_request_data() 2023-09-28 11:33:00 +07:00
Huong Nguyen 1dda6c86b8 MDL-79041 core: Better handling of the MoodleNet resource URL
- Switch use PARAM_TEXT instead of PARAM_URL for resource URL
 - Added noreferrer to the Go to MoodleNet drafts button, to avoid the risks associated with opening in
   _blank without removing access to the referrer and opener
2023-09-28 11:32:59 +07:00
Meirza 0e8cb9c10b MDL-79511 lib: Allow dynamic properties for ADOFieldObject class 2023-09-28 10:15:48 +07:00
Huong Nguyen d2ad5c8c03 Merge branch 'MDL-75329' of https://github.com/paulholden/moodle 2023-09-28 09:35:44 +07:00
Ilya Tregubov 98d07e8514 Merge branch 'MDL-79285' of https://github.com/stronk7/moodle 2023-09-28 10:20:12 +08:00
Ilya Tregubov 78cbb2204d Merge branch 'MDL-79360/403' of https://github.com/skodak/moodle 2023-09-28 09:37:34 +08:00
Shamiso.Jaravaza d1c1acb155 MDL-78918 mod_bigbluebuttonbn: Fix notifications 2023-09-27 12:30:16 -06:00
Jun Pataleta e02ea67301 Merge branch 'MDL-77891-master' of https://github.com/snake/moodle 2023-09-27 23:50:32 +08:00
Jun Pataleta 3c7b5c983e Merge branch 'MDL-78905-master' of https://github.com/mihailges/moodle 2023-09-27 23:34:05 +08:00
Mark Johnson e5a7a18ae2 MDL-79254 quiz_statistics: Convert recalculate task to ad-hoc
Previously, quiz statistics processing happened on a scheduled task.
This task looked for all quizzes with completed attempts, then
determined if those quizzes had a statistics calculation that's newer
than the most recent attempt, then ran the statistics calculation if
needed. It was hard coded to stop processing after 1 hour.

The queries involved in determining which quizzes needed processing
weren't terribly efficient, and combined with the 1 hour limit this made
the statistics unusable on large sites, where they are the most useful.

This converts the scheduled task to an ad-hoc task, and uses an event
observer for mod_quiz\event\attempt_submitted to queue a task when
it is needed. This removes the need for a query to work out what needs
processing, and allows the task processing to be scaled up as needed.
2023-09-27 15:43:15 +01:00
Shamim Rezaie d22da878f1 MDL-79486 qbank_columnsortorder: Document get_data_from_datamethod 2023-09-27 22:59:04 +10:00
Shamim Rezaie 1ecb374344 Merge branch 'MDL-79486-master' of https://github.com/andrewnicols/moodle 2023-09-27 22:57:46 +10:00
Meirza 5dd79c7781 MDL-79511 auth: Avoid errors on the dynamic properties creation
Currently, Moodle uses fetchOBject() from ADOdb to get the table columns.
Sadly, the current ADOdb, especially the fetchObject() function, still creates dynamic properties.
Altering the fetchObject() with fetchRow() to avoid the dynamic properties deprecation error on PHP 8.2
2023-09-27 16:43:15 +07:00
Meirza a72d935dbc MDL-79511 enrol: Avoid errors on the dynamic properties creation
Currently, Moodle uses fetchOBject() from ADOdb to get the table columns.
Sadly, the current ADOdb, especially the fetchObject() function, still creates dynamic properties.
Altering the fetchObject() with fetchRow() to avoid the dynamic properties deprecation error on PHP 8.2
2023-09-27 16:42:50 +07:00
Sara Arjona 346fe1f78a Merge branch 'MDL-79432-master' of https://github.com/junpataleta/moodle 2023-09-27 10:22:21 +02:00
Mihail Geshoski 65b75f18cc MDL-78813 gradereport_grader: Revert border-top-width css change
Revert the modification of the border-top-width css attribute
to the grader table and the table header. This change seems to
be unnecessary.
2023-09-27 10:02:03 +02:00
Mihail Geshoski 8b9efc3135 MDL-78813 gradereport_grader: Make the average row sticky again
After MDL-75762 the overall average row in the grader report
was no longer properly pinned and hidden behind the sticky footer.
This change ensures that the overall average row is pinned again
at the top of the sticky footer when vertically scrolling. Also,
leveragning on the new sticky footer 'toggled' event it is making
sure that the row is properly pinned on narrower screens where the
sticky footer appears and disappears dynamically when scrolling.
2023-09-27 10:02:03 +02:00
Mihail Geshoski 4fa621a62f MDL-78813 core: Event to notify when the sticky footer has been toggled
New event in core/sticky-footer that is fired when the sticky footer has
been enabled or disabled. Leveraging this event, modules can create
event listeners and execute actions once the sticky footer has been
toggled.
2023-09-27 10:02:03 +02:00
Kevin Percy 01687d9125 MDL-78813 gradereport_grader: Fixed help icon covering course average 2023-09-27 10:02:03 +02:00
Mihail Geshoski 867e05ea34 MDL-78905 grades: Carry over form data without validation
Avoids validating form data which is carried over from the
simple dynamic form to the advanced form page when creating
or editing grade items, categories and outcomes in the
Gradebook setup.
2023-09-27 09:19:40 +02:00
Huong Nguyen 48f29f73e0 Merge branch 'MDL-79493-master' of https://github.com/ilyatregubov/moodle 2023-09-27 13:37:25 +07:00
Andrew Nicols 9a4a2df0e4 MDL-78750 communication: Fix missing test properties 2023-09-27 14:12:53 +08:00
Andrew Nicols 9739399741 Merge branch 'MDL-78750-master-int' of https://github.com/stevandoMoodle/moodle 2023-09-27 14:09:13 +08:00
Ilya Tregubov 1fbe7c1113 Merge branch 'MDL-79100-master' of https://github.com/meirzamoodle/moodle 2023-09-27 13:44:14 +08:00
Shamim Rezaie 2f29bac4f9 Merge branch 'MDL-78239-master' of https://github.com/HuongNV13/moodle 2023-09-27 14:39:54 +10:00
Huong Nguyen 9f0778ddb2 Merge branch 'MDL-79255-master' of https://github.com/call-learning/moodle 2023-09-27 11:12:39 +07:00
Huong Nguyen 08a669cba0 Merge branch 'MDL-79480' of https://github.com/paulholden/moodle 2023-09-27 11:10:35 +07:00
Huong Nguyen 82b661d8fa Merge branch 'MDL-79467' of https://github.com/paulholden/moodle 2023-09-27 10:44:02 +07:00
Jun Pataleta 6459f9f6b8 Merge branch 'MDL-78937-master' of https://github.com/meirzamoodle/moodle 2023-09-27 11:40:32 +08:00
Meirza d561e151e5 MDL-78937 lib: Set default value to $component property
We encountered errors on the plugin check page while upgrading from version 3.11 to 4.3.
The plugins marked with the status "To be deleted" have a null value for the $component property,
which results in the following error message:
"Deprecated: strpos(): Passing null to parameter #1 ($haystack) of type string is
deprecated in lib/classes/component.php on line 1056."
To avoid this error, we need to set an empty string as the default value for the $component property.
2023-09-27 09:53:47 +07:00
Ilya Tregubov 53717729a5 Merge branch 'MDL-79451' of https://github.com/paulholden/moodle 2023-09-27 10:01:37 +08:00
Andrew Nicols 3da39da392 Merge branch 'MDL-78847-master' of https://github.com/aanabit/moodle 2023-09-27 09:55:33 +08:00
Ilya Tregubov 386f29a46d Merge branch 'MDL-77708-master' of https://github.com/sarjona/moodle 2023-09-27 09:07:10 +08:00
Eloy Lafuente (stronk7) 71bc684c41 MDL-79285 xhprof: Add support for optional "reducedata" parameter
This new parameter / property will decide if we want to reduce
the run data before processing it:
- By default it will be disabled in table mode.
- By default it will be enabled in graph mode.
- The defaults can be changed by adding reducedata=[0|1] in the URLs
- Once data reduction is enabled, it stays enabled while
  navigating within the xhprof reports.
2023-09-26 19:39:48 +02:00
Eloy Lafuente (stronk7) 8ef2974af1 MDL-79285 xhprof: Cover the new functions with unit tests
This covers the 2 new functions with unit tests:
- xhprof_topo_sort()
- reduce_run_data()

Note that the example graph used in the provider is the
one shown in the issue to explain the reduction procedure.
2023-09-26 19:39:47 +02:00
Eloy Lafuente (stronk7) bc70bd69ed MDL-79285 xhprof: Enable reducing runs data for quicker use
Here we are reducing the xhprof runs data by removing the
__Mustache==>__Mustache calls and all the orphaned data.

To save N iterations what we are doing is:

0. The information is "topologically" sorted, so we ensure that
   all the parents in the data are processed before the children.
   (this will help a lot when cleaning orphaned data, see below).
1. First pass, all the candidate (by regexp) calls are removed
   from the run data.
2. Second pass, all the orphaned information (calls that have
   ended losing his parent) are also removed, so data is consistent.
   Note that, normally we would need N passes to remove all the
   orphaned data (because each pass creates new orphan candidates),
   but, as far as we have ensured that the information is topologically
   sorted (see point 0 above), all this can be done in one unique pass.

TODO:
  - Add unit tests.
  - Enable some system to be able to decide which utilities we
    want to get the data reduced and which ones will continue
    using the complete data. Right now the reduction is being
    applied to all the utilities (both table and graph views).
  - Document the change and, if implemented, the way to select
    between complete/reduced data.
  - Consider adding some caching to speed-up the reduction process
    (some TODOs have been left in the code pointing to the critical
    points).
2023-09-26 19:39:47 +02:00
Paul Holden c16587f52f MDL-79480 repository_wikimedia: drop deprecated image size parameter. 2023-09-26 16:21:43 +01:00
Paul Holden 4fd6045c2a MDL-79482 qbank_columnsortorder: improve resize column modal header. 2023-09-26 16:20:10 +01:00
Mikel Martín fbc7a8f5f3 MDL-79502 courseformat: Fix completion button colour contrast 2023-09-26 16:51:06 +02:00
Sara Arjona c6e727732a Moodle release 4.3beta 2023-09-26 16:19:45 +02:00
Mihail Geshoski abfff66a86 MDL-79499 gradereport_grader: Pin properly the user name column
After modifying the 'overflow-y' css attribute of the #page element
in MDL-75762, the user name column is no longer properly pinned.
The existing 'top' css attribute on the column cells does not work
well with the newly assigned overlow on the #page element and
causes the columns to be partially cut off by the page when
scrolling horizontally. Re-adjusting the 'top' css attribute to '0'
solves this problem.
2023-09-26 15:27:11 +02:00
Ferran Recio 0184a477d5 MDL-79458 output: fix action menu subpanel behats 2023-09-26 13:22:51 +02:00
Paul Holden 6de6f64199 MDL-75329 course: defensive counting of course sections.
Avoids errors due to pre-existing integrity issues with course
data, where the absence entirely of section data would result in
PHP errors.
2023-09-26 12:18:32 +01:00
Paul Holden 2aa56c770d MDL-79477 tool_moodlenet: better handling of network download test.
We can't assume the presense of `Content-Length` header in response
to a HEAD request. Modify test assertion to account for this.
2023-09-26 11:14:46 +01:00
Amaia Anabitarte 8874725c53 MDL-78847 mod_folder: Fix inline folder renderer 2023-09-26 10:39:35 +02:00
Sara Arjona cc9430929d MDL-77708 docs: Update references from docs.moodle.org/dev 2023-09-26 10:30:19 +02:00
Huong Nguyen e38aa09dee MDL-78239 editor_tiny: Calculate correct height for non-visible editors 2023-09-26 14:50:13 +07:00
Ilya Tregubov 0f4efffb76 MDL-79493 core_grades: Fix Behat tests. 2023-09-26 15:49:43 +08:00
meirzamoodle 6246db3d30 MDL-79100 core_communication: Update communication-related lang strings 2023-09-26 12:12:38 +07:00
Jun Pataleta 4cb2b3c758 Merge branch 'MDL-79484' of https://github.com/paulholden/moodle 2023-09-26 12:22:52 +08:00
Jun Pataleta 4569711e18 Merge branch 'MDL-79485' of https://github.com/paulholden/moodle 2023-09-26 12:08:12 +08:00
Jun Pataleta 535747fbc7 Merge branch 'MDL-79481' of https://github.com/paulholden/moodle 2023-09-26 12:06:16 +08:00
Jun Pataleta 2f3033646c Merge branch 'MDL-76713-master' of https://github.com/laurentdavid/moodle 2023-09-26 11:57:24 +08:00
Jun Pataleta a24e9872ef Merge branch 'MDL-79442-master' of https://github.com/snake/moodle 2023-09-26 11:48:31 +08:00
Jun Pataleta 3c2589fb58 Merge branch 'MDL-79400-master' of https://github.com/HuongNV13/moodle 2023-09-26 11:20:02 +08:00
Jun Pataleta 7a369aa0a8 Merge branch 'MDL-79420-master' of https://github.com/HuongNV13/moodle 2023-09-26 10:57:46 +08:00
Huong Nguyen a26954ce4a Merge branch 'MDL-79483' of https://github.com/paulholden/moodle 2023-09-26 09:18:31 +07:00
Stevani Andolo d644c3d89d MDL-78750 communication_matrix: Dynamic settings 2023-09-26 09:55:54 +08:00
Paul Holden acc510ced7 MDL-79490 communication_matrix: include required test library. 2023-09-25 18:31:52 +01:00
Huong Nguyen 440bf51af2 Merge branch 'MDL-79444-master' of https://github.com/meirzamoodle/moodle 2023-09-25 15:24:47 +07:00
Laurent David 8790501437 MDL-76713 core_availability: Fix date restriction conflict
* Use a unique ID the date nodes in the HTML tree to be able to find the current node.
* Look for nodes in the same tree "leaf" and work on conflicts in this single leaf/branch.
2023-09-25 10:16:44 +02:00
Huong Nguyen df76743906 Merge branch 'MDL-79322-master' of https://github.com/mickhawkins/moodle 2023-09-25 15:04:15 +07:00
Huong Nguyen b27f6b0eca Merge branch 'MDL-79440-master' of https://github.com/snake/moodle 2023-09-25 14:56:49 +07:00
Shamim Rezaie 832ee471be Merge branch 'MDL-75928-master' of https://github.com/lameze/moodle 2023-09-25 17:51:29 +10:00
Shamim Rezaie 9e9dab8045 Merge branch 'MDL-79348-master' of https://github.com/roland04/moodle 2023-09-25 17:39:41 +10:00
Andrew Nicols 9c07defb77 MDL-79486 qbank_columnsortorder: Data providers must not create data 2023-09-25 14:34:56 +08:00
Paul Holden 3f6dd17042 MDL-79485 tool_mfa: replace hard-coded verification lang strings. 2023-09-24 21:21:54 +01:00
Paul Holden 4cbce0bae2 MDL-79484 tool_mfa: remove hard-coded assumption of "and" lang string. 2023-09-24 18:56:24 +01:00
Paul Holden bf6dcc5779 MDL-79483 qbank_columnsortorder: set pix_icon for each column action. 2023-09-24 16:54:20 +01:00
Paul Holden 76a0561f91 MDL-79481 question: replace hard-coded template language strings. 2023-09-24 16:09:40 +01:00
Paul Holden 379bac1fe7 MDL-79467 output: handle empty sitename display during installation.
Causes PHP warnings when null on PHP 8.1.
2023-09-22 14:32:11 +01:00
Paul Holden efe3e377b6 MDL-79280 mod_quiz: Behat scenario for adding multiple user overrides. 2023-09-22 10:31:59 +01:00
Tim Hunt 83e840b8e7 MDL-79280 mod_quiz: consistently store ->cm as a cm_info 2023-09-22 10:31:59 +01:00
Paul Holden c518c78ca4 MDL-79463 output: invert logic in url_select change event.
Avoids eslint `consistent-return` errors (because we previously only
returned from the conditional branch).
2023-09-22 10:21:44 +01:00
Mikel Martín 5a5c6ff179 MDL-79348 courseformat: Fix focus indicator of edit restrictions link 2023-09-22 08:34:24 +02:00
Michael Hawkins 81a1695e71 MDL-79322 core: Update MoodleNet course sharing to use course fullname
Previously this was using course short name, which is inconsistent and
less informative than the course full name.
2023-09-22 11:42:28 +08:00
Jake Dallimore 72e867f9e6 MDL-79440 mod_lti: fix missing array key warning on LTI 2 edit
These fields only exist for LTI 1.1 and LTI 1.3 edits, so can be
ignored in LTI 2.0 edits. This change fixes this for the tool
type edit (edit_form) and for the tool instance form (mod_form).
2023-09-22 10:15:20 +08:00
Jake Dallimore 26d0ac156e MDL-79442 mod_lti: launch container should inherit from tool config
Specifically in the case where multiple content items are returned, and
when we hit the content_item_to_form() method, ensure that content-item-
specific values do not influence the final value of launchcontainer,
which must use the value set in tool configuration.
2023-09-22 10:13:56 +08:00
meirzamoodle b7fbbaf7f4 MDL-79444 theme: Move the autocomplete list above the TinyMCE editor 2023-09-22 03:15:55 +07:00
Paul Holden d7d8119201 MDL-79451 mod_bigbluebuttonbn: fix request action dialog cancelling. 2023-09-21 14:37:49 +01:00
Jun Pataleta 54226e8157 MDL-79432 calendar: Deprecate unused functions
* calendar_get_link_previous() and calendar_get_link_next() are no
longer used anywhere except from the already deprecated
calendar_top_controls() so we should deprecate these as well.
2023-09-20 15:39:46 +02:00
Laurent David 0304eacfd5 MDL-79255 mod_bigbluebuttonbn: Allow subplugins to be uninstalled
* Allow subplugins to be uninstalled
2023-09-20 14:56:50 +02:00
Jake Dallimore 38668033ff MDL-77891 core_calendar: support display of mod iconurl
This affects:
- event_item.mustache (day view)
- event_icon.mustache (used by month_mini and month_mini and
month_detailed templates)
2023-09-20 17:03:08 +08:00
Huong Nguyen 90e3a3d994 MDL-79420 core: Remove invalid MoodleNet template call 2023-09-20 11:38:54 +07:00
Huong Nguyen 2bc1598138 MDL-79400 Calendar: Use catch() for native Promise 2023-09-19 09:10:45 +07:00
Dale Davies 06a9b91e17 MDL-79350 core: Do not store lang in session when readonly 2023-09-18 14:30:14 +01:00
Simey Lameze 9d607df0d9 MDL-75928 format_week: improve get_section_dates one week calc 2023-09-18 00:09:57 +08:00
Mark Johnson a92e5c5770 MDL-78580 report_statistics: Prevent database deadlocks when viewing
If a quiz had a long job to calculate statstics running, this would
cause pages that may also attempt a recalculation (the statistics report
page or question bank) to load very slowly, and possibly result in a
database deadlock.

This change will firstly prevent the question bank page performing
analysis calculations at all, since these are not required for this
page, which will speed up loading and prevent deadlocks on this page.

Secondly, this adds a lock to the recalcuation process so that it cannot
run twice concurrently. This will present the user with a message to
indicate that it is waiting for a running calculation until it is
complete, and eventually it will timeout with a message and debugging.
2023-09-15 15:16:52 +01:00
Petr Skoda f35b2d1cb3 MDL-79360 filter: fix nolink tag regression from MDL-77525 2023-09-15 12:44:09 +02:00
Francis Devine a77158fd96 MDL-79274 assignfeedback_editpdf: Fix permission error in conversions
This was causing the adhoc task to fail with a nopermission error, when
a user was unenrolled from a course before their assignment submission
could be converted into a pdf.

Comments from MDL-56810 indicate the correct approach is to not attempt
a conversion in those cases.
2023-09-07 09:26:09 +12:00
248 changed files with 3085 additions and 1048 deletions
+1 -1
View File
@@ -16,4 +16,4 @@ Moodle - the world's open source learning platform
This generated documentation includes API documentation for JavaScript written in the AMD and ES2015 module formats within Moodle.
## Related information
See [https://docs.moodle.org/dev](https://docs.moodle.org/dev) for other related Developer Documentation.
See [https://moodledev.io](https://moodledev.io) for other related Developer Documentation.
+1 -1
View File
@@ -114,7 +114,7 @@ module.exports = {
],
"menu": {
"Developer Docs": {
href: "https://docs.moodle.org/dev",
href: "https://moodledev.io",
target: "_blank",
"class": "menu-item",
id: "devdocs"
+3 -3
View File
@@ -6,7 +6,7 @@ of developers, designers, teachers, testers, translators and other users. We
work in universities, schools, companies and other places. You are very welcome
to join us and contribute to the project.
See <https://docs.moodle.org/dev/Contributing_to_Moodle> for the many ways you
See <https://moodledev.io/general/community/contribute> for the many ways you
can help, not only with coding.
Moodle is open to community contributions to core, though all code must go
@@ -35,7 +35,7 @@ submitted patches has evolved.
* New features are developed on the master branch. Bug fixes are also
backported to currently supported maintenance (stable) branches.
For further details, see <https://docs.moodle.org/dev/Process>.
For further details, see <https://moodledev.io/general/development/process>.
Moodle plugins
--------------
@@ -53,4 +53,4 @@ be easily installed and updated via the Moodle administration interface.
the plugins directory. We do not pull from your code repository; you must do
it explicitly.
For further details, see <https://docs.moodle.org/dev/Plugin_contribution>.
For further details, see <https://moodledev.io/general/community/plugincontribution>.
+1 -1
View File
@@ -20,7 +20,7 @@ Moodle is widely used around the world by universities, schools, companies and
all manner of organisations and individuals.
Moodle is provided freely as open source software, under the GNU General Public
License <https://docs.moodle.org/dev/License>.
License <https://moodledev.io/general/license>.
Moodle is written in PHP and JavaScript and uses an SQL database for storing
the data.
+1 -1
View File
@@ -21,7 +21,7 @@
* Displays the list of found local plugins, their version (if found) and
* a link to delete the local plugin.
*
* @see http://docs.moodle.org/dev/Local_customisation
* @see https://moodledev.io/docs/apis/plugintypes/local
* @package admin
* @copyright 2010 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+1 -1
View File
@@ -20,7 +20,7 @@
* Displays the list of found plagiarism plugins, their version (if found) and
* a link to uninstall the plagiarism plugin.
*
* @see http://docs.moodle.org/dev/Plagiarism_API
* @see https://moodledev.io/docs/apis/subsystems/plagiarism
* @package admin
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+4 -4
View File
@@ -44,7 +44,7 @@ class core_admin_renderer extends plugin_renderer_base {
$output .= $this->heading(get_string('copyrightnotice'));
$output .= $this->box($copyrightnotice, 'copyrightnotice');
$output .= html_writer::empty_tag('br');
$output .= $this->confirm(get_string('doyouagree'), $continue, "http://docs.moodle.org/dev/License");
$output .= $this->confirm(get_string('doyouagree'), $continue, "https://moodledev.io/general/license");
$output .= $this->footer();
return $output;
@@ -745,10 +745,10 @@ class core_admin_renderer extends plugin_renderer_base {
//////////////////////////////////////////////////////////////////////////////////////////////////
//// IT IS ILLEGAL AND A VIOLATION OF THE GPL TO HIDE, REMOVE OR MODIFY THIS COPYRIGHT NOTICE ///
$copyrighttext = '<a href="http://moodle.org/">Moodle</a> '.
'<a href="http://docs.moodle.org/dev/Releases" title="'.$CFG->version.'">'.$CFG->release.'</a><br />'.
'<a href="https://moodledev.io/general/releases" title="'.$CFG->version.'">'.$CFG->release.'</a><br />'.
'Copyright &copy; 1999 onwards, Martin Dougiamas<br />'.
'and <a href="http://moodle.org/dev">many other contributors</a>.<br />'.
'<a href="http://docs.moodle.org/dev/License">GNU Public License</a>';
'<a href="https://moodledev.io/general/license">GNU Public License</a>';
//////////////////////////////////////////////////////////////////////////////////////////////////
return $this->box($copyrighttext, 'copyright');
@@ -992,7 +992,7 @@ class core_admin_renderer extends plugin_renderer_base {
* @return string HTML to output.
*/
protected function release_notes_link() {
$releasenoteslink = get_string('releasenoteslink', 'admin', 'http://docs.moodle.org/dev/Releases');
$releasenoteslink = get_string('releasenoteslink', 'admin', 'https://moodledev.io/general/releases');
$releasenoteslink = str_replace('target="_blank"', 'onclick="this.target=\'_blank\'"', $releasenoteslink); // extremely ugly validation hack
return $this->box($releasenoteslink, 'generalbox alert alert-info');
}
+1 -1
View File
@@ -259,7 +259,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
get_enabled_providers_and_default();
$temp->add(new admin_setting_configselect('moodlecourse/coursecommunicationprovider',
new lang_string('seleccommunicationprovider', 'communication'),
new lang_string('selectcommunicationprovider', 'communication'),
new lang_string('coursecommunication_desc', 'course'),
$defaulprovider, $communicationproviders));
+1 -1
View File
@@ -5,6 +5,6 @@
* @copyright 2017 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("tool_analytics/model",["jquery","core/str","core/log","core/notification","core/modal_save_cancel","core/modal_cancel","core/modal_events","core/templates"],(function($,Str,log,Notification,ModalSaveCancel,ModalCancel,ModalEvents,Templates){var actionsList={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}},delete:{title:{key:"delete",component:"tool_analytics"},body:{key:"deletemodelconfirmation",component:"tool_analytics"}}};return{confirmAction:function(actionId,actionType){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(void 0!==actionsList[actionType]){var wrap,reqStrings=[actionsList[actionType].title,actionsList[actionType].body];reqStrings[1].param=(wrap=$(a).closest("[data-model-name]")).length?wrap.attr("data-model-name"):(log.error("Unexpected DOM error - unable to obtain the model name"),"");var stringsPromise=Str.get_strings(reqStrings),modalPromise=ModalSaveCancel.create({});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setBody(strings[1]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}else log.error('Action "'+actionType+'" is not allowed.')}))},selectEvaluationOptions:function(actionId,trainedOnlyExternally){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget),timeSplittingMethods=$(this).attr("data-timesplitting-methods");ModalSaveCancel.create({title:Str.get_string("evaluatemodel","tool_analytics"),body:Templates.render("tool_analytics/evaluation_options",{trainedexternally:trainedOnlyExternally,timesplittingmethods:JSON.parse(timeSplittingMethods)}),removeOnClose:!0,buttons:{save:Str.get_string("evaluate","tool_analytics")},show:!0}).then((modal=>(modal.getRoot().on(ModalEvents.save,(function(){"trainedmodel"==$("input[name='evaluationmode']:checked").val()&&a.attr("href",a.attr("href")+"&mode=trainedmodel");var timeSplittingMethod=$("#id-evaluation-timesplitting").val();a.attr("href",a.attr("href")+"&timesplitting="+timeSplittingMethod),window.location.href=a.attr("href")})),modal))).catch(Notification.exception)}))},selectExportOptions:function(actionId,isTrained){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(!isTrained)return a.attr("href",a.attr("href")+"&action=exportmodel&includeweights=0"),void(window.location.href=a.attr("href"));var stringsPromise=Str.get_strings([{key:"export",component:"tool_analytics"}]),modalPromise=ModalCancel.create({body:Templates.render("tool_analytics/export_options",{}),removeOnClose:!0});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){"exportdata"==$("input[name='exportoption']:checked").val()?a.attr("href",a.attr("href")+"&action=exportdata"):(a.attr("href",a.attr("href")+"&action=exportmodel"),$("#id-includeweights").is(":checked")?a.attr("href",a.attr("href")+"&includeweights=1"):a.attr("href",a.attr("href")+"&includeweights=0")),window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}))}}}));
define("tool_analytics/model",["jquery","core/str","core/log","core/notification","core/modal_save_cancel","core/modal_cancel","core/modal_events","core/templates"],(function($,Str,log,Notification,ModalSaveCancel,ModalCancel,ModalEvents,Templates){var actionsList={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}},delete:{title:{key:"delete",component:"tool_analytics"},body:{key:"deletemodelconfirmation",component:"tool_analytics"}}};return{confirmAction:function(actionId,actionType){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(void 0!==actionsList[actionType]){var wrap,reqStrings=[actionsList[actionType].title,actionsList[actionType].body];reqStrings[1].param=(wrap=$(a).closest("[data-model-name]")).length?wrap.attr("data-model-name"):(log.error("Unexpected DOM error - unable to obtain the model name"),"");var stringsPromise=Str.get_strings(reqStrings),modalPromise=ModalSaveCancel.create({});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setBody(strings[1]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}else log.error('Action "'+actionType+'" is not allowed.')}))},selectEvaluationOptions:function(actionId,trainedOnlyExternally){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget),timeSplittingMethods=$(this).attr("data-timesplitting-methods");ModalSaveCancel.create({title:Str.get_string("evaluatemodel","tool_analytics"),body:Templates.render("tool_analytics/evaluation_options",{trainedexternally:trainedOnlyExternally,timesplittingmethods:JSON.parse(timeSplittingMethods)}),removeOnClose:!0,buttons:{save:Str.get_string("evaluate","tool_analytics")},show:!0}).then((modal=>(modal.getRoot().on(ModalEvents.save,(function(){"trainedmodel"==$("input[name='evaluationmode']:checked").val()&&a.attr("href",a.attr("href")+"&mode=trainedmodel");var timeSplittingMethod=$("#id-evaluation-timesplitting").val();a.attr("href",a.attr("href")+"&timesplitting="+timeSplittingMethod),window.location.href=a.attr("href")})),modal))).catch(Notification.exception)}))},selectExportOptions:function(actionId,isTrained){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(!isTrained)return a.attr("href",a.attr("href")+"&action=exportmodel&includeweights=0"),void(window.location.href=a.attr("href"));var stringsPromise=Str.get_strings([{key:"export",component:"tool_analytics"}]),modalPromise=ModalSaveCancel.create({body:Templates.render("tool_analytics/export_options",{}),removeOnClose:!0});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){"exportdata"==$("input[name='exportoption']:checked").val()?a.attr("href",a.attr("href")+"&action=exportdata"):(a.attr("href",a.attr("href")+"&action=exportmodel"),$("#id-includeweights").is(":checked")?a.attr("href",a.attr("href")+"&includeweights=1"):a.attr("href",a.attr("href")+"&includeweights=0")),window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}))}}}));
//# sourceMappingURL=model.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -185,7 +185,7 @@ define([
component: 'tool_analytics'
}
]);
var modalPromise = ModalCancel.create({
var modalPromise = ModalSaveCancel.create({
body: Templates.render('tool_analytics/export_options', {}),
removeOnClose: true,
});
@@ -9,7 +9,7 @@ menu: "main"
## Moodle templates
[Moodle templates](https://docs.moodle.org/dev/Templates) are use to write HTML and Javascript using mustache files.
[Moodle templates](https://moodledev.io/docs/guides/templates) are use to write HTML and Javascript using mustache files.
If you are creating your own pages in the UI Component library you can load core templates using this (shortcode) syntax:
@@ -51,4 +51,3 @@ This is the result of adding the core/notification template on this page:
"extraclasses": "foo bar"
}
{{< /mustache >}}
@@ -11,7 +11,7 @@ tags:
## How to use moodle forms
Forms are constructed using lib/formslib.php. Using the addElement methods in php a set of different form element types can be added to a form. For more info visit the [Moodledocs](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition) page for forms
Forms are constructed using lib/formslib.php. Using the addElement methods in php a set of different form element types can be added to a form. For more info visit the [Moodledocs](https://moodledev.io/docs/apis/subsystems/form) page for forms
{{< php >}}
$mform->addElement('button', 'intro', get_string("buttonlabel"));
@@ -61,7 +61,7 @@ Disabled toggle with extra classes.
## Use toggle as a template block
It is also possible to include *core/toggle* in any other template using [blocks](https://docs.moodle.org/dev/Templates#Blocks), instead of rendering it with a context.
It is also possible to include *core/toggle* in any other template using [blocks](https://moodledev.io/docs/guides/templates#blocks), instead of rendering it with a context.
The parameters that you can define are:
* id: Unique id for the toggle input.
* extraclasses: Any extra classes added to the toggle input outer container.
@@ -58,7 +58,7 @@ class behat_tool_dataprivacy extends behat_base {
);
/**
* Creates the specified element. More info about available elements in http://docs.moodle.org/dev/Acceptance_testing#Fixtures.
* Creates the specified element. More info about available elements in https://moodledev.io/general/development/tools/behat.
*
* @Given /^the following data privacy "(?P<element_string>(?:[^"]|\\")*)" exist:$/
*
@@ -94,7 +94,7 @@ class behat_tool_lp_data_generators extends behat_base {
);
/**
* Creates the specified element. More info about available elements in http://docs.moodle.org/dev/Acceptance_testing#Fixtures.
* Creates the specified element. More info about available elements in https://moodledev.io/general/development/tools/behat.
*
* @Given /^the following lp "(?P<element_string>(?:[^"]|\\")*)" exist:$/
*
@@ -16,6 +16,8 @@
namespace tool_mfa\local;
use tool_mfa\local\factor\object_factor_base;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/ddllib.php');
@@ -200,14 +202,13 @@ class admin_setting_managemfa extends \admin_setting {
$table->colclasses = ['leftalign', 'centeralign'];
$table->data = [];
$factorstringconnector = get_string('connector', 'tool_mfa');
foreach ($combinations as $combination) {
$string = '';
foreach ($combination['combination'] as $factor) {
$string .= ' ' . get_string('connector', 'tool_mfa') . ' ' . $factor->get_summary_condition()
. ' <sup>' . $factor->get_weight() . '</sup>';
}
$factorstrings = array_map(static function(object_factor_base $factor): string {
return $factor->get_summary_condition() . ' <sup>' . $factor->get_weight() . '</sup>';
}, $combination['combination']);
$string = substr($string, 4);
$string = implode(" {$factorstringconnector} ", $factorstrings);
$table->data[] = new \html_table_row([$string, $combination['totalweight']]);
}
+1 -1
View File
@@ -6,6 +6,6 @@
* @author Alex Morris <alex.morris@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("factor_webauthn/register",["factor_webauthn/utils"],(function(utils){return{init:function(createArgs){document.getElementById("factor_webauthn-register").addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===(createArgs=JSON.parse(createArgs)).success)throw new Error(createArgs.msg||"unknown error occured");utils.recursiveBase64StrToArrayBuffer(createArgs);const cred=await navigator.credentials.create(createArgs),authenticatorResponse={transports:cred.response.getTransports?cred.response.getTransports():null,clientDataJSON:cred.response.clientDataJSON?utils.arrayBufferToBase64(cred.response.clientDataJSON):null,attestationObject:cred.response.attestationObject?utils.arrayBufferToBase64(cred.response.attestationObject):null};document.getElementById("id_response_input").value=JSON.stringify(authenticatorResponse)}))}}}));
define("factor_webauthn/register",["factor_webauthn/utils","core/log"],(function(utils,Log){return{init:function(createArgs){createArgs=JSON.parse(createArgs),document.getElementById("factor_webauthn-register").addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===createArgs.success)throw new Error(createArgs.msg||"unknown error occured");try{utils.recursiveBase64StrToArrayBuffer(createArgs);const cred=await navigator.credentials.create(createArgs),authenticatorResponse={transports:cred.response.getTransports?cred.response.getTransports():null,clientDataJSON:cred.response.clientDataJSON?utils.arrayBufferToBase64(cred.response.clientDataJSON):null,attestationObject:cred.response.attestationObject?utils.arrayBufferToBase64(cred.response.attestationObject):null};document.getElementById("id_response_input").value=JSON.stringify(authenticatorResponse)}catch(e){Log.debug("The request timed out or you have canceled the request. Please try again later.")}}))}}}));
//# sourceMappingURL=register.min.js.map
@@ -1 +1 @@
{"version":3,"file":"register.min.js","sources":["../src/register.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * For collecting WebAuthn authenticator details on factor setup\n *\n * @module factor_webauthn/register\n * @copyright Catalyst IT\n * @author Alex Morris <alex.morris@catalyst.net.nz>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils'], function(utils) {\n return {\n init: function(createArgs) {\n document.getElementById('factor_webauthn-register').addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n createArgs = JSON.parse(createArgs);\n\n if (createArgs.success === false) {\n throw new Error(createArgs.msg || 'unknown error occured');\n }\n\n utils.recursiveBase64StrToArrayBuffer(createArgs);\n\n const cred = await navigator.credentials.create(createArgs);\n\n const authenticatorResponse = {\n transports: cred.response.getTransports ? cred.response.getTransports() : null,\n clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n attestationObject:\n cred.response.attestationObject ? utils.arrayBufferToBase64(cred.response.attestationObject) : null\n };\n\n document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);\n });\n }\n };\n});\n"],"names":["define","utils","init","createArgs","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","JSON","parse","success","msg","recursiveBase64StrToArrayBuffer","cred","authenticatorResponse","transports","response","getTransports","clientDataJSON","arrayBufferToBase64","attestationObject","value","stringify"],"mappings":";;;;;;;;AAwBAA,kCAAO,CAAC,0BAA0B,SAASC,aAChC,CACHC,KAAM,SAASC,YACXC,SAASC,eAAe,4BAA4BC,iBAAiB,SAASC,eAAeC,MACzFA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAKO,KAF3BV,WAAaW,KAAKC,MAAMZ,aAETa,cACL,IAAIH,MAAMV,WAAWc,KAAO,yBAGtChB,MAAMiB,gCAAgCf,kBAEhCgB,WAAaT,UAAUC,YAAYC,OAAOT,YAE1CiB,sBAAwB,CAC1BC,WAAYF,KAAKG,SAASC,cAAgBJ,KAAKG,SAASC,gBAAkB,KAC1EC,eAAgBL,KAAKG,SAASE,eAAiBvB,MAAMwB,oBAAoBN,KAAKG,SAASE,gBAAkB,KACzGE,kBACIP,KAAKG,SAASI,kBAAoBzB,MAAMwB,oBAAoBN,KAAKG,SAASI,mBAAqB,MAGvGtB,SAASC,eAAe,qBAAqBsB,MAAQb,KAAKc,UAAUR"}
{"version":3,"file":"register.min.js","sources":["../src/register.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * For collecting WebAuthn authenticator details on factor setup\n *\n * @module factor_webauthn/register\n * @copyright Catalyst IT\n * @author Alex Morris <alex.morris@catalyst.net.nz>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils', 'core/log'], function(utils, Log) {\n return {\n init: function(createArgs) {\n createArgs = JSON.parse(createArgs);\n document.getElementById('factor_webauthn-register').addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n if (createArgs.success === false) {\n throw new Error(createArgs.msg || 'unknown error occured');\n }\n\n try {\n utils.recursiveBase64StrToArrayBuffer(createArgs);\n const cred = await navigator.credentials.create(createArgs);\n const authenticatorResponse = {\n transports: cred.response.getTransports ? cred.response.getTransports() : null,\n clientDataJSON: cred.response.clientDataJSON ?\n utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n attestationObject: cred.response.attestationObject ?\n utils.arrayBufferToBase64(cred.response.attestationObject) : null,\n };\n document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);\n } catch (e) {\n Log.debug('The request timed out or you have canceled the request. Please try again later.');\n }\n });\n }\n };\n});\n"],"names":["define","utils","Log","init","createArgs","JSON","parse","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","success","msg","recursiveBase64StrToArrayBuffer","cred","authenticatorResponse","transports","response","getTransports","clientDataJSON","arrayBufferToBase64","attestationObject","value","stringify","debug"],"mappings":";;;;;;;;AAwBAA,kCAAO,CAAC,wBAAyB,aAAa,SAASC,MAAOC,WACnD,CACHC,KAAM,SAASC,YACXA,WAAaC,KAAKC,MAAMF,YACxBG,SAASC,eAAe,4BAA4BC,iBAAiB,SAASC,eAAeC,MACzFA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAGO,IAAvBZ,WAAWa,cACL,IAAID,MAAMZ,WAAWc,KAAO,6BAIlCjB,MAAMkB,gCAAgCf,kBAChCgB,WAAaP,UAAUC,YAAYC,OAAOX,YAC1CiB,sBAAwB,CAC1BC,WAAYF,KAAKG,SAASC,cAAgBJ,KAAKG,SAASC,gBAAkB,KAC1EC,eAAgBL,KAAKG,SAASE,eAC1BxB,MAAMyB,oBAAoBN,KAAKG,SAASE,gBAAkB,KAC9DE,kBAAmBP,KAAKG,SAASI,kBAC7B1B,MAAMyB,oBAAoBN,KAAKG,SAASI,mBAAqB,MAErEpB,SAASC,eAAe,qBAAqBoB,MAAQvB,KAAKwB,UAAUR,uBACtE,MAAOV,GACLT,IAAI4B,MAAM"}
@@ -22,33 +22,34 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['factor_webauthn/utils'], function(utils) {
define(['factor_webauthn/utils', 'core/log'], function(utils, Log) {
return {
init: function(createArgs) {
createArgs = JSON.parse(createArgs);
document.getElementById('factor_webauthn-register').addEventListener('click', async function(e) {
e.preventDefault();
if (!navigator.credentials || !navigator.credentials.create) {
throw new Error('Browser not supported.');
}
createArgs = JSON.parse(createArgs);
if (createArgs.success === false) {
throw new Error(createArgs.msg || 'unknown error occured');
}
utils.recursiveBase64StrToArrayBuffer(createArgs);
const cred = await navigator.credentials.create(createArgs);
const authenticatorResponse = {
transports: cred.response.getTransports ? cred.response.getTransports() : null,
clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,
attestationObject:
cred.response.attestationObject ? utils.arrayBufferToBase64(cred.response.attestationObject) : null
};
document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);
try {
utils.recursiveBase64StrToArrayBuffer(createArgs);
const cred = await navigator.credentials.create(createArgs);
const authenticatorResponse = {
transports: cred.response.getTransports ? cred.response.getTransports() : null,
clientDataJSON: cred.response.clientDataJSON ?
utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,
attestationObject: cred.response.attestationObject ?
utils.arrayBufferToBase64(cred.response.attestationObject) : null,
};
document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);
} catch (e) {
Log.debug('The request timed out or you have canceled the request. Please try again later.');
}
});
}
};
+1
View File
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
$string['achievedweight'] = 'Achieved weight';
$string['alltime'] = 'All time';
$string['areyousure'] = 'Are you sure you want to revoke factor?';
$string['cancellogin'] = 'Cancel login';
$string['combination'] = 'Combination';
$string['connector'] = 'AND';
$string['created'] = 'Created';
@@ -85,12 +85,12 @@
<input type="hidden" id="{{uniqid}}-logout" name="logout" value="true">
<input type="submit" class="btn btn-secondary"
name="cancelmfa" id="cancelmfa_button"
value="Cancel login" data-initial-value="Cancel login">
value="{{#str}} cancellogin, tool_mfa {{/str}}">
</form>
</div>
{{#supportlink}}
<div class="tool-mfa-site-support mt-4">
Need help? {{{supportlink}}}
{{#str}} needhelp, tool_mfa {{/str}} {{{supportlink}}}
</div>
{{/supportlink}}
</div>
@@ -98,7 +98,15 @@ class remote_resource_test extends \advanced_testcase {
]
);
$this->assertGreaterThan(0, $remoteres->get_download_size());
// We need to handle size of -1 (missing "Content-Length" header), or where it is set and greater than zero.
$this->assertThat(
$remoteres->get_download_size(),
$this->logicalOr(
$this->equalTo(-1),
$this->greaterThan(0),
),
);
[$path, $name] = $remoteres->download_to_requestdir();
$this->assertIsString($path);
$this->assertEquals('test.html', $name);
+1 -3
View File
@@ -760,9 +760,7 @@ class auth_plugin_db extends auth_plugin_base {
$rs->close();
} else {
$fields_obj = $rs->FetchObj();
$columns = array_keys((array)$fields_obj);
$columns = array_keys($rs->fetchRow());
echo $OUTPUT->notification(get_string('auth_dbcolumnlist', 'auth_db', implode(', ', $columns)), 'notifysuccess');
$rs->close();
}
+1 -1
View File
@@ -25,7 +25,7 @@ information provided here is intended especially for developers.
* The auth_db and auth_ldap plugins' implementations of update_user_record() have been removed and both now
call the new implementation added in the base class.
* Self registration plugins should use core_privacy\local\sitepolicy\manager instead of directly checking
$CFG->sitepolicy , especially in custom signup forms. See https://docs.moodle.org/dev/Site_policy_handler
$CFG->sitepolicy , especially in custom signup forms.
=== 3.3 ===
@@ -0,0 +1,105 @@
@availability @availability_date @javascript
Feature: As a teacher I can set availability dates restriction to an activity and see a warning when conflicting dates are set
Background:
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion |
| Course 1 | C1 | topics | 1 |
And the following "users" exist:
| username |
| teacher1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | introformat | course | content | contentformat | idnumber |
| page | PageName1 | PageDesc1 | 1 | C1 | Page 1 | 1 | 1 |
Scenario: When I set dates to potential conflicting dates in the same subset, I should see a warning.
Given I am on the PageName1 "page activity editing" page logged in as teacher1
And I expand all fieldsets
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1" "availability_date > Date Restriction" to "4"
And I set the field "Direction" in the "1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "2" "availability_date > Date Restriction" to "6"
And I set the field "Direction" in the "2" "availability_date > Date Restriction" to "until"
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "3" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "3" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "3" "availability_date > Date Restriction" to "6"
When I set the field "Direction" in the "3" "availability_date > Date Restriction" to "from"
Then I should see "Conflicts with other date restrictions"
Scenario: If there are conflicting dates in the same subset, I should not see a warning if condition are separated by "any".
Given I am on the PageName1 "page activity editing" page logged in as teacher1
And I expand all fieldsets
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.1" "availability_date > Date Restriction" to "4"
And I set the field "Direction" in the "1.1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.2" "availability_date > Date Restriction" to "6"
And I set the field "Direction" in the "1.2" "availability_date > Date Restriction" to "until"
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.3" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.3" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.3" "availability_date > Date Restriction" to "6"
And I set the field "Direction" in the "1.3" "availability_date > Date Restriction" to "from"
When I set the field "Required restrictions" in the "1" "core_availability > Set Of Restrictions" to "any"
Then I should not see "Conflicts with other date restrictions"
Scenario: There should a conflicting availability dates are in the same subset separated by "all".
Given I am on the PageName1 "page activity editing" page logged in as teacher1
And I expand all fieldsets
# Root level: Student "must" match the following.
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
# This is the second level: Student "must" match any of the following.
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
# And now the third and final level.
And I click on "Add restriction..." "button" in the "1.1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.1.1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.1.1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.1.1" "availability_date > Date Restriction" to "2"
And I set the field "Direction" in the "1.1.1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "1.1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.1.2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.1.2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.1.2" "availability_date > Date Restriction" to "3"
And I set the field "Direction" in the "1.1.2" "availability_date > Date Restriction" to "until"
# Then add a restriction to the second level.
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
And I click on "Add restriction..." "button" in the "1.2" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.2.1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.2.1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.2.1" "availability_date > Date Restriction" to "4"
And I set the field "Direction" in the "1.2.1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "1.2" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.2.2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.2.2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.2.2" "availability_date > Date Restriction" to "3"
When I set the field "Direction" in the "1.2.2" "availability_date > Date Restriction" to "until"
# Same subset, we can detect conflicts.
Then I should see "Conflicts with other date restrictions"
@@ -0,0 +1,39 @@
<?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/>.
use Behat\Mink\Element\NodeElement;
/**
* Behat availabilty-related steps definitions.
*
* @package availability_date
* @category test
* @copyright 2023 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_availability_date extends behat_base {
/**
* Return the list of partial named selectors.
*
* @return array
*/
public static function get_partial_named_selectors(): array {
return [
new behat_component_named_selector(
'Date Restriction', ["//div[h3[@data-restriction-order=%locator% and contains(text(), 'Date restriction')]]"]
),
];
}
}
@@ -65,6 +65,11 @@ M.availability_date.form.getNode = function(json) {
// Set default time that corresponds to the HTML selectors.
node.setData('time', this.defaultTime);
}
if (json.nodeUID === undefined) {
var miliTime = new Date();
json.nodeUID = miliTime.getTime();
}
node.setData('nodeUID', json.nodeUID);
if (json.d !== undefined) {
node.one('select[name=direction]').set('value', json.d);
}
@@ -114,7 +119,7 @@ M.availability_date.form.getNode = function(json) {
* gets an AJAX response.
*
* @method updateTime
* @param {Y.Node} component Node for plugin controls
* @param {Y.Node} node Node for plugin controls
*/
M.availability_date.form.updateTime = function(node) {
// After a change to the date/time we need to recompute the
@@ -140,39 +145,53 @@ M.availability_date.form.updateTime = function(node) {
M.availability_date.form.fillValue = function(value, node) {
value.d = node.one('select[name=direction]').get('value');
value.t = parseInt(node.getData('time'), 10);
value.nodeUID = node.getData('nodeUID');
};
/**
* List out Date node value in an array node.
* List out Date node value in the same branch.
*
* This will go through all array node and list from earlier date node to current date node.
* This will go through all array node and list nodes that are sibling of the current node.
*
* @method convertTreeDateValue
* @param {array} tree Tree node to convert
* @param {array} arrayDateNode
* @param {array} currentNode current node.
*
* @return {array} arrayDateNode
* @method findAllDateSiblings
* @param {Array} tree Tree items to convert
* @param {Number} nodeUIDToFind node UID to find.
* @return {Array|null} array of surrounding date avaiability values
*/
M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, currentNode) {
var shouldSkip = false;
tree.forEach(function(node) {
if (shouldSkip) {
return;
}
if (node.type === 'date') {
// We go through all tree node, if we meet the current node then return.
if (node.t === parseInt(currentNode.getData('time'), 10)
&& currentNode.one('select[name=direction]').get('value') == node.d) {
shouldSkip = true;
return;
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
var itemValue = null;
var siblingsFinderRecursive = function(itemsTree) {
var dateSiblings = [];
var nodeFound = false;
var index;
var childDates;
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
if (itemsTree.c !== undefined) {
var children = itemsTree.c;
for (index = 0; index < children.length; index++) {
itemValue = children.at(index);
if (itemValue.type === undefined) {
childDates = siblingsFinderRecursive(itemValue);
if (childDates) {
return childDates;
}
}
if (itemValue.type === 'date') {
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
if (nodeUIDToFind === itemValue.nodeUID) {
nodeFound = true;
} else if (currentOp === '&') {
dateSiblings.push(itemValue);
}
}
}
if (nodeFound) {
return dateSiblings;
}
arrayDateNode.push(node);
} else if (node.type === undefined) {
M.availability_date.form.convertTreeDateValue(node.c, arrayDateNode, currentNode);
}
});
return arrayDateNode;
return null;
};
return siblingsFinderRecursive(tree);
};
/**
@@ -181,37 +200,34 @@ M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, cu
* This will check current date node with all date node in tree node.
*
* @method checkConditionDate
* @param {array} currentNode The curent node.
* @param {Y.Node} currentNode The curent node.
*
* @return {boolean} error Return true if the date is conflict.
*/
M.availability_date.form.checkConditionDate = function(currentNode) {
var error = false;
if (M.core_availability.form.rootList.getValue().op === '&') {
var jsValue = M.core_availability.form.rootList.getValue().c;
var arrayDateNode = M.availability_date.form.convertTreeDateValue(jsValue, [], currentNode);
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
arrayDateNode.forEach(function(checkNode) {
var currentNodeUID = currentNode.getData('nodeUID');
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
var dateSiblings = M.availability_date.form.findAllDateSiblings(
M.core_availability.form.rootList.getValue(),
currentNodeUID);
if (dateSiblings) {
dateSiblings.forEach(function(dateSibling) {
// Validate if the date is conflict.
if (checkNode.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= checkNode.t) {
if (dateSibling.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
error = true;
}
} else {
if (currentNodeDirection === '<' && currentNodeTime <= checkNode.t) {
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
error = true;
}
}
return error;
});
return error;
} else {
if (currentNode.one('div > .badge-warning')) {
currentNode.one('div > .badge-warning').remove();
}
return error;
}
return error;
};
M.availability_date.form.fillErrors = function(errors, node) {
@@ -1 +1 @@
YUI.add("moodle-availability_date-form",function(o,e){M.availability_date=M.availability_date||{},M.availability_date.form=o.Object(M.core_availability.plugin),M.availability_date.form.initInner=function(e,a){this.html=e,this.defaultTime=a},M.availability_date.form.getNode=function(e){var t,i,a='<span class="col-form-label pr-3">'+M.util.get_string("direction_before","availability_date")+'</span> <span class="availability-group"><label><span class="accesshide">'+M.util.get_string("direction_label","availability_date")+' </span><select name="direction" class="custom-select"><option value="&gt;=">'+M.util.get_string("direction_from","availability_date")+'</option><option value="&lt;">'+M.util.get_string("direction_until","availability_date")+"</option></select></label></span> "+this.html,n=o.Node.create("<span>"+a+"</span>");return e.t!==undefined?(n.setData("time",e.t),n.all("select:not([name=direction])").each(function(e){e.set("disabled",!0)}),a=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=fromtime&time="+e.t,o.io(a,{on:{success:function(e,a){var t,i,l=o.JSON.parse(a.responseText);for(t in l)(i=n.one("select[name=x\\["+t+"\\]]")).set("value",""+l[t]),i.set("disabled",!1)},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})):n.setData("time",this.defaultTime),e.d!==undefined&&n.one("select[name=direction]").set("value",e.d),M.availability_date.form.addedEvents||(M.availability_date.form.addedEvents=!0,(a=o.one(".availability-field")).delegate("change",function(){M.core_availability.form.update()},".availability_date select[name=direction]"),a.delegate("change",function(){M.availability_date.form.updateTime(this.ancestor("span.availability_date"))},".availability_date select:not([name=direction])")),n.one("a[href=#]")&&(M.form.dateselector.init_single_date_selector(n),t=n.one("select[name=x\\[year\\]]"),i=t.set,t.set=function(e,a){i.call(t,e,a),"selectedIndex"===e&&setTimeout(function(){M.availability_date.form.updateTime(n)},0)}),n},M.availability_date.form.updateTime=function(t){var e=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=totime&year="+t.one("select[name=x\\[year\\]]").get("value")+"&month="+t.one("select[name=x\\[month\\]]").get("value")+"&day="+t.one("select[name=x\\[day\\]]").get("value")+"&hour="+t.one("select[name=x\\[hour\\]]").get("value")+"&minute="+t.one("select[name=x\\[minute\\]]").get("value");o.io(e,{on:{success:function(e,a){t.setData("time",a.responseText),M.core_availability.form.update()},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})},M.availability_date.form.fillValue=function(e,a){e.d=a.one("select[name=direction]").get("value"),e.t=parseInt(a.getData("time"),10)},M.availability_date.form.convertTreeDateValue=function(e,a,t){var i=!1;return e.forEach(function(e){i||("date"===e.type?e.t!==parseInt(t.getData("time"),10)||t.one("select[name=direction]").get("value")!=e.d?a.push(e):i=!0:e.type===undefined&&M.availability_date.form.convertTreeDateValue(e.c,a,t))}),a},M.availability_date.form.checkConditionDate=function(e){var a,t,i,l=!1;return"&"===M.core_availability.form.rootList.getValue().op?(a=M.core_availability.form.rootList.getValue().c,a=M.availability_date.form.convertTreeDateValue(a,[],e),t=e.one("select[name=direction]").get("value"),i=parseInt(e.getData("time"),10),a.forEach(function(e){return"<"===e.d?">="===t&&i>=e.t&&(l=!0):"<"===t&&i<=e.t&&(l=!0),l})):e.one("div > .badge-warning")&&e.one("div > .badge-warning").remove(),l},M.availability_date.form.fillErrors=function(e,a){M.availability_date.form.checkConditionDate(a)&&e.push("availability_date:error_dateconflict")}},"@VERSION@",{requires:["base","node","event","io","moodle-core_availability-form"]});
YUI.add("moodle-availability_date-form",function(o,e){M.availability_date=M.availability_date||{},M.availability_date.form=o.Object(M.core_availability.plugin),M.availability_date.form.initInner=function(e,a){this.html=e,this.defaultTime=a},M.availability_date.form.getNode=function(e){var t,i,a='<span class="col-form-label pr-3">'+M.util.get_string("direction_before","availability_date")+'</span> <span class="availability-group"><label><span class="accesshide">'+M.util.get_string("direction_label","availability_date")+' </span><select name="direction" class="custom-select"><option value="&gt;=">'+M.util.get_string("direction_from","availability_date")+'</option><option value="&lt;">'+M.util.get_string("direction_until","availability_date")+"</option></select></label></span> "+this.html,l=o.Node.create("<span>"+a+"</span>");return e.t!==undefined?(l.setData("time",e.t),l.all("select:not([name=direction])").each(function(e){e.set("disabled",!0)}),a=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=fromtime&time="+e.t,o.io(a,{on:{success:function(e,a){var t,i,n=o.JSON.parse(a.responseText);for(t in n)(i=l.one("select[name=x\\["+t+"\\]]")).set("value",""+n[t]),i.set("disabled",!1)},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})):l.setData("time",this.defaultTime),e.nodeUID===undefined&&(a=new Date,e.nodeUID=a.getTime()),l.setData("nodeUID",e.nodeUID),e.d!==undefined&&l.one("select[name=direction]").set("value",e.d),M.availability_date.form.addedEvents||(M.availability_date.form.addedEvents=!0,(a=o.one(".availability-field")).delegate("change",function(){M.core_availability.form.update()},".availability_date select[name=direction]"),a.delegate("change",function(){M.availability_date.form.updateTime(this.ancestor("span.availability_date"))},".availability_date select:not([name=direction])")),l.one("a[href=#]")&&(M.form.dateselector.init_single_date_selector(l),t=l.one("select[name=x\\[year\\]]"),i=t.set,t.set=function(e,a){i.call(t,e,a),"selectedIndex"===e&&setTimeout(function(){M.availability_date.form.updateTime(l)},0)}),l},M.availability_date.form.updateTime=function(t){var e=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=totime&year="+t.one("select[name=x\\[year\\]]").get("value")+"&month="+t.one("select[name=x\\[month\\]]").get("value")+"&day="+t.one("select[name=x\\[day\\]]").get("value")+"&hour="+t.one("select[name=x\\[hour\\]]").get("value")+"&minute="+t.one("select[name=x\\[minute\\]]").get("value");o.io(e,{on:{success:function(e,a){t.setData("time",a.responseText),M.core_availability.form.update()},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})},M.availability_date.form.fillValue=function(e,a){e.d=a.one("select[name=direction]").get("value"),e.t=parseInt(a.getData("time"),10),e.nodeUID=a.getData("nodeUID")},M.availability_date.form.findAllDateSiblings=function(e,d){var r,c=function(e){var a,t,i,n=[],l=!1,o=e.op!==undefined?e.op:null;if(e.c!==undefined){for(i=e.c,a=0;a<i.length;a++){if((r=i.at(a)).type===undefined&&(t=c(r)))return t;"date"===r.type&&(d===r.nodeUID?l=!0:"&"===o&&n.push(r))}if(l)return n}return null};return c(e)},M.availability_date.form.checkConditionDate=function(e){var a=!1,t=e.getData("nodeUID"),i=e.one("select[name=direction]").get("value"),n=parseInt(e.getData("time"),10),e=M.availability_date.form.findAllDateSiblings(M.core_availability.form.rootList.getValue(),t);return e&&e.forEach(function(e){return"<"===e.d?">="===i&&n>=e.t&&(a=!0):"<"===i&&n<=e.t&&(a=!0),a}),a},M.availability_date.form.fillErrors=function(e,a){M.availability_date.form.checkConditionDate(a)&&e.push("availability_date:error_dateconflict")}},"@VERSION@",{requires:["base","node","event","io","moodle-core_availability-form"]});
@@ -65,6 +65,11 @@ M.availability_date.form.getNode = function(json) {
// Set default time that corresponds to the HTML selectors.
node.setData('time', this.defaultTime);
}
if (json.nodeUID === undefined) {
var miliTime = new Date();
json.nodeUID = miliTime.getTime();
}
node.setData('nodeUID', json.nodeUID);
if (json.d !== undefined) {
node.one('select[name=direction]').set('value', json.d);
}
@@ -114,7 +119,7 @@ M.availability_date.form.getNode = function(json) {
* gets an AJAX response.
*
* @method updateTime
* @param {Y.Node} component Node for plugin controls
* @param {Y.Node} node Node for plugin controls
*/
M.availability_date.form.updateTime = function(node) {
// After a change to the date/time we need to recompute the
@@ -140,39 +145,53 @@ M.availability_date.form.updateTime = function(node) {
M.availability_date.form.fillValue = function(value, node) {
value.d = node.one('select[name=direction]').get('value');
value.t = parseInt(node.getData('time'), 10);
value.nodeUID = node.getData('nodeUID');
};
/**
* List out Date node value in an array node.
* List out Date node value in the same branch.
*
* This will go through all array node and list from earlier date node to current date node.
* This will go through all array node and list nodes that are sibling of the current node.
*
* @method convertTreeDateValue
* @param {array} tree Tree node to convert
* @param {array} arrayDateNode
* @param {array} currentNode current node.
*
* @return {array} arrayDateNode
* @method findAllDateSiblings
* @param {Array} tree Tree items to convert
* @param {Number} nodeUIDToFind node UID to find.
* @return {Array|null} array of surrounding date avaiability values
*/
M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, currentNode) {
var shouldSkip = false;
tree.forEach(function(node) {
if (shouldSkip) {
return;
}
if (node.type === 'date') {
// We go through all tree node, if we meet the current node then return.
if (node.t === parseInt(currentNode.getData('time'), 10)
&& currentNode.one('select[name=direction]').get('value') == node.d) {
shouldSkip = true;
return;
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
var itemValue = null;
var siblingsFinderRecursive = function(itemsTree) {
var dateSiblings = [];
var nodeFound = false;
var index;
var childDates;
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
if (itemsTree.c !== undefined) {
var children = itemsTree.c;
for (index = 0; index < children.length; index++) {
itemValue = children.at(index);
if (itemValue.type === undefined) {
childDates = siblingsFinderRecursive(itemValue);
if (childDates) {
return childDates;
}
}
if (itemValue.type === 'date') {
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
if (nodeUIDToFind === itemValue.nodeUID) {
nodeFound = true;
} else if (currentOp === '&') {
dateSiblings.push(itemValue);
}
}
}
if (nodeFound) {
return dateSiblings;
}
arrayDateNode.push(node);
} else if (node.type === undefined) {
M.availability_date.form.convertTreeDateValue(node.c, arrayDateNode, currentNode);
}
});
return arrayDateNode;
return null;
};
return siblingsFinderRecursive(tree);
};
/**
@@ -181,37 +200,34 @@ M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, cu
* This will check current date node with all date node in tree node.
*
* @method checkConditionDate
* @param {array} currentNode The curent node.
* @param {Y.Node} currentNode The curent node.
*
* @return {boolean} error Return true if the date is conflict.
*/
M.availability_date.form.checkConditionDate = function(currentNode) {
var error = false;
if (M.core_availability.form.rootList.getValue().op === '&') {
var jsValue = M.core_availability.form.rootList.getValue().c;
var arrayDateNode = M.availability_date.form.convertTreeDateValue(jsValue, [], currentNode);
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
arrayDateNode.forEach(function(checkNode) {
var currentNodeUID = currentNode.getData('nodeUID');
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
var dateSiblings = M.availability_date.form.findAllDateSiblings(
M.core_availability.form.rootList.getValue(),
currentNodeUID);
if (dateSiblings) {
dateSiblings.forEach(function(dateSibling) {
// Validate if the date is conflict.
if (checkNode.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= checkNode.t) {
if (dateSibling.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
error = true;
}
} else {
if (currentNodeDirection === '<' && currentNodeTime <= checkNode.t) {
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
error = true;
}
}
return error;
});
return error;
} else {
if (currentNode.one('div > .badge-warning')) {
currentNode.one('div > .badge-warning').remove();
}
return error;
}
return error;
};
M.availability_date.form.fillErrors = function(errors, node) {
+58 -42
View File
@@ -63,6 +63,11 @@ M.availability_date.form.getNode = function(json) {
// Set default time that corresponds to the HTML selectors.
node.setData('time', this.defaultTime);
}
if (json.nodeUID === undefined) {
var miliTime = new Date();
json.nodeUID = miliTime.getTime();
}
node.setData('nodeUID', json.nodeUID);
if (json.d !== undefined) {
node.one('select[name=direction]').set('value', json.d);
}
@@ -112,7 +117,7 @@ M.availability_date.form.getNode = function(json) {
* gets an AJAX response.
*
* @method updateTime
* @param {Y.Node} component Node for plugin controls
* @param {Y.Node} node Node for plugin controls
*/
M.availability_date.form.updateTime = function(node) {
// After a change to the date/time we need to recompute the
@@ -138,39 +143,53 @@ M.availability_date.form.updateTime = function(node) {
M.availability_date.form.fillValue = function(value, node) {
value.d = node.one('select[name=direction]').get('value');
value.t = parseInt(node.getData('time'), 10);
value.nodeUID = node.getData('nodeUID');
};
/**
* List out Date node value in an array node.
* List out Date node value in the same branch.
*
* This will go through all array node and list from earlier date node to current date node.
* This will go through all array node and list nodes that are sibling of the current node.
*
* @method convertTreeDateValue
* @param {array} tree Tree node to convert
* @param {array} arrayDateNode
* @param {array} currentNode current node.
*
* @return {array} arrayDateNode
* @method findAllDateSiblings
* @param {Array} tree Tree items to convert
* @param {Number} nodeUIDToFind node UID to find.
* @return {Array|null} array of surrounding date avaiability values
*/
M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, currentNode) {
var shouldSkip = false;
tree.forEach(function(node) {
if (shouldSkip) {
return;
}
if (node.type === 'date') {
// We go through all tree node, if we meet the current node then return.
if (node.t === parseInt(currentNode.getData('time'), 10)
&& currentNode.one('select[name=direction]').get('value') == node.d) {
shouldSkip = true;
return;
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
var itemValue = null;
var siblingsFinderRecursive = function(itemsTree) {
var dateSiblings = [];
var nodeFound = false;
var index;
var childDates;
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
if (itemsTree.c !== undefined) {
var children = itemsTree.c;
for (index = 0; index < children.length; index++) {
itemValue = children.at(index);
if (itemValue.type === undefined) {
childDates = siblingsFinderRecursive(itemValue);
if (childDates) {
return childDates;
}
}
if (itemValue.type === 'date') {
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
if (nodeUIDToFind === itemValue.nodeUID) {
nodeFound = true;
} else if (currentOp === '&') {
dateSiblings.push(itemValue);
}
}
}
if (nodeFound) {
return dateSiblings;
}
arrayDateNode.push(node);
} else if (node.type === undefined) {
M.availability_date.form.convertTreeDateValue(node.c, arrayDateNode, currentNode);
}
});
return arrayDateNode;
return null;
};
return siblingsFinderRecursive(tree);
};
/**
@@ -179,37 +198,34 @@ M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, cu
* This will check current date node with all date node in tree node.
*
* @method checkConditionDate
* @param {array} currentNode The curent node.
* @param {Y.Node} currentNode The curent node.
*
* @return {boolean} error Return true if the date is conflict.
*/
M.availability_date.form.checkConditionDate = function(currentNode) {
var error = false;
if (M.core_availability.form.rootList.getValue().op === '&') {
var jsValue = M.core_availability.form.rootList.getValue().c;
var arrayDateNode = M.availability_date.form.convertTreeDateValue(jsValue, [], currentNode);
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
arrayDateNode.forEach(function(checkNode) {
var currentNodeUID = currentNode.getData('nodeUID');
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
var dateSiblings = M.availability_date.form.findAllDateSiblings(
M.core_availability.form.rootList.getValue(),
currentNodeUID);
if (dateSiblings) {
dateSiblings.forEach(function(dateSibling) {
// Validate if the date is conflict.
if (checkNode.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= checkNode.t) {
if (dateSibling.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
error = true;
}
} else {
if (currentNodeDirection === '<' && currentNodeTime <= checkNode.t) {
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
error = true;
}
}
return error;
});
return error;
} else {
if (currentNode.one('div > .badge-warning')) {
currentNode.one('div > .badge-warning').remove();
}
return error;
}
return error;
};
M.availability_date.form.fillErrors = function(errors, node) {
+27 -11
View File
@@ -24,7 +24,7 @@ require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
* @copyright 2023 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_availability extends \behat_base {
class behat_availability extends behat_base {
/**
* Return the list of partial named selectors.
@@ -33,18 +33,34 @@ class behat_availability extends \behat_base {
*/
public static function get_partial_named_selectors(): array {
return [
new \behat_component_named_selector(
'Activity availability', [<<<XPATH
.//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][descendant::*[contains(normalize-space(.), %locator%)]]
//div[@data-region='availabilityinfo']
XPATH
new behat_component_named_selector(
'Activity availability', [
".//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]"
. "[descendant::*[contains(normalize-space(.), %locator%)]]//div[@data-region='availabilityinfo']",
]
),
new \behat_component_named_selector(
'Section availability', [<<<XPATH
.//li[@id = %locator%]//div[@data-region='availabilityinfo']
XPATH
]
new behat_component_named_selector(
'Section availability', [".//li[@id = %locator%]//div[@data-region='availabilityinfo']"],
),
new behat_component_named_selector(
'Set Of Restrictions', ["//div[h3[@data-restriction-order=%locator% and contains(text(), 'Set of')]]"],
),
];
}
/**
* Return the list of exact named selectors
*
* @return array
*/
public static function get_exact_named_selectors(): array {
return [
new behat_component_named_selector(
'Availability Button Area',
[
"//h3[@data-restriction-order=%locator%]/following-sibling::div[contains(@class,'availability-inner')]/"
. "div[contains(@class,'availability-button')]",
],
),
];
}
@@ -584,7 +584,7 @@ M.core_availability.List.prototype.renumber = function(parentNumber) {
}
var heading = M.util.get_string('setheading', 'availability', headingParams);
this.node.one('> h3').set('innerHTML', heading);
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = parentNumber ? parentNumber : 'root';
// Do children.
for (var i = 0; i < this.children.length; i++) {
var child = this.children[i];
@@ -1078,6 +1078,7 @@ M.core_availability.Item.prototype.renumber = function(number) {
headingParams.number = number + ':';
var heading = M.util.get_string('itemheading', 'availability', headingParams);
this.node.one('> h3').set('innerHTML', heading);
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = number ? number : 'root';
};
/**
File diff suppressed because one or more lines are too long
@@ -584,7 +584,7 @@ M.core_availability.List.prototype.renumber = function(parentNumber) {
}
var heading = M.util.get_string('setheading', 'availability', headingParams);
this.node.one('> h3').set('innerHTML', heading);
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = parentNumber ? parentNumber : 'root';
// Do children.
for (var i = 0; i < this.children.length; i++) {
var child = this.children[i];
@@ -1078,6 +1078,7 @@ M.core_availability.Item.prototype.renumber = function(number) {
headingParams.number = number + ':';
var heading = M.util.get_string('itemheading', 'availability', headingParams);
this.node.one('> h3').set('innerHTML', heading);
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = number ? number : 'root';
};
/**
+2 -1
View File
@@ -582,7 +582,7 @@ M.core_availability.List.prototype.renumber = function(parentNumber) {
}
var heading = M.util.get_string('setheading', 'availability', headingParams);
this.node.one('> h3').set('innerHTML', heading);
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = parentNumber ? parentNumber : 'root';
// Do children.
for (var i = 0; i < this.children.length; i++) {
var child = this.children[i];
@@ -1076,6 +1076,7 @@ M.core_availability.Item.prototype.renumber = function(number) {
headingParams.number = number + ':';
var heading = M.util.get_string('itemheading', 'availability', headingParams);
this.node.one('> h3').set('innerHTML', heading);
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = number ? number : 'root';
};
/**
+3 -1
View File
@@ -867,7 +867,9 @@ class moodle1_course_outline_handler extends moodle1_xml_handler {
$plugin->version = null;
$module = $plugin;
include($versionfile);
$data['version'] = $plugin->version;
// Have to hardcode - since quiz uses some hardcoded version numbers when restoring.
// This is the lowest number used minus one.
$data['version'] = 2011010099;
} else {
$data['version'] = null;
}
@@ -35,7 +35,6 @@ Feature: Edit activities in main menu block
| activity | course | section | name |
| forum | Acceptance test site | 0 | Visible forum |
| forum | Acceptance test site | 0 | My forum name |
And I change window size to "large"
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
+1 -1
View File
@@ -5,6 +5,6 @@
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/crud",["jquery","core/str","core/notification","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending","core/modal_save_cancel"],(function($,Str,Notification,ModalEvents,ModalEventForm,CalendarRepository,CalendarEvents,CalendarModalDelete,CalendarSelectors,Pending,ModalSaveCancel){return{registerRemove:function(root){root.on("click",CalendarSelectors.actions.remove,(function(e){var eventSource=$(this).closest(CalendarSelectors.eventItem);!function(eventId,eventTitle,eventCount){var deletePromise,pendingPromise=new Pending("core_calendar/crud:confirmDeletion"),deleteStrings=[{key:"deleteevent",component:"calendar"}],isRepeatedEvent=(eventCount=parseInt(eventCount,10))>1;isRepeatedEvent?(deleteStrings.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:eventTitle,count:eventCount}}),deletePromise=CalendarModalDelete.create()):(deleteStrings.push({key:"confirmeventdelete",component:"calendar",param:eventTitle}),deletePromise=ModalSaveCancel.create());var stringsPromise=Str.get_strings(deleteStrings);$.when(stringsPromise,deletePromise).then((function(strings,deleteModal){return deleteModal.setRemoveOnClose(!0),deleteModal.setTitle(strings[0]),deleteModal.setBody(strings[1]),isRepeatedEvent||deleteModal.setSaveButtonText(strings[0]),deleteModal.show(),deleteModal.getRoot().on(ModalEvents.save,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedevent");CalendarRepository.deleteEvent(eventId,!1).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!1])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal.getRoot().on(CalendarEvents.deleteAll,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedallevent");CalendarRepository.deleteEvent(eventId,!0).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!0])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)}(eventSource.data("eventId"),eventSource.data("eventTitle"),eventSource.data("eventCount")),e.preventDefault()}))},registerEditListeners:function(root,eventFormModalPromise){var pendingPromise=new Pending("core_calendar/crud:registerEditListeners");return eventFormModalPromise.then((function(modal){return $("body").on(CalendarEvents.editEvent,(function(e,eventId){var target=root.find("[data-event-id=".concat(eventId,"]")),calendarWrapper=root.find(CalendarSelectors.wrapper);modal.setEventId(eventId),modal.setContextId(calendarWrapper.data("contextId")),modal.setReturnElement(target),modal.show(),e.stopImmediatePropagation()})),modal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)},registerEventFormModal:function(root){var eventFormPromise=ModalEventForm.create();return root.on("click",CalendarSelectors.actions.create,(function(e){eventFormPromise.then((function(modal){var wrapper=root.find(CalendarSelectors.wrapper),categoryId=wrapper.data("categoryid");void 0!==categoryId&&modal.setCategoryId(categoryId);var today=root.find(CalendarSelectors.today),firstDay=root.find(CalendarSelectors.day);!today.length&&firstDay.length&&modal.setStartTime(firstDay.data("newEventTimestamp")),modal.setContextId(wrapper.data("contextId")),modal.setCourseId(wrapper.data("courseid")),modal.show()})).fail(Notification.exception),e.preventDefault()})),root.on("click",CalendarSelectors.actions.edit,(function(e){e.preventDefault();var target=$(e.currentTarget),calendarWrapper=target.closest(CalendarSelectors.wrapper),eventWrapper=target.closest(CalendarSelectors.eventItem);eventFormPromise.then((function(modal){modal.setEventId(eventWrapper.data("eventId")),modal.setContextId(calendarWrapper.data("contextId")),modal.setCourseId(eventWrapper.data("courseId")),modal.show(),e.stopImmediatePropagation()})).fail(Notification.exception)})),eventFormPromise}}}));
define("core_calendar/crud",["jquery","core/str","core/notification","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending","core/modal_save_cancel"],(function($,Str,Notification,ModalEvents,ModalEventForm,CalendarRepository,CalendarEvents,CalendarModalDelete,CalendarSelectors,Pending,ModalSaveCancel){return{registerRemove:function(root){root.on("click",CalendarSelectors.actions.remove,(function(e){var eventSource=$(this).closest(CalendarSelectors.eventItem);!function(eventId,eventTitle,eventCount){var deletePromise,pendingPromise=new Pending("core_calendar/crud:confirmDeletion"),deleteStrings=[{key:"deleteevent",component:"calendar"}],isRepeatedEvent=(eventCount=parseInt(eventCount,10))>1;isRepeatedEvent?(deleteStrings.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:eventTitle,count:eventCount}}),deletePromise=CalendarModalDelete.create()):(deleteStrings.push({key:"confirmeventdelete",component:"calendar",param:eventTitle}),deletePromise=ModalSaveCancel.create());var stringsPromise=Str.get_strings(deleteStrings);$.when(stringsPromise,deletePromise).then((function(strings,deleteModal){return deleteModal.setRemoveOnClose(!0),deleteModal.setTitle(strings[0]),deleteModal.setBody(strings[1]),isRepeatedEvent||deleteModal.setSaveButtonText(strings[0]),deleteModal.show(),deleteModal.getRoot().on(ModalEvents.save,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedevent");CalendarRepository.deleteEvent(eventId,!1).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!1])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal.getRoot().on(CalendarEvents.deleteAll,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedallevent");CalendarRepository.deleteEvent(eventId,!0).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!0])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)}(eventSource.data("eventId"),eventSource.data("eventTitle"),eventSource.data("eventCount")),e.preventDefault()}))},registerEditListeners:function(root,eventFormModalPromise){var pendingPromise=new Pending("core_calendar/crud:registerEditListeners");return eventFormModalPromise.then((function(modal){return $("body").on(CalendarEvents.editEvent,(function(e,eventId){var target=root.find("[data-event-id=".concat(eventId,"]")),calendarWrapper=root.find(CalendarSelectors.wrapper);modal.setEventId(eventId),modal.setContextId(calendarWrapper.data("contextId")),modal.setReturnElement(target),modal.show(),e.stopImmediatePropagation()})),modal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)},registerEventFormModal:function(root){var eventFormPromise=ModalEventForm.create();return root.on("click",CalendarSelectors.actions.create,(function(e){eventFormPromise.then((function(modal){var wrapper=root.find(CalendarSelectors.wrapper),categoryId=wrapper.data("categoryid");void 0!==categoryId&&modal.setCategoryId(categoryId);var today=root.find(CalendarSelectors.today),firstDay=root.find(CalendarSelectors.day);!today.length&&firstDay.length&&modal.setStartTime(firstDay.data("newEventTimestamp")),modal.setContextId(wrapper.data("contextId")),modal.setCourseId(wrapper.data("courseid")),modal.show()})).catch(Notification.exception),e.preventDefault()})),root.on("click",CalendarSelectors.actions.edit,(function(e){e.preventDefault();var target=$(e.currentTarget),calendarWrapper=target.closest(CalendarSelectors.wrapper),eventWrapper=target.closest(CalendarSelectors.eventItem);eventFormPromise.then((function(modal){modal.setEventId(eventWrapper.data("eventId")),modal.setContextId(calendarWrapper.data("contextId")),modal.setCourseId(eventWrapper.data("courseId")),modal.show(),e.stopImmediatePropagation()})).catch(Notification.exception)})),eventFormPromise}}}));
//# sourceMappingURL=crud.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -170,7 +170,7 @@ function(
modal.show();
return;
})
.fail(Notification.exception);
.catch(Notification.exception);
e.preventDefault();
});
@@ -192,7 +192,7 @@ function(
e.stopImmediatePropagation();
return;
}).fail(Notification.exception);
}).catch(Notification.exception);
});
+1 -1
View File
@@ -93,7 +93,7 @@ export const foldDayEvents = () => {
moreEventsLink.attr('data-event-folded', 'false');
link.text(str);
return str;
}).fail();
}).catch(Notification.exception);
} else {
moreEventsLink.hide();
}
+2 -2
View File
@@ -165,7 +165,7 @@ abstract class type_base {
* @param int $time the timestamp in UTC, as obtained from the database
* @param string $format strftime format
* @param int|float|string $timezone the timezone to use
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* @param bool $fixday if true then the leading zero from %d is removed,
* if false then the leading zero is maintained
* @param bool $fixhour if true then the leading zero from %I is removed,
@@ -180,7 +180,7 @@ abstract class type_base {
*
* @param int $time timestamp in GMT
* @param float|int|string $timezone the timezone to use to calculate the time
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* @return array an array that represents the date in user time
*/
public abstract function timestamp_to_date_array($time, $timezone = 99);
+6
View File
@@ -1936,6 +1936,7 @@ function calendar_get_link_href($linkbase, $d, $m, $y, $time = 0) {
/**
* Build and return a previous month HTML link, with an arrow.
*
* @deprecated since Moodle 4.3
* @param string $text The text label.
* @param string|moodle_url $linkbase The URL stub.
* @param int $d The number of the date.
@@ -1947,6 +1948,8 @@ function calendar_get_link_href($linkbase, $d, $m, $y, $time = 0) {
* @return string HTML string.
*/
function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) {
debugging(__FUNCTION__ . ' has been deprecated and should not be used anymore.', DEBUG_DEVELOPER);
$href = calendar_get_link_href(new \moodle_url($linkbase), $d, $m, $y, $time);
if (empty($href)) {
@@ -1964,6 +1967,7 @@ function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide =
/**
* Build and return a next month HTML link, with an arrow.
*
* @deprecated since Moodle 4.3
* @param string $text The text label.
* @param string|moodle_url $linkbase The URL stub.
* @param int $d the number of the Day
@@ -1975,6 +1979,8 @@ function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide =
* @return string HTML string.
*/
function calendar_get_link_next($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) {
debugging(__FUNCTION__ . ' has been deprecated and should not be used anymore.', DEBUG_DEVELOPER);
$href = calendar_get_link_href(new \moodle_url($linkbase), $d, $m, $y, $time);
if (empty($href)) {
+11 -1
View File
@@ -34,7 +34,17 @@
}
}}
{{#modulename}}
{{#pix}} monologo, {{modulename}} {{/pix}}
{{#icon}}
{{#iconurl}}
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon {{iconclass}}">
{{/iconurl}}
{{^iconurl}}
{{#pix}} monologo, {{modulename}} {{/pix}}
{{/iconurl}}
{{/icon}}
{{^icon}}
{{#pix}} monologo, {{modulename}} {{/pix}}
{{/icon}}
{{/modulename}}
{{^modulename}}
{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}
+10 -1
View File
@@ -75,7 +75,16 @@
{{/isactionevent}}
{{/canedit}}
</div>
{{#icon}}<div class="d-inline-block mt-1 align-top">{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}</div>{{/icon}}
{{#icon}}
<div class="d-inline-block mt-1 align-top">
{{#iconurl}}
<img alt="{{alttext}}" title="{{alttext}}" src="{{{iconurl}}}" class="icon {{iconclass}}">
{{/iconurl}}
{{^iconurl}}
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}
{{/iconurl}}
</div>
{{/icon}}
<div class="d-inline-block">
<h3 class="name d-inline-block">{{{name}}}</h3>
</div>
+2 -2
View File
@@ -222,7 +222,7 @@ class structure extends type_base {
* @param int $time the timestamp in UTC, as obtained from the database
* @param string $format strftime format
* @param int|float|string $timezone the timezone to use
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* @param bool $fixday if true then the leading zero from %d is removed,
* if false then the leading zero is maintained
* @param bool $fixhour if true then the leading zero from %I is removed,
@@ -239,7 +239,7 @@ class structure extends type_base {
*
* @param int $time timestamp in GMT
* @param float|int|string $timezone the timezone to use to calculate the time
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* @return array an array that represents the date in user time
*/
public function timestamp_to_date_array($time, $timezone = 99) {
@@ -289,7 +289,7 @@ class structure extends type_base {
* @param int $time the timestamp in UTC, as obtained from the database
* @param string $format strftime format
* @param int|float|string $timezone the timezone to use
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* @param bool $fixday if true then the leading zero from %d is removed,
* if false then the leading zero is maintained
* @param bool $fixhour if true then the leading zero from %I is removed,
@@ -348,7 +348,7 @@ class structure extends type_base {
*
* @param int $time Timestamp in GMT
* @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
* dst offset is applied {@link http://docs.moodle.org/dev/Time_API#Timezone}
* dst offset is applied {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* @return array an array that represents the date in user time
*/
public function timestamp_to_date_array($time, $timezone = 99) {
+5 -2
View File
@@ -4,7 +4,10 @@ information provided here is intended especially for developers.
=== 4.3 ===
* The `navigation` property has been removed from `\core_calendar\external\day_exporter` as it is not being used by any of the
calendar templates.
* calendar_top_controls() has been deprecated and should no longer be used.
* The following functions have been deprecated and should no longer be used:
- calendar_top_controls()
- calendar_get_link_previous()
- calendar_get_link_next()
=== 4.1 ===
* New method `calendar_format_event_location` which will format the location property of an event, converting any
@@ -35,7 +38,7 @@ information provided here is intended especially for developers.
=== 3.9 ===
* Plugins can now create their own calendar events, both standard and action ones. To do it they need to specify
$event->component when creating an event. Component events can not be edited or deleted manually.
See https://docs.moodle.org/dev/Calendar_API#Component_events
See https://moodledev.io/docs/apis/core/calendar#component-events
* The following functions have been deprecated because they were no longer used:
- calendar_add_event_metadata()
- core_calendar_renderer::event()
+12 -3
View File
@@ -207,11 +207,11 @@ class api {
$mform->addElement(
'select',
'selectedcommunication',
get_string('seleccommunicationprovider', 'communication'),
get_string('selectcommunicationprovider', 'communication'),
$communicationproviders,
['data-communicationchooser-field' => 'selector'],
);
$mform->addHelpButton('selectedcommunication', 'seleccommunicationprovider', 'communication');
$mform->addHelpButton('selectedcommunication', 'selectcommunicationprovider', 'communication');
$mform->setDefault('selectedcommunication', $defaulprovider);
$mform->registerNoSubmitButton('updatecommunicationprovider');
@@ -245,9 +245,18 @@ class api {
),
'addcommunicationoptionshere'
);
$mform->addHelpButton('communicationroomname', 'communicationroomname', 'communication');
$mform->setType('communicationroomname', PARAM_TEXT);
$mform->insertElementBefore(
$mform->createElement(
'static',
'communicationroomnameinfo',
'',
get_string('communicationroomnameinfo', 'communication'),
),
'addcommunicationoptionshere',
);
processor::set_provider_specific_form_definition($provider, $mform);
}
}
@@ -34,4 +34,12 @@ interface communication_provider {
* @param processor $communication The communication object
*/
public static function load_for_instance(processor $communication): self;
/**
* Check if the provider is configured or not.
*
* This method is intended to check if the plugin have got any settings and if all the settings are set properly.
* This checking helps to reduce errors in future when a communication instance is added for the provider and not configured.
*/
public static function is_configured(): bool;
}
+9 -5
View File
@@ -347,7 +347,7 @@ class processor {
public static function load_by_id(int $id): ?self {
global $DB;
$record = $DB->get_record('communication', ['id' => $id]);
if ($record && self::is_provider_enabled($record->provider)) {
if ($record && self::is_provider_available($record->provider)) {
return new self($record);
}
@@ -376,7 +376,7 @@ class processor {
'instancetype' => $instancetype,
]);
if ($record && self::is_provider_enabled($record->provider)) {
if ($record && self::is_provider_available($record->provider)) {
return new self($record);
}
@@ -671,12 +671,16 @@ class processor {
}
/**
* Is communication provider enabled/disabled.
* Is the communication provider enabled and configured, or disabled.
*
* @param string $provider provider component name
* @return bool
*/
public static function is_provider_enabled(string $provider): bool {
return \core\plugininfo\communication::is_plugin_enabled($provider);
public static function is_provider_available(string $provider): bool {
if (\core\plugininfo\communication::is_plugin_enabled($provider)) {
$providerclass = "{$provider}\\communication_feature";
return $providerclass::is_configured();
}
return false;
}
}
@@ -170,4 +170,8 @@ class communication_feature implements
'addcommunicationoptionshere'
), 'addcommunicationoptionshere');
}
public static function is_configured(): bool {
return true;
}
}
@@ -23,9 +23,9 @@ Feature: Communication custom link
Given I am on the "Course 1" "Course" page logged in as "teacher1"
And "Chat to course participants" "button" should not be visible
When I navigate to "Communication" in current page administration
And the "Communication service" select box should contain "Custom link"
And the "Provider" select box should contain "Custom link"
And I should not see "Custom link URL"
And I select "Custom link" from the "Communication service" singleselect
And I select "Custom link" from the "Provider" singleselect
And I should see "Custom link URL"
And I set the following fields to these values:
| communicationroomname | Test URL |
@@ -57,7 +57,7 @@ Feature: Communication custom link
Given I am on the "Course 1" "Course" page logged in as "teacher1"
And "Chat to course participants" "button" should not be visible
When I navigate to "Communication" in current page administration
And I select "Custom link" from the "Communication service" singleselect
And I select "Custom link" from the "Provider" singleselect
And I set the following fields to these values:
| communicationroomname | Test URL |
| customlinkurl | #wwwroot#/communication/provider/customlink/tests/behat/fixtures/custom_link_test_page.php |
@@ -65,14 +65,14 @@ Feature: Communication custom link
And "Chat to course participants" "button" should be visible
And I run all adhoc tasks
And I navigate to "Communication" in current page administration
And I select "None" from the "Communication service" singleselect
And I select "None" from the "Provider" singleselect
And I press "Save changes"
And "Chat to course participants" "button" should not be visible
And I run all adhoc tasks
And I am on the "Course 1" course page
And "Chat to course participants" "button" should not be visible
And I navigate to "Communication" in current page administration
And I select "Custom link" from the "Communication service" singleselect
And I select "Custom link" from the "Provider" singleselect
And I set the following fields to these values:
| communicationroomname | Test URL |
| customlinkurl | #wwwroot#/communication/provider/customlink/tests/behat/fixtures/custom_link_test_page.php |
@@ -113,4 +113,14 @@ class communication_feature_test extends \advanced_testcase {
return $communicationprocessor;
}
/**
* Test if the selected provider is configured.
*
* @covers ::is_configured
*/
public function test_is_configured(): void {
$communicationprocessor = $this->get_test_communication_processor();
$this->assertTrue($communicationprocessor->get_form_provider()->is_configured());
}
}
@@ -92,7 +92,7 @@ class communication_feature implements
$this->homeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
$this->webclienturl = get_config('communication_matrix', 'matrixelementurl');
if ($this->homeserverurl) {
if ($processor::is_provider_available('communication_matrix')) {
// Generate the API instance.
$this->matrixapi = matrix_client::instance(
serverurl: $this->homeserverurl,
@@ -751,4 +751,25 @@ class communication_feature implements
return $powerlevel;
}
/*
* Check if matrix settings are configured
*
* @return boolean
*/
public static function is_configured(): bool {
// Matrix communication settings.
$matrixhomeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
$matrixaccesstoken = get_config('communication_matrix', 'matrixaccesstoken');
$matrixelementurl = get_config('communication_matrix', 'matrixelementurl');
if (
!empty($matrixhomeserverurl) &&
!empty($matrixaccesstoken) &&
(PHPUNIT_TEST || defined('BEHAT_SITE_RUNNING') || !empty($matrixelementurl))
) {
return true;
}
return false;
}
}
@@ -94,7 +94,7 @@ class matrix_user_manager {
public static function get_formatted_matrix_home_server(): string {
$homeserver = get_config('communication_matrix', 'matrixhomeserverurl');
if ($homeserver === false) {
throw new \moodle_exception('Unknown matrix home server url');
throw new \moodle_exception('Unknown matrix homeserver url');
}
$homeserver = parse_url($homeserver)['host'];
@@ -150,8 +150,6 @@ class matrix_user_manager {
'shortname' => 'matrixuserid',
'name' => get_string('matrixuserid', 'communication_matrix'),
'datatype' => 'text',
'description' => get_string('matrixuserid_desc', 'communication_matrix'),
'descriptionformat' => 1,
'categoryid' => $categoryid,
'forceunique' => 1,
'visible' => 0,
@@ -24,17 +24,15 @@
$string['cachedef_serverversions'] = 'Matrix server version information for running servers';
$string['matrixuserid'] = 'Matrix user ID';
$string['matrixuserid_desc'] = 'The user ID to be used for Matrix';
$string['matrixhomeserverurl'] = 'Home server URL';
$string['matrixhomeserverurl_desc'] = 'The URL of the Synapse home server to connect to, for user and room creation.';
$string['matrixhomeserverurl'] = 'Homeserver URL';
$string['matrixhomeserverurl_desc'] = 'The URL of the Synapse homeserver to connect to, for user and room creation.';
$string['matrixaccesstoken'] = 'Access token';
$string['matrixaccesstoken_desc'] = 'Admin access token to authenticate against the Synapse Home server.';
$string['matrixaccesstoken_desc'] = 'Access token for the account which will perform actions on the homeserver.';
$string['matrixrefreshtoken'] = 'Refresh token';
$string['matrixrefreshtoken_desc'] = 'Admin refresh token to associated with the access token.';
$string['matrixelementurl'] = 'Element web URL';
$string['matrixelementurl_desc'] = 'The URL to Element Web instance.';
$string['matrixroomtopic'] = 'Room topic';
$string['matrixroomtopic_help'] = 'A short description of what this room is for.';
$string['matrix:moderator'] = 'Matrix moderator';
$string['pluginname'] = 'Matrix';
$string['privacy:metadata'] = 'Matrix communication plugin does not store any personal data.';
$string['privacy:metadata'] = 'The Matrix communication plugin does not store any personal data.';
+3 -9
View File
@@ -28,20 +28,14 @@ if ($hassiteconfig) {
// Home server URL.
$name = new lang_string('matrixhomeserverurl', 'communication_matrix');
$desc = new lang_string('matrixhomeserverurl_desc', 'communication_matrix');
$settings->add(new admin_setting_requiredtext('communication_matrix/matrixhomeserverurl', $name, $desc, ''));
$settings->add(new admin_setting_configtext('communication_matrix/matrixhomeserverurl', $name, $desc, ''));
// Access token.
$name = new lang_string('matrixaccesstoken', 'communication_matrix');
$desc = new lang_string('matrixaccesstoken_desc', 'communication_matrix');
$settings->add(new admin_setting_requiredpasswordunmask('communication_matrix/matrixaccesstoken', $name, $desc, ''));
// Refresh token.
$name = new lang_string('matrixrefreshtoken', 'communication_matrix');
$desc = new lang_string('matrixrefreshtoken_desc', 'communication_matrix');
$settings->add(new admin_setting_requiredpasswordunmask('communication_matrix/matrixrefreshtoken', $name, $desc, ''));
$settings->add(new admin_setting_configpasswordunmask('communication_matrix/matrixaccesstoken', $name, $desc, ''));
// Element web URL.
$name = new lang_string('matrixelementurl', 'communication_matrix');
$desc = new lang_string('matrixelementurl_desc', 'communication_matrix');
$settings->add(new admin_setting_requiredtext('communication_matrix/matrixelementurl', $name, $desc, ''));
$settings->add(new admin_setting_configtext('communication_matrix/matrixelementurl', $name, '', ''));
}
@@ -31,10 +31,10 @@ Feature: Display communication room status banner
# Then I should see "Your Matrix room is ready!" in the "page-content" "region"
# # This is a one time message per user.
# When I reload the page
# Then I should not see "Your Matrix room is ready!" in the "page-content" "region"
# Then I should not see "Your Matrix room is ready." in the "page-content" "region"
# # Not for students to see.
# When I am on the "Test course" "Course" page logged in as "student1"
# Then I should not see "Your Matrix room is ready!" in the "page-content" "region"
# Then I should not see "Your Matrix room is ready." in the "page-content" "region"
Scenario: Enabling or disabling the matrix plugin hides the banner accordingly
Given I am on the "Test course" "Course" page logged in as "teacher1"
@@ -52,7 +52,7 @@ class communication_feature_test extends \advanced_testcase {
*
* @covers ::create_chat_room
*/
public function test_create_chat_room() {
public function test_create_chat_room(): void {
// Set up the test data first.
$communication = \core_communication\api::load_by_instance(
component: 'communication_matrix',
@@ -296,7 +296,7 @@ class communication_feature_test extends \advanced_testcase {
*
* @return array
*/
public function avatar_provider(): array {
public static function avatar_provider(): array {
return [
'Empty to avatar' => [
null,
@@ -539,4 +539,24 @@ class communication_feature_test extends \advanced_testcase {
$communication->reload();
return $communication;
}
/**
* Test if the selected provider is configured.
*
* @covers ::is_configured
*/
public function test_is_configured(): void {
$course = $this->get_course();
$communicationprocessor = processor::load_by_instance(
component: 'core_course',
instancetype: 'coursecommunication',
instanceid: $course->id
);
$this->assertTrue($communicationprocessor->get_room_provider()->is_configured());
// Unset communication_matrix settings.
unset_config('matrixhomeserverurl', 'communication_matrix');
unset_config('matrixaccesstoken', 'communication_matrix');
$this->assertFalse($communicationprocessor->get_room_provider()->is_configured());
}
}
@@ -217,6 +217,9 @@ class matrix_user_manager_test extends \advanced_testcase {
* Test creation of matrix user profile fields.
*/
public function test_create_matrix_user_profile_fields(): void {
global $CFG;
require_once("{$CFG->dirroot}/user/profile/lib.php");
$this->resetAfterTest();
$matrixprofilefield = get_config('communication_matrix', 'matrixuserid_field');
+1 -1
View File
@@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'communication_matrix';
$plugin->version = 2023090600;
$plugin->version = 2023092300;
$plugin->requires = 2023011300;
$plugin->maturity = MATURITY_ALPHA;
+6
View File
@@ -18,8 +18,11 @@ namespace core_communication;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../provider/matrix/tests/matrix_test_helper_trait.php');
require_once(__DIR__ . '/communication_test_helper_trait.php');
use \communication_matrix\matrix_test_helper_trait;
/**
* Class api_test to test the communication public api and its associated methods.
*
@@ -30,12 +33,15 @@ require_once(__DIR__ . '/communication_test_helper_trait.php');
* @covers \core_communication\api
*/
class api_test extends \advanced_testcase {
use matrix_test_helper_trait;
use communication_test_helper_trait;
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->setup_communication_configs();
$this->initialise_mock_server();
}
/**
@@ -33,7 +33,8 @@ Feature: Access the communication configuration page
@javascript
Scenario: The communication form fields toggle dynamically when valid provider is set
Given I am on the "Test course" "Course" page logged in as "teacher1"
Given a Matrix mock server is configured
And I am on the "Test course" "Course" page logged in as "teacher1"
When I navigate to "Communication" in current page administration
And I set the following fields to these values:
| selectedcommunication | communication_matrix |
+17 -5
View File
@@ -18,8 +18,11 @@ namespace core_communication;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../provider/matrix/tests/matrix_test_helper_trait.php');
require_once(__DIR__ . '/communication_test_helper_trait.php');
use \communication_matrix\matrix_test_helper_trait;
/**
* Class processor_test to test the communication internal api and its associated methods.
*
@@ -30,8 +33,17 @@ require_once(__DIR__ . '/communication_test_helper_trait.php');
* @coversDefaultClass \core_communication\processor
*/
class processor_test extends \advanced_testcase {
use matrix_test_helper_trait;
use communication_test_helper_trait;
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->setup_communication_configs();
$this->initialise_mock_server();
}
/**
* Test create instance.
*
@@ -417,18 +429,18 @@ class processor_test extends \advanced_testcase {
}
/**
* Test if the provider is enabled or disabled.
* Test if the provider is enabled and configured, or disabled.
*
* @covers ::is_provider_enabled
* @covers ::is_provider_available
*/
public function test_is_provider_enabled(): void {
public function test_is_provider_available(): void {
$this->resetAfterTest();
$communicationprovider = 'communication_matrix';
$this->assertTrue(processor::is_provider_enabled($communicationprovider));
$this->assertTrue(processor::is_provider_available($communicationprovider));
// Now test is disabling the plugin returns false.
set_config('disabled', 1, $communicationprovider);
$this->assertFalse(processor::is_provider_enabled($communicationprovider));
$this->assertFalse(processor::is_provider_available($communicationprovider));
}
/**
@@ -82,7 +82,7 @@ class contenttype extends \core_contentbank\contenttype {
public function get_icon(\core_contentbank\content $content): string {
global $OUTPUT, $DB;
$iconurl = $OUTPUT->image_url('f/h5p-64', 'moodle')->out(false);
$iconurl = $OUTPUT->image_url('f/h5p')->out(false);
$file = $content->get_file();
if (!empty($file)) {
$h5p = \core_h5p\api::get_content_from_pathnamehash($file->get_pathnamehash());
+7 -1
View File
@@ -383,7 +383,7 @@ abstract class base {
* This method ensures that 3rd party course format plugins that still use 'numsections' continue to
* work but at the same time we no longer expect formats to have 'numsections' property.
*
* @return int
* @return int The last section number, or -1 if sections are entirely missing
*/
public function get_last_section_number() {
$course = $this->get_course();
@@ -392,6 +392,12 @@ abstract class base {
}
$modinfo = get_fast_modinfo($course);
$sections = $modinfo->get_section_info_all();
// Sections seem to be missing entirely. Avoid subsequent errors and return early.
if (count($sections) === 0) {
return -1;
}
return (int)max(array_keys($sections));
}
@@ -51,7 +51,7 @@
{{#editing}}
{{#editurl}}
<div class="editavailability border-top mt-2 py-2">
<a href="{{editurl}}" class="px-2 py-1">{{#pix}} i/edit, core {{/pix}}{{#str}}editsettings, availability{{/str}}</a>
<a href="{{editurl}}" class="btn btn-sm py-0">{{#pix}} i/edit, core {{/pix}}{{#str}}editsettings, availability{{/str}}</a>
</div>
{{/editurl}}
{{/editing}}
+27 -2
View File
@@ -14,14 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
/**
* Course related unit tests
*
* @package core_course
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_courseformat\base
*/
class base_test extends advanced_testcase {
@@ -368,6 +367,32 @@ class base_test extends advanced_testcase {
);
}
/**
* Test that retrieving last section number for a course
*
* @covers ::get_last_section_number
*/
public function test_get_last_section_number(): void {
global $DB;
$this->resetAfterTest();
// Course with two additional sections.
$courseone = $this->getDataGenerator()->create_course(['numsections' => 2]);
$this->assertEquals(2, course_get_format($courseone)->get_last_section_number());
// Course without additional sections, section zero is the "default" section that always exists.
$coursetwo = $this->getDataGenerator()->create_course(['numsections' => 0]);
$this->assertEquals(0, course_get_format($coursetwo)->get_last_section_number());
// Course without additional sections, manually remove section zero, as "course_delete_section" prevents that. This
// simulates course data integrity issues that previously triggered errors.
$coursethree = $this->getDataGenerator()->create_course(['numsections' => 0]);
$DB->delete_records('course_sections', ['course' => $coursethree->id, 'section' => 0]);
$this->assertEquals(-1, course_get_format($coursethree)->get_last_section_number());
}
/**
* Test for the default delete format data behaviour.
*
+1 -1
View File
@@ -1,6 +1,6 @@
This files describes API changes for course formats
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
Overview of this plugin type at https://moodledev.io/docs/apis/plugintypes/format
=== 4.3 ===
* New core_courseformat\output\activitybadge class that can be extended by any module to display content near the activity name.
+17 -6
View File
@@ -406,14 +406,25 @@ class format_weeks extends core_courseformat\base {
} else {
$sectionnum = $section;
}
$oneweekseconds = 604800;
// Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight
// savings and the date changes.
$startdate = $startdate + 7200;
// Create a DateTime object for the start date.
$startdateobj = new DateTime("@$startdate");
// Calculate the interval for one week.
$oneweekinterval = new DateInterval('P7D');
// Calculate the interval for the specified number of sections.
for ($i = 1; $i < $sectionnum; $i++) {
$startdateobj->add($oneweekinterval);
}
// Calculate the end date.
$enddateobj = clone $startdateobj;
$enddateobj->add($oneweekinterval);
$dates = new stdClass();
$dates->start = $startdate + ($oneweekseconds * ($sectionnum - 1));
$dates->end = $dates->start + $oneweekseconds;
$dates->start = $startdateobj->getTimestamp();
$dates->end = $enddateobj->getTimestamp();
return $dates;
}
@@ -221,8 +221,8 @@ class format_weeks_test extends \advanced_testcase {
$courseform = new \testable_course_edit_form(null, $args);
$courseform->definition_after_data();
// format_weeks::get_section_dates is adding 2h to avoid DST problems, we need to replicate it here.
$enddate = $params['startdate'] + (WEEKSECS * $params['numsections']) + 7200;
// Calculate the expected end date.
$enddate = $params['startdate'] + (WEEKSECS * $params['numsections']);
$weeksformat = course_get_format($course->id);
$this->assertEquals($enddate, $weeksformat->get_default_course_enddate($courseform->get_quick_form()));
-4
View File
@@ -97,8 +97,6 @@ class core_course_renderer extends plugin_renderer_base {
*
* @deprecated since 2.5
*
* Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5
*
* @param array $ignored argument ignored
* @return string
*/
@@ -112,8 +110,6 @@ class core_course_renderer extends plugin_renderer_base {
*
* @deprecated since 2.5
*
* Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5
*
* @param array $category
* @param int $depth
* @return string
@@ -99,7 +99,6 @@ Feature: Toggle activities visibility from the course page
Scenario: Activities can be made available but not visible on a course page
Given the following config values are set as admin:
| allowstealth | 1 |
And I change window size to "large"
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I open "Test assignment name" actions menu
@@ -20,9 +20,9 @@ Feature: Course communication
| Course short name | C2 |
And I press "Save and display"
When I navigate to "Communication" in current page administration
Then the field "Communication service" matches value "None"
Then the field "Provider" matches value "None"
Scenario: I should have communication disabled by default for existing courses
Given I am on "Course 1" course homepage
When I navigate to "Communication" in current page administration
Then the field "Communication service" matches value "None"
Then the field "Provider" matches value "None"
+2 -6
View File
@@ -1042,9 +1042,7 @@ class enrol_database_plugin extends enrol_plugin {
$rs->Close();
} else {
$fields_obj = $rs->FetchObj();
$columns = array_keys((array)$fields_obj);
$columns = array_keys($rs->fetchRow());
echo $OUTPUT->notification('External enrolment table contains following columns:<br />'.implode(', ', $columns), 'notifysuccess');
$rs->Close();
}
@@ -1061,9 +1059,7 @@ class enrol_database_plugin extends enrol_plugin {
$rs->Close();
} else {
$fields_obj = $rs->FetchObj();
$columns = array_keys((array)$fields_obj);
$columns = array_keys($rs->fetchRow());
echo $OUTPUT->notification('External course table contains following columns:<br />'.implode(', ', $columns), 'notifysuccess');
$rs->Close();
}
+3 -1
View File
@@ -176,7 +176,9 @@ class filter_test extends \basic_testcase {
'<p>text www.moodle.org&lt;/p> text' => '<p>text <a href="http://www.moodle.org" class="_blanktarget">www.moodle.org</a>&lt;/p> text',
// Some more urls.
'<link rel="search" type="application/opensearchdescription+xml" href="/osd.jsp" title="Peer review - Moodle Tracker"/>' => '<link rel="search" type="application/opensearchdescription+xml" href="/osd.jsp" title="Peer review - Moodle Tracker"/>',
'<a href="https://docs.moodle.org/dev/Main_Page"></a><span>www.google.com</span><span class="placeholder"></span>' => '<a href="https://docs.moodle.org/dev/Main_Page"></a><span><a href="http://www.google.com" class="_blanktarget">www.google.com</a></span><span class="placeholder"></span>',
'<a href="https://moodledev.io"></a><span>www.google.com</span><span class="placeholder"></span>' =>
'<a href="https://moodledev.io"></a><span><a href="http://www.google.com" class="_blanktarget">www.google.com</a>' .
'</span><span class="placeholder"></span>',
'http://nolandforzombies.com <a href="zombiesFTW.com">Zombies FTW</a> http://aliens.org' => '<a href="http://nolandforzombies.com" class="_blanktarget">http://nolandforzombies.com</a> <a href="zombiesFTW.com">Zombies FTW</a> <a href="http://aliens.org" class="_blanktarget">http://aliens.org</a>',
// Test 'nolink' class.
'URL: <span class="nolink">http://moodle.org</span>' => 'URL: <span class="nolink">http://moodle.org</span>',
-6
View File
@@ -604,12 +604,6 @@ class add_category extends dynamic_form {
$local = $this->get_gradecategory();
$gradecategory = $local['gradecategory'];
// GRADE ITEM.
// Grade item data saved with prefix "grade_item_".
$data->grade_item_gradepass = $local['categoryitem']->grade_item_gradepass;
$data->grade_item_grademax = $local['categoryitem']->grade_item_grademax;
$data->grade_item_grademin = $local['categoryitem']->grade_item_grademin;
grade_edit_tree::update_gradecategory($gradecategory, $data);
return [
+2 -1
View File
@@ -133,7 +133,8 @@ if ($id) {
$mform = new edit_category_form(null, array('current'=>$category, 'gpr'=>$gpr));
$simpleform = new add_category(null, ['category' => $grade_category->id, 'courseid' => $courseid, 'gpr' => $gpr]);
if ($simpledata = $simpleform->get_data()) {
// Data has been carried over from the dynamic form.
if ($simpledata = $simpleform->get_submitted_data()) {
$mform->set_data($simpledata);
}
+2 -1
View File
@@ -109,7 +109,8 @@ $item->cancontrolvisibility = $grade_item->can_control_visibility();
$mform = new edit_item_form(null, array('current'=>$item, 'gpr'=>$gpr));
$simpleform = new add_item(null, ['itemid' => $grade_item->id, 'courseid' => $courseid, 'gpr' => $gpr]);
if ($simpledata = $simpleform->get_data()) {
// Data has been carried over from the dynamic form.
if ($simpledata = $simpleform->get_submitted_data()) {
$mform->set_data($simpledata);
}
+2 -2
View File
@@ -276,7 +276,7 @@ class grade_edit_tree {
}
$categoryrow->attributes['data-aggregation'] = $category->aggregation;
$categoryrow->attributes['data-grademax'] = $category->grade_item->grademax;
$categoryrow->attributes['data-aggregationcoef'] = $category->grade_item->aggregationcoef;
$categoryrow->attributes['data-aggregationcoef'] = floatval($category->grade_item->aggregationcoef);
$categoryrow->attributes['data-itemid'] = $category->grade_item->id;
$categoryrow->attributes['data-hidden'] = 'false';
foreach ($rowclasses as $class) {
@@ -350,7 +350,7 @@ class grade_edit_tree {
} else {
$gradeitemrow->attributes['data-parent-category'] = $parent_eid;
$gradeitemrow->attributes['data-grademax'] = $object->grademax;
$gradeitemrow->attributes['data-aggregationcoef'] = $object->aggregationcoef;
$gradeitemrow->attributes['data-aggregationcoef'] = floatval($object->aggregationcoef);
}
foreach ($rowclasses as $class) {
$gradeitemrow->attributes['class'] .= ' ' . $class;
+2 -1
View File
@@ -114,7 +114,8 @@ if (empty($parent_category)) {
$mform->set_data($item);
$simpleform = new add_outcome(null, ['itemid' => $grade_item->id, 'courseid' => $courseid, 'gpr' => $gpr]);
if ($simpledata = $simpleform->get_data()) {
// Data has been carried over from the dynamic form.
if ($simpledata = $simpleform->get_submitted_data()) {
$mform->set_data($simpledata);
}
+2 -2
View File
@@ -1,10 +1,10 @@
define("gradereport_grader/stickycolspan",["exports","jquery"],(function(_exports,_jquery){var obj;
define("gradereport_grader/stickycolspan",["exports","jquery","core/sticky-footer"],(function(_exports,_jquery,_stickyFooter){var obj;
/**
* Javascript module for fixing the position of sticky headers with multiple colspans
*
* @module gradereport_grader/stickycolspan
* @copyright 2022 Bas Brands <bas@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const SELECTORS_GRADEPARENT=".gradeparent",SELECTORS_STUDENTHEADER="#studentheader",SELECTORS_TABLEHEADER="th.header",SELECTORS_BEHAT="body.behat-site",SELECTORS_USERDROPDOWN=".userrow th .dropdown",SELECTORS_AVERAGEROWHEADER=".lastrow th";_exports.init=()=>{(0,_jquery.default)(SELECTORS_USERDROPDOWN).on("show.bs.dropdown hide.bs.dropdown",(e=>{e.target.closest(SELECTORS_TABLEHEADER).classList.toggle("actions-menu-active")}));if(new IntersectionObserver((_ref=>{let[e]=_ref;return e.target.closest("tr").classList.toggle("pinned",e.intersectionRatio<1)}),{threshold:[1]}).observe(document.querySelector(SELECTORS_AVERAGEROWHEADER)),!document.querySelector(SELECTORS_BEHAT)){const grader=document.querySelector(SELECTORS_GRADEPARENT),tableHeaders=grader.querySelectorAll(SELECTORS_TABLEHEADER),studentHeader=grader.querySelector(SELECTORS_STUDENTHEADER),leftOffset=getComputedStyle(studentHeader).getPropertyValue("left"),rightOffset=getComputedStyle(studentHeader).getPropertyValue("right");tableHeaders.forEach((tableHeader=>{if(tableHeader.colSpan>1){const addOffset=tableHeader.offsetWidth-studentHeader.offsetWidth;window.right_to_left()?tableHeader.style.right="calc("+rightOffset+" - "+addOffset+"px )":tableHeader.style.left="calc("+leftOffset+" - "+addOffset+"px )"}}))}}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const SELECTORS_GRADEPARENT=".gradeparent",SELECTORS_STUDENTHEADER="#studentheader",SELECTORS_TABLEHEADER="th.header",SELECTORS_BEHAT="body.behat-site",SELECTORS_USERDROPDOWN=".userrow th .dropdown",SELECTORS_AVERAGEROWHEADER=".lastrow th";_exports.init=()=>{if((0,_jquery.default)(SELECTORS_USERDROPDOWN).on("show.bs.dropdown hide.bs.dropdown",(e=>{e.target.closest(SELECTORS_TABLEHEADER).classList.toggle("actions-menu-active")})),defineAverageRowIntersectionObserver(!0),document.addEventListener(_stickyFooter.eventTypes.stickyFooterStateChanged,(e=>{defineAverageRowIntersectionObserver(e.detail.enabled)})),!document.querySelector(SELECTORS_BEHAT)){const grader=document.querySelector(SELECTORS_GRADEPARENT),tableHeaders=grader.querySelectorAll(SELECTORS_TABLEHEADER),studentHeader=grader.querySelector(SELECTORS_STUDENTHEADER),leftOffset=getComputedStyle(studentHeader).getPropertyValue("left"),rightOffset=getComputedStyle(studentHeader).getPropertyValue("right");tableHeaders.forEach((tableHeader=>{if(tableHeader.colSpan>1){const addOffset=tableHeader.offsetWidth-studentHeader.offsetWidth;window.right_to_left()?tableHeader.style.right="calc("+rightOffset+" - "+addOffset+"px )":tableHeader.style.left="calc("+leftOffset+" - "+addOffset+"px )"}}))}};const defineAverageRowIntersectionObserver=stickyFooterEnabled=>{const stickyFooterHeight=stickyFooterEnabled?document.querySelector(_stickyFooter.SELECTORS.STICKYFOOTER).offsetHeight:null;new IntersectionObserver((_ref=>{let[e]=_ref;return e.target.closest("tr").classList.toggle("pinned",e.intersectionRatio<1)}),{rootMargin:stickyFooterHeight?"0px 0px -".concat(stickyFooterHeight,"px 0px"):"0px",threshold:[1]}).observe(document.querySelector(SELECTORS_AVERAGEROWHEADER))}}));
//# sourceMappingURL=stickycolspan.min.js.map
File diff suppressed because one or more lines are too long
+30 -8
View File
@@ -22,6 +22,7 @@
*/
import $ from 'jquery';
import {SELECTORS as stickyFooterSelectors, eventTypes as stickyFooterEvents} from 'core/sticky-footer';
const SELECTORS = {
GRADEPARENT: '.gradeparent',
@@ -43,14 +44,14 @@ export const init = () => {
// The closest heading element has sticky positioning which affects the stacking context in this case.
e.target.closest(SELECTORS.TABLEHEADER).classList.toggle('actions-menu-active');
});
// Register an observer that will bump up the z-index value of the average overall row when it's pinned to prevent
// the row being cut-off by the user column cells or other components within the report table that have higher
// z-index values.
const observer = new IntersectionObserver(
([e]) => e.target.closest('tr').classList.toggle('pinned', e.intersectionRatio < 1),
{threshold: [1]}
);
observer.observe(document.querySelector(SELECTORS.AVERAGEROWHEADER));
defineAverageRowIntersectionObserver(true);
// Add an event listener to the sticky footer toggled event to re-define the average row intersection observer
// accordingly. This is needed as on narrow screens when scrolling vertically the sticky footer is enabled and
// disabled dynamically.
document.addEventListener(stickyFooterEvents.stickyFooterStateChanged, (e) => {
defineAverageRowIntersectionObserver(e.detail.enabled);
});
if (!document.querySelector(SELECTORS.BEHAT)) {
const grader = document.querySelector(SELECTORS.GRADEPARENT);
@@ -71,3 +72,24 @@ export const init = () => {
});
}
};
/**
* Define the intersection observer that will make sure that the average overall row is properly pinned.
*
* @param {boolean} stickyFooterEnabled Whether the page shows a sticky footer or not.
*/
const defineAverageRowIntersectionObserver = (stickyFooterEnabled) => {
const stickyFooterHeight = stickyFooterEnabled ? document.querySelector(stickyFooterSelectors.STICKYFOOTER).offsetHeight : null;
// Register an observer that will bump up the z-index value of the average overall row when it's pinned to prevent
// the row being cut-off by the user column cells or other components within the report table that have higher
// z-index values. If the page has a sticky footer, we need to make sure that the bottom root margin of the observer
// subtracts the height of the sticky footer to prevent the row being cut-off by the footer.
const intersectionObserver = new IntersectionObserver(
([e]) => e.target.closest('tr').classList.toggle('pinned', e.intersectionRatio < 1),
{
rootMargin: stickyFooterHeight ? `0px 0px -${stickyFooterHeight}px 0px` : "0px",
threshold: [1]
}
);
intersectionObserver.observe(document.querySelector(SELECTORS.AVERAGEROWHEADER));
};
+6
View File
@@ -269,3 +269,9 @@
width: 300px;
max-height: 300px;
}
@media only screen and (min-width: 768px) {
.path-grade-report-grader .gradeparent table {
padding-right: 6em;
}
}
@@ -39,7 +39,6 @@ Feature: We can change what we are viewing on the grader report
@javascript
Scenario: View and minimise the grader report containing hidden activities
When I am on "Course 1" course homepage with editing mode on
And I change window size to "large"
And I open "Test assignment name 2" actions menu
And I choose "Availability > Hide on course page" in the open action menu
And I am on "Course 1" course homepage with editing mode off
+18 -11
View File
@@ -445,7 +445,7 @@ class user extends grade_report {
$depth = $element['depth'];
$gradeobject = $element['object'];
$eid = $gradeobject->id;
$element['userid'] = $this->user->id;
$element['userid'] = $userid = $this->user->id;
$fullname = $this->gtree->get_element_header($element, true, false, true, false, true);
$data = [];
$gradeitemdata = [];
@@ -587,7 +587,7 @@ class user extends grade_report {
if ($this->showweight) {
$data['weight']['class'] = $class;
$data['weight']['content'] = '-';
$data['weight']['headers'] = "$headercat $headerrow weight";
$data['weight']['headers'] = "$headercat $headerrow weight$userid";
// Has a weight assigned, might be extra credit.
// This obliterates the weight because it provides a more informative description.
@@ -690,7 +690,7 @@ class user extends grade_report {
$gradegrade->grade_item, true) . $gradestatus;
$gradeitemdata['graderaw'] = $gradeval;
}
$data['grade']['headers'] = "$headercat $headerrow grade";
$data['grade']['headers'] = "$headercat $headerrow grade$userid";
$gradeitemdata['gradeformatted'] = $data['grade']['content'];
}
@@ -701,7 +701,7 @@ class user extends grade_report {
GRADE_DISPLAY_TYPE_REAL,
$this->rangedecimals
);
$data['range']['headers'] = "$headercat $headerrow range";
$data['range']['headers'] = "$headercat $headerrow range$userid";
$gradeitemdata['rangeformatted'] = $data['range']['content'];
$gradeitemdata['grademin'] = $gradegrade->grade_item->grademin;
@@ -733,7 +733,7 @@ class user extends grade_report {
GRADE_DISPLAY_TYPE_PERCENTAGE
);
}
$data['percentage']['headers'] = "$headercat $headerrow percentage";
$data['percentage']['headers'] = "$headercat $headerrow percentage$userid";
$gradeitemdata['percentageformatted'] = $data['percentage']['content'];
}
@@ -763,7 +763,7 @@ class user extends grade_report {
GRADE_DISPLAY_TYPE_LETTER
);
}
$data['lettergrade']['headers'] = "$headercat $headerrow lettergrade";
$data['lettergrade']['headers'] = "$headercat $headerrow lettergrade$userid";
$gradeitemdata['lettergradeformatted'] = $data['lettergrade']['content'];
}
@@ -797,7 +797,7 @@ class user extends grade_report {
$gradeitemdata['rank'] = $rank;
$gradeitemdata['numusers'] = $numusers;
}
$data['rank']['headers'] = "$headercat $headerrow rank";
$data['rank']['headers'] = "$headercat $headerrow rank$userid";
}
// Average.
@@ -811,7 +811,7 @@ class user extends grade_report {
} else {
$data['average']['content'] = '-';
}
$data['average']['headers'] = "$headercat $headerrow average";
$data['average']['headers'] = "$headercat $headerrow average$userid";
}
// Feedback.
@@ -838,13 +838,13 @@ class user extends grade_report {
['context' => $gradegrade->get_context()]);
$gradeitemdata['feedback'] = $gradegrade->feedback;
}
$data['feedback']['headers'] = "$headercat $headerrow feedback";
$data['feedback']['headers'] = "$headercat $headerrow feedback$userid";
}
// Contribution to the course total column.
if ($this->showcontributiontocoursetotal) {
$data['contributiontocoursetotal']['class'] = $class;
$data['contributiontocoursetotal']['content'] = '-';
$data['contributiontocoursetotal']['headers'] = "$headercat $headerrow contributiontocoursetotal";
$data['contributiontocoursetotal']['headers'] = "$headercat $headerrow contributiontocoursetotal$userid";
}
$this->gradeitemsdata[] = $gradeitemdata;
@@ -1026,9 +1026,10 @@ class user extends grade_report {
];
// Set the table headings.
$userid = $this->user->id;
foreach ($this->tableheaders as $index => $heading) {
$headingcell = new \html_table_cell($heading);
$headingcell->attributes['id'] = $this->tablecolumns[$index];
$headingcell->attributes['id'] = $this->tablecolumns[$index] . $userid;
$headingcell->attributes['class'] = "header column-{$this->tablecolumns[$index]}";
if ($index == 0) {
$headingcell->colspan = $this->maxdepth;
@@ -1054,6 +1055,12 @@ class user extends grade_report {
if (!is_null($content)) {
$rowcell = new \html_table_cell($content);
// Grade item names and cateogry names are referenced in the `headers` attribute of table cells.
// These table cells should be set to <th> tags.
if ($tablecolumn === 'itemname') {
$rowcell->header = true;
}
if (isset($rowdata[$tablecolumn]['class'])) {
$rowcell->attributes['class'] = $rowdata[$tablecolumn]['class'];
}
+23 -14
View File
@@ -67,6 +67,8 @@
.grade-report-user .user-grade .baggt,
.grade-report-user .user-grade .baggb {
font-weight: bold;
background-color: #f8f9fa;
border: none;
}
.path-grade-report-user .user-report-container,
@@ -99,23 +101,30 @@
border-bottom: 1px solid #dee2e6;
}
.path-grade-report-user .user-grade td.category,
.grade-report-user .user-grade td.category {
.path-grade-report-user .user-grade th.column-itemname:not(.header,.category,.baggt,.baggb),
.grade-report-user .user-grade th.column-itemname:not(.header,.category,.baggt,.baggb) {
background-color: white;
font-weight: normal;
border-bottom: 1px solid #dee2e6;
}
.path-grade-report-user .user-grade th.category,
.grade-report-user .user-grade th.category {
background-color: white;
border: 1px solid #dee2e6;
padding-left: 10px;
font-weight: bold;
}
.path-grade-report-user .user-grade td.category a[aria-expanded="true"] .expanded,
.path-grade-report-user .user-grade td.category a[aria-expanded="false"] .collapsed,
.grade-report-user .user-grade td.category a[aria-expanded="true"] .expanded,
.grade-report-user .user-grade td.category a[aria-expanded="false"] .collapsed {
.path-grade-report-user .user-grade th.category a[aria-expanded="true"] .expanded,
.path-grade-report-user .user-grade th.category a[aria-expanded="false"] .collapsed,
.grade-report-user .user-grade th.category a[aria-expanded="true"] .expanded,
.grade-report-user .user-grade th.category a[aria-expanded="false"] .collapsed {
display: none;
}
.path-grade-report-user .user-grade td.category a.toggle-category,
.grade-report-user .user-grade td.category a.toggle-category {
.path-grade-report-user .user-grade th.category a.toggle-category,
.grade-report-user .user-grade th.category a.toggle-category {
height: 24px;
width: 24px;
font-size: 12px;
@@ -123,8 +132,8 @@
margin-right: 3px;
}
.path-grade-report-user .user-grade td.category a.toggle-category i,
.grade-report-user .user-grade td.category a.toggle-category i {
.path-grade-report-user .user-grade th.category a.toggle-category i,
.grade-report-user .user-grade th.category a.toggle-category i {
font-size: 12px;
width: 12px;
height: 12px;
@@ -173,14 +182,14 @@
}
@media print {
.path-grade-report-user .user-grade td.category,
.grade-report-user .user-grade td.category {
.path-grade-report-user .user-grade th.category,
.grade-report-user .user-grade th.category {
border-left: none;
border-right: none;
}
.path-grade-report-user .user-grade td.category a.toggle-category,
.grade-report-user .user-grade td.category a.toggle-category
.path-grade-report-user .user-grade th.category a.toggle-category,
.grade-report-user .user-grade th.category a.toggle-category
.path-grade-report-user #page-footer {
display: none;
}
@@ -253,7 +253,8 @@ Feature: Teachers can perform bulk actions on grade items and categories in the
And I press tab
And the focused element is "Move" "button" in the "Move items" "dialogue"
When I press the enter key
And I wait to be redirected
And I wait until the page is ready
And I wait "2" seconds
And I press tab key in "region-main" "region"
# Confirm that 'Grade item 1' and 'Grade item 2' have been moved to 'Category 3'
And I click on grade item menu "Grade item 1" of type "gradeitem" on "setup" page
@@ -1,4 +1,4 @@
@core @core_grades @javascript
@core @core_grades @javascript @testtt
Feature: We carry over data from modal to advanced grade item settings
In order to setup grade items quickly
As an teacher
@@ -103,6 +103,25 @@ Feature: We carry over data from modal to advanced grade item settings
| grade_item_aggregationcoef2 | 100 |
| Parent category | Some cool grade category |
And I press "Cancel"
And I choose the "Add category" item in the "Add" action menu
# Confirm that the form values are carried over even if some mandatory fields are missing (e.g. Category name).
And I set the following fields to these values:
| Aggregation | Mean of grades |
| Minimum grade | 1 |
| Maximum grade | 99 |
| Weight adjusted | 1 |
| grade_item_aggregationcoef2 | 100 |
| Parent category | Some cool grade category |
And I click on "Show more..." "link" in the ".modal-dialog" "css_element"
And the following fields match these values:
| Category name | |
| Aggregation | Mean of grades |
| Minimum grade | 1 |
| Maximum grade | 99 |
| Weight adjusted | 1 |
| grade_item_aggregationcoef2 | 100 |
| Parent category | Some cool grade category |
And I press "Cancel"
And I wait until the page is ready
And I choose the "Add outcome item" item in the "Add" action menu
And I set the following fields to these values:
+1 -1
View File
@@ -86,6 +86,6 @@ $string['welcomep20'] = 'Vous voyez cette page, car vous avez installé Moodle c
$string['welcomep30'] = 'Cette version de <b>{$a->installername}</b> comprend des logiciels qui créent un environnement dans lequel <b>Moodle</b> va fonctionner, à savoir :';
$string['welcomep40'] = 'Ce paquet contient également <b>Moodle {$a->moodlerelease} ({$a->moodleversion})</b>.';
$string['welcomep50'] = 'L\'utilisation de tous les logiciels de ce paquetage est soumis à l\'acceptation de leurs licences respectives. Le paquetage complet <strong>{$a->installername}</strong> est un <a href="https://www.opensource.org/docs/definition_plain.html">logiciel libre</a>. Il est distribué sous licence <a href="https://www.gnu.org/copyleft/gpl.html">GPL</a>.';
$string['welcomep60'] = 'Les pages suivantes vous aideront pas à pas à configurer et mettre en place <strong>Moodle</strong> sur votre ordinateur. Il vous sera possible d\'accepter les réglages par défaut ou, facultativement, de les adapter à vos propres besoins.';
$string['welcomep60'] = 'Les pages suivantes vous aideront pas à pas à configurer et installer <strong>Moodle</strong> sur votre ordinateur. Il vous sera possible d\'accepter les réglages par défaut ou, facultativement, de les adapter à vos propres besoins.';
$string['welcomep70'] = 'Cliquer sur le bouton « Suivant » ci-dessous pour continuer l\'installation de <b>Moodle</b>.';
$string['wwwroot'] = 'Adresse web';
+3 -3
View File
@@ -585,8 +585,8 @@ $string['enableanalytics'] = 'Analytics';
$string['enableblogs'] = 'Enable blogs';
$string['enablecalendarexport'] = 'Enable calendar export';
$string['enablecomments'] = 'Enable comments';
$string['enablecommunicationsubsystem'] = 'Enable communication subsystem';
$string['enablecommunicationsubsystem_desc'] = 'This setting enables the new communication subsystem. This new subsystem allow teachers and students to more easily communicate while using Moodle for teaching and learning';
$string['enablecommunicationsubsystem'] = 'Enable communication providers';
$string['enablecommunicationsubsystem_desc'] = 'Allow integration with communication providers such as Matrix so teachers and students can communicate more easily. You can manage these integrations in <a href="settings.php?section=managecommunicationproviders">Plugins</a>.';
$string['enablecourserelativedates'] = 'Enable course relative dates';
$string['enablecourserelativedates_desc'] = 'Allow courses to be set up to display dates relative to the user\'s start date in the course.';
$string['enablecourserequests'] = 'Enable course requests';
@@ -637,7 +637,7 @@ $string['environmentmariadbwrongdbtype'] = 'Wrong $CFG->dbtype. You need to chan
$string['errordeletingconfig'] = 'An error occurred while deleting the configuration records for plugin \'{$a}\'.';
$string['errorsetting'] = 'Could not save setting:';
$string['errorwithsettings'] = 'Some settings were not changed due to an error.';
$string['eventshandlersinuse'] = 'The following plugins in your system are using Events 1 API deprecated handlers: \'{$a}\'. Please, update them to use Events 2 API. See https://docs.moodle.org/dev/Event_2#Event_dispatching_and_observers.';
$string['eventshandlersinuse'] = 'The following plugins in your system are using Events 1 API deprecated handlers: \'{$a}\'. Please, update them to use Events 2 API. See https://docs.moodle.org/dev/Events_API#Event_dispatching_and_observers.';
$string['everyonewhocan'] = 'Everyone who can \'{$a}\'';
$string['exceptions'] = 'exceptions';
$string['execpathnotallowed'] = 'Setting executable and local paths disabled in config.php';
+4 -4
View File
@@ -26,9 +26,9 @@ $string['communication'] = 'Communication';
$string['communicationdisabled'] = 'Communication is disabled.';
$string['communicationprovidernotfound'] = 'The \'{$a}\' communication provider doesn\'t exist or is not recognised.';
$string['communicationroomname'] = 'Room name';
$string['communicationroomname_help'] = 'The name that participants see when they visit the room. If you leave this blank, a default room name will be automatically set.';
$string['communicationroomnameinfo'] = 'If the field is left blank, the course name is used as the room name.';
$string['communicationroompending'] = 'Your {$a} room will be ready soon.';
$string['communicationroomready'] = 'Your {$a} room is ready!';
$string['communicationroomready'] = 'Your {$a} room is ready.';
$string['managecommunicationproviders'] = 'Manage communication providers';
$string['nocommunicationinstance'] = 'No communication instance found.';
$string['nocommunicationprovider'] = 'No communication provider found.';
@@ -37,5 +37,5 @@ $string['privacy:metadata:communication_user'] = 'The communication user table s
$string['privacy:metadata:communication_user:commid'] = 'The communication ID for user mapping';
$string['privacy:metadata:communication_user:userid'] = 'The user ID for communication provider mapping for users';
$string['privacy:metadata:communication_user:synced'] = 'The user is synced with the provider or not';
$string['seleccommunicationprovider'] = 'Communication service';
$string['seleccommunicationprovider_help'] = 'The tool available for chat and related communication methods.';
$string['selectcommunicationprovider'] = 'Provider';
$string['selectcommunicationprovider_help'] = 'The tool available for chat and related communication methods.';
+1 -1
View File
@@ -65,7 +65,7 @@ $string['completion_setby:manual:markdone'] = '{$a->activityname} is marked by {
$string['completionrequirements'] = 'Completion requirements for {$a}';
$string['courseaccess'] = 'Course access';
$string['coursealreadyfinished'] = 'Course already finished';
$string['coursecommunication_desc'] = 'The default communication service for new courses. Existing courses will not have any default provider selected.';
$string['coursecommunication_desc'] = 'The default communication service for new courses. Existing courses will not have any provider selected by default.';
$string['coursecontentnotification'] = 'Send content change notification';
$string['coursecontentnotifnew'] = '{$a->coursename} new content';
$string['coursecontentnotifnewbody'] = '<p>{$a->moduletypename} <a href="{$a->link}">{$a->modulename}</a> is new in the course <a href="{$a->courselink}">{$a->coursename}</a>.</p><p><a href="{$a->notificationpreferenceslink}">Change your notification preferences</a></p>';
+2 -2
View File
@@ -978,7 +978,7 @@ This program is free software; you can redistribute it and/or modify it under th
This program 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 Moodle License information page for full details: https://docs.moodle.org/dev/License';
See the Moodle License information page for full details: https://moodledev.io/general/license';
$string['gpllicense'] = 'GPL license';
$string['gpl3'] = 'Copyright (C) 1999 onwards Martin Dougiamas (https://moodle.com)
@@ -986,7 +986,7 @@ This program is free software: you can redistribute it and/or modify it under th
This program 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 Moodle License information page for full details: https://docs.moodle.org/dev/License';
See the Moodle License information page for full details: https://moodledev.io/general/license';
$string['grades'] = 'Grades';
$string['gradenoun'] = 'Grade';
$string['gradeverb'] = 'Grade';
+6 -6
View File
@@ -135,17 +135,17 @@ define('CONTEXT_MODULE', 70);
*/
define('CONTEXT_BLOCK', 80);
/** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
/** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
define('RISK_MANAGETRUST', 0x0001);
/** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
/** Capability allows changes in system configuration - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
define('RISK_CONFIG', 0x0002);
/** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
/** Capability allows user to add scripted content - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
define('RISK_XSS', 0x0004);
/** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
/** Capability allows access to personal user information - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
define('RISK_PERSONAL', 0x0008);
/** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
/** Capability allows users to add content others may see - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
define('RISK_SPAM', 0x0010);
/** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
/** capability allows mass delete of data belonging to other users - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
define('RISK_DATALOSS', 0x0020);
/** rolename displays - the name as defined in the role definition, localised if name empty */

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